diff --git a/apps/telemetry-backend/src/prisma/contract.d.ts b/apps/telemetry-backend/src/prisma/contract.d.ts index 4c48fb3b1095..c89a352d9809 100644 --- a/apps/telemetry-backend/src/prisma/contract.d.ts +++ b/apps/telemetry-backend/src/prisma/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -609,7 +611,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/docs/architecture docs/ADR-INDEX.md b/docs/architecture docs/ADR-INDEX.md index f102c9230218..848eabbe31f3 100644 --- a/docs/architecture docs/ADR-INDEX.md +++ b/docs/architecture docs/ADR-INDEX.md @@ -81,6 +81,7 @@ Open this list before you propose a new feature or surface, or judge a feature r | 273 | A column of a type no data type claims is a raw column | A column whose database type no data type claims has data type `sql/raw`, whose parameter `sql` is the type exactly as the catalog prints it, and codec `sql/raw@1`, which passes values through typed `unknown`; migrations write the text into DDL and `db verify` compares it exactly; a Prisma 8 field declares one with ``Raw(sql`...`)``, and its default is a `sql` expression; the Prisma 7 reader reads `Unsupported("...")` as a raw column with no field | [ADR 273 - A column of a type no data type claims is a raw column.md](adrs/ADR%20273%20-%20A%20column%20of%20a%20type%20no%20data%20type%20claims%20is%20a%20raw%20column.md) | | 275 | Client generators are field attributes, and a type alias is expanded into each column | **Implemented.** `@onCreate(fn())` and `@onUpdate(fn())` set the client's generators, resolved from the column's codec through a generator function registry the stack assembles, with params from the column's type parameters; `@default` means a database default only; a `types {}` alias may carry `@default`, `@onCreate` and `@onUpdate`, is expanded into each column, and is never stored, so `storage.types` keeps only types a contract space registers; field presets are removed from SQL and Mongo, PSL and the TypeScript builder, and targets contribute aliases such as `temporal.UpdatedAt` | [ADR 275 - Client generators are field attributes, and a type alias is expanded into each column.md](adrs/ADR%20275%20-%20Client%20generators%20are%20field%20attributes%2C%20and%20a%20type%20alias%20is%20expanded%20into%20each%20column.md) | | 279 | Convenience types choose an explicit type from their arguments | A target can declare convenience types, named for what a field means, whose `as:` and `storage:` arguments pick one of its explicit, database-named types, with defaults that need no setup; the contract stores only the explicit type, and the tools write the convenience form. PostgreSQL's `DateTime`, `Date`, `Time` and `Duration` are the first | [ADR 279 - Convenience types choose an explicit type from their arguments.md](adrs/ADR%20279%20-%20Convenience%20types%20choose%20an%20explicit%20type%20from%20their%20arguments.md) | +| 309 | Value objects are storage entities, and a discriminator column names a value set | **Accepted.** A value object lives at `storage.namespaces..entries.valueObject.` in both families, and a domain field refers to it by storage coordinate; a SQL discriminator column refers to a value set keyed `.`; on a managed Postgres table both derive a check; a change to either changes the storage hash, and a plan with no operations is written and applied. Partially supersedes ADR 178. | [ADR 309 - Value objects are storage entities, and a discriminator column names a value set.md](adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md) | ## Query System @@ -263,6 +264,7 @@ Requests answered with a no, with what to use instead. Every Accepted row is on - **ADR 157** is partially superseded by its own October 12, 2026 amendment: the idea stands, and the amendment replaces how the contract realizes it - **ADR 170** is partially superseded by ADR 275: its field-preset half; type constructors and namespacing stand - **ADR 246** is superseded by ADR 275, except its two omission rules for `executionDefaults` and `typeParams`, which the Data Contract subsystem doc now states +- **ADR 178** is partially superseded by ADR 309: value objects are storage entities, not a domain section - Field presets were removed on 2026-10-22 (ADR 275); ADRs that show them carry a dated amendment note - **ADRs 104-118** form the core extension system architecture (decorators, attributes, capabilities, packs) - **ADRs 126-127** introduce PSL top-level blocks and views as composable extensions diff --git a/docs/architecture docs/adrs/ADR 172 - Contract domain-storage separation.md b/docs/architecture docs/adrs/ADR 172 - Contract domain-storage separation.md index b6c1e9f05642..66b70dc804fa 100644 --- a/docs/architecture docs/adrs/ADR 172 - Contract domain-storage separation.md +++ b/docs/architecture docs/adrs/ADR 172 - Contract domain-storage separation.md @@ -139,6 +139,8 @@ The contract has three levels, each serving a different consumer: 2. **Model storage bridge** (`model.storage`) — how domain fields connect to persistence. Sits on the model to preserve co-location. SQL carries field-to-column mappings because field names and column names can differ; Mongo carries the collection name and, in `model.storage.fields`, the key each field is stored under (`{ "field": "" }`, an entry for every field), because field names and stored names can differ too (`id` → `_id`, `createdAt` → `_created_at`). 3. **Top-level storage** (`storage`) — the database schema itself. SQL: every table, every column with its native type, nullability constraint, default, plus indexes and foreign keys. Mongo: collection metadata (indexes, validators). Consumed by migration tooling, schema introspection, and DDL generation. +**Note:** value objects are not at the domain level. They live in storage, at `storage.namespaces..entries.valueObject`, and a domain field that holds one refers to it by its storage coordinate, because their fields are the keys every row stores ([ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)). + `model.storage` sits on the model (not in a separate section) to preserve co-location. In SQL, field-to-column mappings like `"name": { "column": "display_name" }` need their table context nearby — separating them would leave column references dangling. For a consumer that only cares about the domain, `model.storage` is a clearly scoped block to skip. The separation is logical, not physical. ### Redundancy between levels diff --git a/docs/architecture docs/adrs/ADR 173 - Polymorphism via discriminator and variants.md b/docs/architecture docs/adrs/ADR 173 - Polymorphism via discriminator and variants.md index 459e0f919fb4..405729d1e853 100644 --- a/docs/architecture docs/adrs/ADR 173 - Polymorphism via discriminator and variants.md +++ b/docs/architecture docs/adrs/ADR 173 - Polymorphism via discriminator and variants.md @@ -177,6 +177,8 @@ These are independent properties. This composability is why we rejected labeled ### Value objects use the same mechanism +> **Note:** value objects are now storage entities, at `storage.namespaces..entries.valueObject`, not a domain `valueObjects` section ([ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)). On SQL, a base model's discriminator column also refers to a value set of its variants' values. + The `discriminator`/`variants`/`base` pattern applies identically to value objects in the `valueObjects` section ([ADR 178](ADR%20178%20-%20Value%20objects%20in%20the%20contract.md)). A base value object declares a discriminator and variants; each variant declares its base and adds type-specific fields: ```json diff --git a/docs/architecture docs/adrs/ADR 178 - Value objects in the contract.md b/docs/architecture docs/adrs/ADR 178 - Value objects in the contract.md index cff3b2e9fdd7..58dd4d44c551 100644 --- a/docs/architecture docs/adrs/ADR 178 - Value objects in the contract.md +++ b/docs/architecture docs/adrs/ADR 178 - Value objects in the contract.md @@ -2,6 +2,8 @@ ## At a glance +> **Note:** this ADR describes value objects in the domain plane, where they first lived. They are now storage entities ([ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)); the shapes of their members below are unchanged, and a field now refers to its value object by storage coordinate rather than by name. + A User model with scalar fields and value object fields. Value objects are defined in a separate `valueObjects` section — they share the same field descriptor shape (`{ nullable, codecId }`) as models, but the framework makes fundamentally different promises about them. ```json @@ -103,6 +105,8 @@ This extends the framework commitment hierarchy: ### Value objects are a top-level contract section +> **Superseded:** value objects now live in the storage plane, at `storage.namespaces..entries.valueObject`, and a domain field refers to one by its storage coordinate. See [ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md). + Value objects live in a `valueObjects` section alongside `models`. They use the same field shape (`{ nullable, codecId }`) as model fields. Refer to the [At a glance](#at-a-glance) example for the complete structure. **Why not inside `models`?** The `models` section carries an implicit promise: everything here is a full framework citizen. Mixing in value objects conflates two concepts with different guarantees. Consumers iterating `models` (the ORM building its graph, migration tooling planning DDL, validation checking integrity constraints) would need to filter by kind. @@ -168,6 +172,8 @@ You say `boundingBox.topLeft`, not `boundingBox[0]`. Length constraints on homog ### Value objects need no special storage mapping +> **Superseded:** the value object's definition is now a storage entity, so a change to its fields changes the storage hash. See [ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md). + Value object fields use `storage.fields` like any other field. The storage layer maps domain field names to physical locations without knowing whether the field holds a scalar or a composite: **Mongo:** diff --git a/docs/architecture docs/adrs/ADR 221 - Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds.md b/docs/architecture docs/adrs/ADR 221 - Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds.md index f4d3775f151e..f2e73343eaf2 100644 --- a/docs/architecture docs/adrs/ADR 221 - Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds.md +++ b/docs/architecture docs/adrs/ADR 221 - Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds.md @@ -3,6 +3,8 @@ **Status:** Accepted **Date:** 2026-05-20 +> **Note:** value objects have since moved out of the domain plane. They are storage entities, at `storage.namespaces..entries.valueObject`, and a domain field refers to one by coordinate ([ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)). The domain `valueObjects` key shown below no longer exists. + --- ## A concrete example @@ -143,6 +145,8 @@ The built-in kinds (`tables`, `collections`) stay hardcoded inside their family The mechanism's value is that it satisfies two requirements at once: a kind is contributed at the framework level (so authoring tools — the PSL interpreter, the TS DSL, the emitter — all see it) *without* any target-specific symbol appearing in the framework or in sibling-target packages. A cheaper-looking option — keep a kind hardcoded but move the hardcode from the framework into the pack — fails the second requirement, because the slot name still leaks into the framework's authoring-tool dispatch. +**Kinds the framework defines or reads.** One storage kind is not contributed but defined by the framework, because domain fields refer to it: `valueObject`. The framework reads it by slot key, validates a value object's fields against one schema that both families use, and types it in `contract.d.ts`. Where a value object member names a value set, the framework also reads the `valueSet` slot by key, to check that the reference resolves; the families define the value set entity. Each family still registers a descriptor and an IR class for both kinds. See [ADR 309](ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md). + ## The `Namespace` interface The framework's `Namespace` interface declares only `{ id, kind }`. Family-specific slots live on family-shaped namespace types — `SqlNamespace = Namespace & { tables: …, [...packContributedSlots] }`, `MongoNamespace = Namespace & { collections: … }`. The framework type is honest about what the framework actually knows. diff --git a/docs/architecture docs/adrs/ADR 275 - Client generators are field attributes, and a type alias is expanded into each column.md b/docs/architecture docs/adrs/ADR 275 - Client generators are field attributes, and a type alias is expanded into each column.md index b80bb2d618f6..e35c813b3f59 100644 --- a/docs/architecture docs/adrs/ADR 275 - Client generators are field attributes, and a type alias is expanded into each column.md +++ b/docs/architecture docs/adrs/ADR 275 - Client generators are field attributes, and a type alias is expanded into each column.md @@ -190,7 +190,7 @@ The Prisma 7 reader builds the contract directly and writes no aliases. It reads - **Breaking for schemas.** Every preset call and every `@default(())` must be rewritten. `@updatedAt` keeps being refused in Prisma 8 PSL, with a hint that names `@onCreate(now()) @onUpdate(now())` and `temporal.UpdatedAt`. - **Hashes.** Rewriting a preset or `@default(uuid())` as attributes changes no hash when the column and generators are the same. A contract with a `types {}` block or TypeScript `types` gets a new storage hash, because its columns lose `typeRef` and gain inline params. Supabase's space contract gets a new hash. pgvector's and PostGIS's do not. -- **Upgrade.** The upgrade instructions ship a script that rewrites stored contracts and migration snapshots into the expanded form and refreshes their hashes, as the removal of `@noCheck` did. Users then re-emit and run `db sign` against every database. `migration plan` alone cannot record the new hash, because the change has no operations. The instructions also give the PSL and TypeScript rewrites for presets. +- **Upgrade.** The upgrade instructions ship a script that rewrites stored contracts and migration snapshots into the expanded form and refreshes their hashes, as the removal of `@noCheck` did. Users then re-emit and run `db sign` against every database. The change has no operations; since ADR 309, `migration plan` writes such a migration with no operations, and `db migrate` applies it by moving the marker to the new hash. The instructions also give the PSL and TypeScript rewrites for presets. - **Breaking for extension authors.** A default function can no longer lower to a client generator: `ControlMutationDefaultEntry.lower` returns a database default only. An extension that generated a value through a default function, such as `slugid()`, contributes a generator function with a binding for each codec it fills instead. `printArguments` and `applicableCodecIds` are removed; a binding declares the codecs it fills and how `contract print` writes each generator. - **Diagnostics.** The `PSL_PRESET_*` diagnostics and the refusal of preset-only generators in `@default` are removed. `@default(uuid())` gets a refusal that names `@onCreate(uuid())`. (Amended 2026-10-22.) A preset call is `PSL_UNRESOLVED_REFERENCE` with its rewrite. Mongo's `PSL_PRESET_ON_VARIANT_FIELD` and `PSL_PRESET_CONFLICT`, which also fire for `@onCreate` and `@onUpdate`, are renamed `PSL_GENERATOR_ON_VARIANT_FIELD` and `PSL_GENERATOR_CONFLICT`. - **Less code.** The `fieldPreset` descriptor kind, `instantiateAuthoringFieldPreset`, the `authoring.field` namespace, the `option`, `number` and `object` argument kinds, the `select` template node, the `path` and `default` of an argument reference, the PSL text parsers only those kinds used, the temporal preset builders, `field.generated(...)` with the `@internal/ids` spec helpers, and the preset print search are removed (amended 2026-10-22). Only the `value` argument kind is left. The language server loses its field-preset completion and hover branches. diff --git a/docs/architecture docs/adrs/ADR 278 - Value objects are storage entities, and a discriminator column names a value set.md b/docs/architecture docs/adrs/ADR 278 - Value objects are storage entities, and a discriminator column names a value set.md deleted file mode 100644 index 3c55aba2023a..000000000000 --- a/docs/architecture docs/adrs/ADR 278 - Value objects are storage entities, and a discriminator column names a value set.md +++ /dev/null @@ -1,128 +0,0 @@ -# ADR 278 — Value objects are storage entities, and a discriminator column names a value set - -**Status:** Accepted, implementation pending -**Date:** 2026-10-21 -**Domain:** Contract, Migration System - -The contract gains the storage entity for value objects and the discriminator value set in a later change of this work; the contract today still keeps value objects in the domain plane. - -## At a glance - -A model stores a value object in one column on SQL, and as a subdocument on MongoDB: - -```prisma -type Address { - street String - city String -} - -model User { - id Int @id - address Address? -} -``` - -Every `User` row holds a document such as `{"street": "Main St", "city": "Oslo"}`. Suppose the schema renames `street` to `line1`. The rows still hold `street`, so the application reads `line1` as missing. - -The contract has two planes ([ADR 221](ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)). `domain` describes what the application works with. `storage` describes what is stored, and its hash, `storageHash`, is how the migration system tells one database state from another. When `Address` lives in the domain plane, the rename changes nothing in storage, the hash stays the same, and the migration system sees no change. - -So `Address` lives in the storage plane, and the domain field refers to it there by coordinate: - -```json -{ - "domain": { - "namespaces": { - "public": { - "models": { - "User": { - "fields": { - "address": { - "nullable": true, - "type": { "kind": "valueObject", "entityKind": "valueObject", "entityName": "Address", "namespaceId": "public", "plane": "storage" } - } - } - } - } - } - } - }, - "storage": { - "namespaces": { - "public": { - "entries": { - "valueObject": { - "Address": { - "fields": { - "city": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } }, - "street": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } } - } - } - } - } - } - } - } -} -``` - -Renaming `street` changes the storage entity, so it changes `storageHash`. The planner sees the change the same way it sees a renamed column. - -## Decision - -1. **A value object is a storage entity.** It lives at `storage.namespaces..entries.valueObject.`, in both families, with the same definition it had in the domain plane: `fields`, each with `nullable`, `type` and, for a list, `many`. A domain field that holds one refers to it by entity coordinate, as a field typed by an enum refers to its value set. A value object field that holds another value object refers to it the same way. -2. **A discriminator column names a value set.** The build writes one value set per base model that has variants, holding each variant's discriminator value as the column stores it, and the base table's discriminator column refers to it, as an enum column refers to its value set ([ADR 157](ADR%20157%20-%20Execution%20enums.md)). - -Both are enforced where the database can enforce them, as an enum's value set is: on a managed Postgres table the build derives a check for the discriminator column and for a column that holds a value object, and on MongoDB the collection validator lists the value object's fields and the discriminator's values. - -## Why - -Which plane an entity belongs to depends on what it is used for. The migration planner, the database marker and the saved contract snapshots all work in storage hashes, so a fact the planner must see belongs in storage. A value object's fields are such a fact: they are the keys every row holds in that column, or every subdocument holds in that collection. A discriminator's values are the same kind of fact for its column. The domain plane may refer to the storage plane, never the reverse, so the domain field keeps its reference and storage keeps the definition. - -When these facts sit only in the domain plane, a change to them is invisible to the migration system: - -- `migration plan` reports no changes. -- A migration from a hash to the same hash is rejected unless it carries a hand-written data operation. -- Snapshots are saved by storage hash, and the first one saved for a hash is kept, so a plan's "previous contract" may be several changes old. - -The rows then hold keys and values the contract no longer names, without a word. Refusing a schema change that silently loses data is what the migration system's refusal exists for, and it cannot refuse a change it cannot see. - -Storage already holds stored facts the database does not enforce. SQLite derives no check for an enum, yet its value set is in storage, and changing it changes the hash. The MongoDB collection validator lists the fields of embedded value objects. This decision applies the same rule to value objects on SQL and to discriminator values. - -## How - -**Value objects.** The storage entity keeps the field definition the domain plane used, so every reader of a value object's fields reads the same shape, from the storage plane. Scalar members carry their codec and, when an enum types them, a `valueSet` reference. A member that is a value object refers to its storage entity by coordinate, so a value object that contains itself, such as a `NavItem` whose `children` are `NavItem`s, is one entity that refers to itself. On SQL the column that holds a value object stays a JSON column; on MongoDB the validator is derived from the storage entity, as it is from the definition today. - -**Discriminators.** The value set is keyed `.`, such as `Task.type`. An enum's name cannot contain a dot, so the key never collides with an enum's value set. A variant stored in its own table changes nothing here: its rows are still told apart by the base table's column. The ORM writes a row only through a variant, so every row's value names a variant, and the derived check (`"type" IN ('bug', 'feature')`) rejects only a value no variant names, as an enum's check does. - -```json -"type": { "codecId": "pg/text@1", "dataType": "pg/text", "nullable": false, "valueSet": { "entityKind": "valueSet", "entityName": "Task.type", "namespaceId": "public", "plane": "storage" } } -``` - -**Enforcement on Postgres.** A column that holds a value object gets a derived check, as an enum column does, written with `jsonb` operators. It checks that the required keys are present (`"address" ?& array['street', 'city']`) and that no other key is (`"address" - array['street', 'city'] = '{}'`), with a nullable column allowed to be null. For each scalar member, a value that is not JSON `null` must have the declared JSON type (`jsonb_typeof("address" -> 'street') = 'string'`), and JSON `null` is accepted only for a nullable member. A nested value object and a list are checked through `jsonpath`. The exact predicate for every shape SQL authoring produces is settled when the storage change is built; a shape the check cannot cover is raised as an open question, not worked around. The check is opaque and content-named ([ADR 244](ADR%20244%20-%20Check%20constraints%20are%20opaque%20wire-named%20expressions.md)), so any change to the value object changes the check's name, and the diff drops the old check and adds the new one. - -**Why every change is an operation.** The storage hash identifies a database state. Two different hashes are two different database states, so the migration between them does something to the database. Because the value object and the discriminator values are enforced, a change to either is a change to a check or a validator, which is an operation. A plan that changes the storage hash and produces no operation is therefore unexpected: it means the database cannot tell the two states apart. Such cases will still arise, so the system handles them rather than refusing: `migration plan` writes the package with no operations and says so, naming what changed, and `db migrate` applies it by moving the marker to the new hash. - -**Who reads them.** The migration planner compares them between the origin and destination contracts, to find renamed and removed value object fields and changed discriminator values, and to order the row rewrite between the old check's drop and the new check's creation, as for an enum value. Type emission and the ORM read value objects from the storage plane through the domain field's reference. - -## Consequences - -- Any change to a value object, and any change to a variant's discriminator value, changes `storageHash` and the enforcing check or validator. The planner asks about the ones that lose data and plans their rewrite, as for any other storage change. -- A removed variant that has no columns of its own is visible to the planner through the value set. -- Whether the rows are rewritten, and how efficiently, is a separate decision. This one only makes the change visible. -- `valueObjects` is no longer a domain namespace key. - -## Alternatives considered - -- **Keep value objects in the domain plane, because the database schema does not change.** The tables, columns and checks are the same after a value object field rename, so one could hold that the storage hash should not change and the migration system should ignore it. Then the system can never detect the change, even to refuse it, and the old keys stay in every row without a word. -- **Keep them in the domain plane and record such a change as a migration from a hash to itself.** A migration with a hand-written data operation can already do this. But snapshots are saved by storage hash, so a plan's "previous contract" can be several changes old. Fixing that means a different snapshot key, a larger change than this one, and `db update`, which runs no data operations, still could not apply it. -- **Copy the definition into the storage plane and keep the domain copy.** The hash would change, but two copies of one definition invite drift, and the domain plane can already refer to storage. -- **Feed the domain value objects into the storage hash without moving them.** The hash would change, but the planner compares storage, so it still could not tell what changed, and every reader of the hash would have to know that part of the domain plane feeds it. -- **Record the facts in storage without enforcing them.** The hash would change while the database stayed the same, so a change would need a migration with no operations. That contradicts what the hash means, and it leaves rows the contract no longer describes. - -## Related - -- [ADR 221 — Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds](ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md): the planes, coordinates and the direction of references -- [ADR 172 — Contract domain-storage separation](ADR%20172%20-%20Contract%20domain-storage%20separation.md) -- [ADR 178 — Value objects in the contract](ADR%20178%20-%20Value%20objects%20in%20the%20contract.md): this decision replaces its sections "Value objects are a top-level contract section" and "Value objects need no special storage mapping" -- [ADR 173 — Polymorphism via discriminator and variants](ADR%20173%20-%20Polymorphism%20via%20discriminator%20and%20variants.md) -- [ADR 157 — Execution enums](ADR%20157%20-%20Execution%20enums.md): value sets diff --git a/docs/architecture docs/adrs/ADR 309 - Value objects are storage entities, and a discriminator column names a value set.md b/docs/architecture docs/adrs/ADR 309 - Value objects are storage entities, and a discriminator column names a value set.md new file mode 100644 index 000000000000..c5fdcd2d5372 --- /dev/null +++ b/docs/architecture docs/adrs/ADR 309 - Value objects are storage entities, and a discriminator column names a value set.md @@ -0,0 +1,146 @@ +# ADR 309 — Value objects are storage entities, and a discriminator column names a value set + +**Status:** Accepted +**Date:** 2026-10-21 +**Domain:** Contract, Migration System + +## At a glance + +A model stores a value object in one column on SQL, and as a subdocument on MongoDB: + +```prisma +type Address { + street String + city String +} + +model User { + id Int @id + address Address? +} +``` + +Every `User` row holds a document such as `{"street": "Main St", "city": "Oslo"}`. Suppose the schema renames `street` to `line1`. The rows still hold `street`, so the application reads `line1` as missing. + +The contract has two planes ([ADR 221](ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)). `domain` describes what the application works with. `storage` describes what is stored, and its hash, `storageHash`, is how the migration system tells one database state from another. When `Address` lives in the domain plane, the rename changes nothing in storage, the hash stays the same, and the migration system sees no change. + +So `Address` lives in the storage plane, and the domain field refers to it there by coordinate: + +```json +{ + "domain": { + "namespaces": { + "public": { + "models": { + "User": { + "fields": { + "address": { + "nullable": true, + "type": { "kind": "valueObject", "entityKind": "valueObject", "entityName": "Address", "namespaceId": "public", "plane": "storage" } + } + } + } + } + } + } + }, + "storage": { + "namespaces": { + "public": { + "entries": { + "valueObject": { + "Address": { + "fields": { + "city": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } }, + "street": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } } + } + } + } + } + } + } + } +} +``` + +Renaming `street` changes the storage entity, so it changes `storageHash`. The planner sees the change the same way it sees a renamed column. + +## Decision + +1. **A value object is a storage entity.** It lives at `storage.namespaces..entries.valueObject.`, in both families, with the same definition it had in the domain plane: `fields`, each with `nullable`, `type` and, for a list, `many`. A domain field that holds one refers to it by entity coordinate, as a field typed by an enum refers to its value set. A value object field that holds another value object refers to it the same way. +2. **A SQL discriminator column names a value set.** The SQL build writes one value set per base model that has variants, holding each variant's discriminator value as the column stores it, and the base table's discriminator column refers to it, as an enum column refers to its value set ([ADR 157](ADR%20157%20-%20Execution%20enums.md)). MongoDB writes none: its collection validator already pins each variant's discriminator value, so a change to one already changes the storage hash. + +Both are enforced where the database can enforce them, as an enum's value set is: on a managed Postgres table the build derives a check for the discriminator column and for a column that holds a value object, and on MongoDB the collection validator lists the value object's fields and the discriminator's values. + +## Why + +Which plane an entity belongs to depends on what it is used for. The migration planner, the database marker and the saved contract snapshots all work in storage hashes, so a fact the planner must see belongs in storage. A value object's fields are such a fact: they are the keys every row holds in that column, or every subdocument holds in that collection. A discriminator's values are the same kind of fact for its column. The domain plane may refer to the storage plane, never the reverse, so the domain field keeps its reference and storage keeps the definition. + +When these facts sit only in the domain plane, a change to them is invisible to the migration system: + +- `migration plan` reports no changes. +- A migration from a hash to the same hash is rejected unless it carries a hand-written data operation. +- Snapshots are saved by storage hash, and the first one saved for a hash is kept, so a plan's "previous contract" may be several changes old. + +The rows then hold keys and values the contract no longer names, without a word. Refusing a schema change that silently loses data is what the migration system's refusal exists for, and it cannot refuse a change it cannot see. + +Storage already holds stored facts the database does not enforce. SQLite derives no check for an enum, yet its value set is in storage, and changing it changes the hash. The MongoDB collection validator lists the fields of embedded value objects. This decision applies the same rule to value objects on SQL and to discriminator values. + +## How + +**Value objects.** The storage entity keeps the field definition the domain plane used, so every reader of a value object's fields reads the same shape, from the storage plane. Its fields have no stored names of their own: each is stored under its own name, in both families. Scalar members carry their codec and, when an enum types them, a `valueSet` reference to the enum's storage value set, since storage never refers to the domain plane. A member that is a value object refers to its storage entity by coordinate, so a value object that contains itself, such as a `NavItem` whose `children` are `NavItem`s, is one entity that refers to itself. On SQL the column that holds a value object stays a JSON column; on MongoDB the validator is derived from the storage entity, as it is from the definition today. + +**Why one definition, not a domain definition and a storage entity.** Models and tables are two definitions: a domain model, a storage table, and the model's `storage` that maps one to the other, so a field can have a stored name different from its own. A value object could follow that pattern, with a domain value object, a storage entity and a mapping between them. It does not, because nothing needs the mapping today: a value object's fields are stored under their own names in both families, so a second definition would hold the same fields and need a check that the two agree. The cost is that a value object field cannot have a stored name of its own without a later change to this shape. Adding one then means a stored-name slot, or a domain definition beside the storage entity, and the hash rules for each. + +**Where the principle stops.** "A fact the planner must see belongs in storage" is applied here to value objects and discriminator values. It is not applied to the names of enum members, which decide which member a stored value reads as; they stay in the domain plane. Whether they should move is open in [TML-3720](https://linear.app/prisma-company/issue/TML-3720). + +**The framework defines the entity kind.** Families and packs contribute storage entity kinds ([ADR 221](ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)), but the framework defines `valueObject`, because a domain field refers to it and the framework resolves that reference, validates it and types it. Where a value object member names a value set, the framework reads the `valueSet` slot by key, to check that the reference resolves; each family defines the value set entity itself. Both families validate a value object against one schema from the framework: each field has `nullable`, always written; `type`; `many` when it is a list; `dict` only as `true`; and an optional `valueSet` reference. Each family still registers its own descriptor and IR class for the kind. + +**Discriminators.** The value set is keyed `.`, such as `Task.type`, and holds the values sorted, so the order variants are declared in does not change the hash. A discriminator an enum types keeps the enum's value set and check: the column has one value set reference, and the enum already restricts it. Enum value sets are keyed by the enum's name, so only an enum named exactly like a discriminator's key, in the same storage namespace, clashes with it. No authoring surface writes such a name, since PSL names are identifiers and the SQL TypeScript builder has no polymorphism; the build refuses it all the same, naming both and saying to rename the enum. Enum names are not otherwise restricted. A variant stored in its own table changes nothing here: its rows are still told apart by the base table's column. The ORM writes a row only through a variant, so every row's value names a variant, and the derived check (`"type" IN ('bug', 'feature')`) rejects only a value no variant names, as an enum's check does. + +```json +"type": { "codecId": "pg/text@1", "dataType": "pg/text", "nullable": false, "valueSet": { "entityKind": "valueSet", "entityName": "Task.type", "namespaceId": "public", "plane": "storage" } } +``` + +**Enforcement on Postgres.** On a managed table, a column that holds a value object gets a derived check, as an enum column does, named `__shape_`, where `` is the first eight hexadecimal digits of the hash of the check's expression, such as `user_address_shape_5c02601b`. Its check kind is `shape`: like `membership` and `elementNotNull`, the kind names what the check enforces, here the shape of the column's value. It is one strict `jsonpath` predicate, written so that an unknown result fails, rather than the key-set operators `?&` and `-`: + +```sql +jsonb_path_match("address", 'strict $.type() == "object" + && exists($.keyvalue() ? (@.key == "street" && (@.value.type() == "string"))) + && !exists($.keyvalue() ? (@.key != "street"))') IS TRUE +``` + +The value must be an object, or for a list an array of objects. Each required member must be present; a nullable member may be absent or JSON `null`; no other key may be. A scalar member must have the JSON type its codec writes, an enum member must hold a value of its value set, and a nested value object is checked the same way, inline. A nullable column also accepts SQL `NULL` and JSON `null`. Members are read through `keyvalue()`, so an absent member is never an error, and strict mode keeps a comparison from unwrapping an array. A value object that contains itself is checked in full down to where it refers back into itself, and any object is accepted below, as the MongoDB validator does. Each PostgreSQL codec declares, on its descriptor, the JSON type its value is written as inside a JSON document: a string, a number, a boolean, an object, an array, or any JSON value, for a JSON codec. The SQL family reads it when it resolves the value object, and the check requires each member to be of that type, so a required member is never JSON `null` unless its codec's type is any JSON value. A `Decimal` member must be a JSON string, as the SQL ORM writes it. The check is opaque and content-named ([ADR 244](ADR%20244%20-%20Check%20constraints%20are%20opaque%20wire-named%20expressions.md)), so any change to the value object changes the check's name, and the diff drops the old check and adds the new one. + +**Why a change is normally an operation.** The storage hash identifies a database state. Two different hashes are two different database states, so the migration between them is expected to do something to the database. On a target that derives enforcement, a change to a value object or to the discriminator values is a change to a check or a validator, which is an operation. This holds on Postgres, for its derived checks, and on MongoDB, for its collection validators. It does not hold in every case, and the known cases with no operations are: + +- every value object change and every discriminator value change on a target with no check renderer, which today are SQLite and MySQL; +- on Postgres, a change to a member's codec or type parameters that keeps the JSON type the check reads, such as `pg/text@1` to `pg/varchar@1`: the check expression, and so its name, stay the same. + +The system handles these rather than refusing them: `migration plan` writes the package with no operations and reports it, naming the storage entities that changed, and `db migrate` applies it by moving the marker to the new hash. + +**Who reads them.** The migration planner compares them between the origin and destination contracts, to find renamed and removed value object fields and changed discriminator values, and to order the row rewrite between the old check's drop and the new check's creation, as for an enum value. Type emission and the ORM read value objects from the storage plane through the domain field's reference. + +## Consequences + +- Any change to a value object, and any change to a variant's discriminator value, changes `storageHash`. On a target that derives enforcement it also changes the enforcing check or validator, except in the cases listed under "Why a change is normally an operation", where the migration has no operations. The planner asks about the ones that lose data and plans their rewrite, as for any other storage change. +- A removed variant that has no columns of its own is visible to the planner through the value set. +- Whether the rows are rewritten, and how efficiently, is a separate decision. This one only makes the change visible. +- `valueObjects` is no longer a domain namespace key. A contract that keeps it there is refused, with a message saying to re-emit it. +- Moving the value objects changes the storage hash of every contract that has them, including a project's committed migration snapshots. The upgrade rewrites the snapshots, renames their directories and updates the migrations that name them; the databases are then signed with the new hashes. On Postgres, the next migration adds the derived checks, and a row that does not fit, such as one whose value object has a key it does not declare, fails it. + +## Alternatives considered + +- **Keep value objects in the domain plane, because the database schema does not change.** The tables, columns and checks are the same after a value object field rename, so one could hold that the storage hash should not change and the migration system should ignore it. Then the system can never detect the change, even to refuse it, and the old keys stay in every row without a word. +- **Keep them in the domain plane and record such a change as a migration from a hash to itself.** A migration with a hand-written data operation can already do this. But snapshots are saved by storage hash, so a plan's "previous contract" can be several changes old. Fixing that means a different snapshot key, a larger change than this one, and `db update`, which runs no data operations, still could not apply it. +- **Copy the definition into the storage plane and keep the domain copy.** The hash would change, but while value object fields have no stored names the two copies would hold the same fields, and they could drift apart. The domain plane can already refer to storage. See "Why one definition" above for when a second definition would be worth its cost. +- **Feed the domain value objects into the storage hash without moving them.** The hash would change, but the planner compares storage, so it still could not tell what changed, and every reader of the hash would have to know that part of the domain plane feeds it. +- **Record the facts in storage without enforcing them.** The hash would change, but no target would enforce the new facts, not even one that can. What this gives up is enforcement on Postgres and MongoDB: rows the contract no longer describes would stay without a word. Migrations with no operations are not the reason; this decision accepts them on targets that cannot enforce. + +## Related + +- [ADR 221 — Contract IR two planes with uniform entity coordinate and pack-contributed entity kinds](ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md): the planes, coordinates and the direction of references +- [ADR 172 — Contract domain-storage separation](ADR%20172%20-%20Contract%20domain-storage%20separation.md) +- [ADR 178 — Value objects in the contract](ADR%20178%20-%20Value%20objects%20in%20the%20contract.md): this decision replaces its sections "Value objects are a top-level contract section" and "Value objects need no special storage mapping" +- [ADR 173 — Polymorphism via discriminator and variants](ADR%20173%20-%20Polymorphism%20via%20discriminator%20and%20variants.md) +- [ADR 157 — Execution enums](ADR%20157%20-%20Execution%20enums.md): value sets diff --git a/docs/architecture docs/subsystems/1. Data Contract.md b/docs/architecture docs/subsystems/1. Data Contract.md index 99ab92739635..d33c2ed6a96f 100644 --- a/docs/architecture docs/subsystems/1. Data Contract.md +++ b/docs/architecture docs/subsystems/1. Data Contract.md @@ -139,8 +139,7 @@ At a high level. The contract encodes what exists (models, storage), how it can "fields": { "id": { "column": "id" }, "userId": { "column": "user_id" } } } } - }, - "valueObjects": {} + } } } }, @@ -184,8 +183,8 @@ At a high level. The contract encodes what exists (models, storage), how it can **Notes:** - **roots** map ORM accessor names to a model coordinate — an object pair `{ namespace, model }` (e.g., `"users": { "namespace": "public", "model": "User" }`) -- **domain** holds the application concepts the user defines, under `domain.namespaces..{ models, valueObjects }`. A model carries fields with `{ nullable, codecId }`, relations whose `to` is an object pair `{ namespace, model }`, and a `storage` bridge to the physical layer. The framework domain plane has **no** `types` member — codec aliases live on the SQL storage plane. -- **storage** holds the family-owned persistence projection. Each namespace carries an open `entries` dictionary keyed by entity kind — `storage.namespaces..entries[entityKind][entityName]` — plus plane-level metadata (`storageHash`). Built-in entity kinds are `table` (SQL family), `collection` (Mongo family) and `valueSet` (both families); pack-contributed kinds (e.g. `native_enum` for Postgres enum types) register via the serializer registry. Cross-namespace foreign-key references are object pairs `{ namespaceId, tableName, columns }`. +- **domain** holds the application concepts the user defines, under `domain.namespaces..models`. A model carries fields with `{ nullable, codecId }`, relations whose `to` is an object pair `{ namespace, model }`, and a `storage` bridge to the physical layer. The framework domain plane has **no** `types` member — codec aliases live on the SQL storage plane. +- **storage** holds the family-owned persistence projection. Each namespace carries an open `entries` dictionary keyed by entity kind — `storage.namespaces..entries[entityKind][entityName]` — plus plane-level metadata (`storageHash`). Built-in entity kinds are `table` (SQL family), `collection` (Mongo family), and `valueSet` and `valueObject` (both families); pack-contributed kinds (e.g. `native_enum` for Postgres enum types) register via the serializer registry. Cross-namespace foreign-key references are object pairs `{ namespaceId, tableName, columns }`. - **capabilities** surface target features that affect compilation/lowering (e.g., lateral) - **Types (.d.ts)** expose branded TS types for contract type identifiers; at runtime, codecs provided by packs/adapters handle encode/decode ([ADR 114 — Extension codecs & branded types](../adrs/ADR%20114%20-%20Extension%20codecs%20&%20branded%20types.md)) - Authoring provenance (paths/source IDs/spans) is **diagnostics-only**; it is not embedded in canonical artifacts @@ -231,7 +230,8 @@ The contract's domain-level structure generalizes across database families. The - **`model.fields`** — domain-level field descriptors as `Record`. Both `nullable` and `codecId` are domain concepts. "Family-agnostic" describes the structure, not the values — codec ID values differ across families but the structure is identical. See [ADR 172](../adrs/ADR%20172%20-%20Contract%20domain-storage%20separation.md). - **Polymorphism** — `discriminator` + `variants` on the base model, `base` on each variant. Persistence strategy (STI vs MTI) is emergent from storage mappings. Uses specialization/generalization terminology. See [ADR 173](../adrs/ADR%20173%20-%20Polymorphism%20via%20discriminator%20and%20variants.md). - **Model ownership** — an owned model declares `"owner": "ParentModel"`, a domain fact about aggregate membership. Its data is co-located with the owner's storage (embedded document in Mongo, JSONB column in SQL). The parent's `storage.relations` maps the relation to its physical location. Relations to owned models are plain graph edges. See [ADR 177](../adrs/ADR%20177%20-%20Ownership%20replaces%20relation%20strategy.md). -- **Value objects** — typed data structures without identity, lifecycle, or framework capabilities (no query entry points, no hooks, no referential integrity). Defined under `domain.namespaces..valueObjects` alongside `models`, using the same `{ nullable, codecId }` field shape. Fields reference value objects via `type` (mutually exclusive with `codecId`); unions of types use `union`. Value object support in SQL is capability-gated (requires JSONB-compatible columns). See [ADR 178](../adrs/ADR%20178%20-%20Value%20objects%20in%20the%20contract.md), [ADR 179](../adrs/ADR%20179%20-%20Union%20field%20types.md). +- **Value objects** — typed data structures without identity, lifecycle, or framework capabilities (no query entry points, no hooks, no referential integrity). They are storage entities, at `storage.namespaces..entries.valueObject.` in both families, because their fields are the keys every row or subdocument stores, so a change to them changes the storage hash. A value object's `fields` use the field shape models use; a member typed by an enum refers to the enum's storage value set. A domain field that holds one has the type `{ kind: 'valueObject', plane: 'storage', namespaceId, entityKind: 'valueObject', entityName }`, the value object's storage coordinate, and a member that holds another value object refers to it the same way. Readers resolve the coordinate with `valueObjectAt` from `@internal/contract/types`. A contract that keeps `valueObjects` in a domain namespace, or refers to a value object by `name`, is refused with a message saying to re-emit it. Value object support in SQL is capability-gated (requires JSONB-compatible columns). See [ADR 309](../adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md), [ADR 178](../adrs/ADR%20178%20-%20Value%20objects%20in%20the%20contract.md), [ADR 179](../adrs/ADR%20179%20-%20Union%20field%20types.md). +- **Discriminator value sets (SQL)** — the SQL build writes one value set per base model with variants, keyed `.` in the base table's namespace and holding the variants' values, sorted; the discriminator column refers to it. A discriminator column an enum types keeps the enum's value set. An enum with exactly that name in the same namespace is refused. MongoDB writes no such value set: its collection validator already pins each variant's value. See [ADR 309](../adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md). ### Core entities vs pack extensions @@ -322,6 +322,10 @@ A `pg/int4@1` enum `Int4Level` with members `Low = 1` and `High = 10`, on column A list column gets `<@` containment instead of `IN`. In the same fixture, `levels FloatMixed[]`, whose members are written `1.5` and `NaN` and stored as `1.5` and `"NaN"`, gets `array_remove("levels", NULL) <@ '{1.5,"NaN"}'`, plus the derived check `array_position("levels", NULL) IS NULL` because its elements are not nullable. +On a `managed` Postgres table two more columns derive checks ([ADR 309](../adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)). A discriminator column with no enum gets a membership check, `
__check`, holding the values of its `.` value set. A column that holds a value object gets a check named `
__shape`: one strict `jsonpath` predicate that the column value is an object (or, for a list, an array of objects) with each required member present, no other key, each scalar member of its JSON type, each enum member one of its value set's values, and each nested value object checked the same way. A nullable column accepts SQL `NULL` and JSON `null`; the ORM writes SQL `NULL` for an absent value. Each Postgres codec declares on its descriptor the JSON type its value is written as (`jsonType`): a `Decimal` member must be a JSON string and a `Bytes` member a JSON object, as the ORM writes them. A member is of its codec's JSON type, so a required member is never JSON `null`, except a `Json` member, whose type is `any` and which may hold any value. A contract build refuses a member whose codec declares no JSON type. Below the point where a value object refers back into itself, any object is accepted, as the MongoDB validator does. Adding a check takes a lock on the table while Postgres reads every row to check it. Any later change to a value object renames the check, so the check is dropped and added again, and the rows are read again. + +`contract infer` produces no value objects and no polymorphism, so on a live table with these checks it prints each one as an authored `@@check` with its exact name in `map:`, as it does for an enum membership check. + ### Storage identity and verification semantics (v1) The control plane verifies that a live database **satisfies** the contract’s storage requirements. For MVP, verification favors **semantic satisfaction** over strict, byte-for-byte equivalence. diff --git a/docs/architecture docs/subsystems/10. MongoDB Family.md b/docs/architecture docs/subsystems/10. MongoDB Family.md index 0850d7d5961c..96c69e8a04fa 100644 --- a/docs/architecture docs/subsystems/10. MongoDB Family.md +++ b/docs/architecture docs/subsystems/10. MongoDB Family.md @@ -86,7 +86,7 @@ A few things to notice before reading further: - **`roots`** declares which models are ORM entry points — `db.users` and `db.posts`. Comment is not in `roots` because it's only reachable through Post. - **`owner: "Post"`** on the Comment model means Comment belongs to Post's aggregate. Its data lives inside Post documents. The relation from Post to Comment (`{ "to": "Comment", "cardinality": "1:N" }`) is a plain graph edge — no storage annotation. The physical location is in Post's `storage.relations`: `"comments": { "field": "comments" }`. -- **Comment has no `collection`**, because it doesn't own one. Its data lives within Post's storage. Note that Comment is still an entity with its own id — it has identity even when embedded. Value objects (data without identity, like an address or a money amount) are a separate concept with their own contract section — see [ADR 178](../adrs/ADR%20178%20-%20Value%20objects%20in%20the%20contract.md). +- **Comment has no `collection`**, because it doesn't own one. Its data lives within Post's storage. Note that Comment is still an entity with its own id — it has identity even when embedded. Value objects (data without identity, like an address or a money amount) are a separate concept: storage entities at `storage.namespaces..entries.valueObject`, which a field refers to by storage coordinate — see [ADR 178](../adrs/ADR%20178%20-%20Value%20objects%20in%20the%20contract.md) and [ADR 309](../adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md). - **Field structure is identical to SQL.** Each field is `{ nullable, codecId }`. A Mongo field says `"mongo/string@1"` where SQL would say `"pg/text@1"`, but the shape is the same. - **Fields keep their own names; storage records where each is stored.** As SQL records each field's column in `model.storage.fields`, a Mongo model records each field's stored name there: `{ "": { "field": "" } }`, an entry for every field. `id ObjectId @id @map("_id")` in PSL, or `field.objectId().mongo({ field: '_id' })` in the TypeScript builder, gives the field `id` stored as `_id`. A field without `@map` is stored under its own name. Relations and the discriminator name fields; indexes, validators and execution defaults name stored names. An embed relation is stored under its `storage.relations` entry, or its own name when it has none. A value object has no storage block: its fields are stored under their own names, and PSL and the TypeScript builder refuse a stored name on them. `validateMongoStorage` checks that every field has an entry and that no two fields or embed relations of a model, with its base for a variant, share a stored name. The helpers in `@internal/mongo-contract` (`ownStoredNamesOf`, `ownStoredNameOf`, `ownFieldStoredAs`, `ownEmbedStoredNamesOf`, `ownDocumentIdFieldOf`, `storedNamesOf` with inherited fields, `modelAndBasesOf`) and the types `OwnStoredNameOf` and `OwnFieldStoredAs` translate between the two, and every reader goes through them: the validator derivation, the planner, the ORM and the pipeline builder. @@ -126,7 +126,7 @@ A referenced relation (to a model with its own collection) carries `on` join det This also introduces the concept of **aggregate roots**: models that own their own collection and can be queried directly. In SQL, every model has its own table, so this distinction is invisible. In MongoDB, some models are owned by a parent and can only be reached through it. The contract makes this explicit with a top-level `roots` section that maps ORM accessor names (like `db.users`) to model names (like `User`). If a model isn't in `roots`, it's not an independent entry point. See [ADR 174](../adrs/ADR%20174%20-%20Aggregate%20roots%20and%20relation%20strategies.md). -Each model has exactly one canonical storage location — either its own collection (aggregate root) or inside a parent's document (owned). A model can't be both. When developers need data from one aggregate root available inside another for read performance (the MongoDB "extended reference" pattern), the embedded copy is a denormalized snapshot — a value object — not the model itself. The canonical Post lives in the `posts` collection; a `{ title, slug }` summary embedded in a User document is a different thing, even though it shares some fields. Value objects are defined in the contract's `valueObjects` section — see [ADR 178](../adrs/ADR%20178%20-%20Value%20objects%20in%20the%20contract.md). See also [design principle #5](#design-principles). +Each model has exactly one canonical storage location — either its own collection (aggregate root) or inside a parent's document (owned). A model can't be both. When developers need data from one aggregate root available inside another for read performance (the MongoDB "extended reference" pattern), the embedded copy is a denormalized snapshot — a value object — not the model itself. The canonical Post lives in the `posts` collection; a `{ title, slug }` summary embedded in a User document is a different thing, even though it shares some fields. Value objects are storage entities, at `storage.namespaces..entries.valueObject`, and the collection validator is derived from them — see [ADR 178](../adrs/ADR%20178%20-%20Value%20objects%20in%20the%20contract.md) and [ADR 309](../adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md). A change to a value object's fields changes the storage hash and the validator. MongoDB writes no discriminator value set: the validator already pins each variant's discriminator value. See also [design principle #5](#design-principles). In the ORM, owned relations auto-project into the parent row — no separate query is issued. Referenced relations use `$lookup` pipeline stages or multi-query stitching, similar to how SQL handles joins. diff --git a/docs/architecture docs/subsystems/2. Contract Emitter & Types.md b/docs/architecture docs/subsystems/2. Contract Emitter & Types.md index 211173eaf814..b68dd9dabd34 100644 --- a/docs/architecture docs/subsystems/2. Contract Emitter & Types.md +++ b/docs/architecture docs/subsystems/2. Contract Emitter & Types.md @@ -237,8 +237,8 @@ export type QueryOperationTypes = PgTargetQueryOps; export type AggregateTypes = { /* … per-aggregate codec and trait maps … */ }; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; type FieldTypeOverrides = { readonly public: { readonly models: { readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' } } } } }; -export type FieldOutputTypes = FieldTypesFromDomain; -export type FieldInputTypes = FieldTypesFromDomain; +export type FieldOutputTypes = FieldTypesFromDomain; +export type FieldInputTypes = FieldTypesFromDomain; type ColumnTypeOverrides = { /* … the same, keyed by namespace, table and column … */ }; export type StorageColumnTypes = ColumnTypesFromStorage; export type StorageColumnInputTypes = ColumnTypesFromStorage; @@ -259,7 +259,7 @@ type ContractBase = Omit< readonly target: 'postgres'; readonly targetFamily: 'sql'; readonly roots: { readonly users: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' } }; - readonly domain: { readonly namespaces: { readonly public: { readonly models: { readonly User: { /* … fields / relations / storage bridge … */ } }; readonly valueObjects: {} } } }; + readonly domain: { readonly namespaces: { readonly public: { readonly models: { readonly User: { /* … fields / relations / storage bridge … */ } } } } }; readonly capabilities: { /* … */ }; readonly profileHash: ProfileHash; }; @@ -269,7 +269,7 @@ export type Contract = ContractWithTypeMaps; export type Namespaces = Contract['storage']['namespaces']; ``` -The emitter writes the field type maps of every family, and the SQL family its storage column type maps, as applications of `FieldTypesFromDomain` (`@internal/contract/types`) and `ColumnTypesFromStorage` (`@internal/sql-contract/types`) to the emitted contract's own shape. A contract written with `defineContract`, in either family, applies the same generics to its built shape, so the two paths share one set of rules: an override first, then, on the output side, the pack's type for a field with type parameters, then the codec's output or input type, an object of a value object's own field types, or the union of a union field's members, then list and dictionary wrapping and nullability. Each value object's `Output` and `Input` alias is an entry of `ValueObjectTypesFromDomain`. The emitter writes only what a type cannot compute: `FieldTypeOverrides` and `ColumnTypeOverrides` hold the literal union of a value set (each value rendered by its codec's `renderValueLiteral`; a codec that renders no literal, such as one that sets `keepsWrittenForm`, leaves the codec's type) and text a codec renders with `renderOutputType`. Each enum member's type comes from the same `renderValueLiteral`, or is the codec's output type when the codec renders no literal. `Models` members index `FieldOutputTypes`. +The emitter writes the field type maps of every family, and the SQL family its storage column type maps, as applications of `FieldTypesFromDomain` (`@internal/contract/types`) and `ColumnTypesFromStorage` (`@internal/sql-contract/types`) to the emitted contract's own shape. A contract written with `defineContract`, in either family, applies the same generics to its built shape, so the two paths share one set of rules: an override first, then, on the output side, the pack's type for a field with type parameters, then the codec's output or input type, an object of a value object's own field types, or the union of a union field's members, then list and dictionary wrapping and nullability. Value objects are storage entities ([ADR 309](../adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)): `contract.d.ts` writes them in the storage namespaces type, under `entries.valueObject`, and types a field that holds one by its storage coordinate. Each value object's `Output` and `Input` alias is an entry of `ValueObjectTypesFromStorage`, applied to `ContractBase['storage']['namespaces']`. `FieldTypesFromDomain` reads both planes: it takes the storage namespaces as a required sixth type parameter, where it finds the value object a field's coordinate names, and the emitter always passes it. A SQL discriminator field is typed by the literal union of its value set, through `FieldTypeOverrides`. The emitter writes only what a type cannot compute: `FieldTypeOverrides` and `ColumnTypeOverrides` hold the literal union of a value set (each value rendered by its codec's `renderValueLiteral`; a codec that renders no literal, such as one that sets `keepsWrittenForm`, leaves the codec's type) and text a codec renders with `renderOutputType`. Each enum member's type comes from the same `renderValueLiteral`, or is the codec's output type when the codec renders no literal. `Models` members index `FieldOutputTypes`. Both planes are namespaced: storage entities live under `storage.namespaces..entries[entityKind][entityName]` (e.g. `entries['table']['user']`) and models under `domain.namespaces..models` (see [Data Contract § Two planes, namespaced](1.%20Data%20Contract.md#two-planes-namespaced) and [ADR 221](../adrs/ADR%20221%20-%20Contract%20IR%20two%20planes%20with%20uniform%20entity%20coordinate%20and%20pack-contributed%20entity%20kinds.md)). Consumers read a namespace's models directly — `Contract['domain']['namespaces']['public']['models']` (or `[keyof …['namespaces']]` for the sole-namespace case). See [ADR 223 — Target-owned default namespace](../adrs/ADR%20223%20-%20Target-owned%20default%20namespace.md) for the runtime/emitter fail-loud split. diff --git a/docs/architecture docs/subsystems/7. Migration System.md b/docs/architecture docs/subsystems/7. Migration System.md index 337486f501d8..a3a73dd96e75 100644 --- a/docs/architecture docs/subsystems/7. Migration System.md +++ b/docs/architecture docs/subsystems/7. Migration System.md @@ -172,6 +172,8 @@ The from-contract always materialises by reading the content-addressed snapshot When `--to` is omitted, `to_contract` is the emitted contract; when `--to` is supplied, `to_contract` is the resolved destination. +**A plan with no operations.** Two different storage hashes are two different database states, so a migration between them is expected to do something. When the planner produces no operations for a changed storage hash anyway, `migration plan` still writes the package, with an empty `ops.json`, and says so: the summary, a warning in the human output, names each storage entity whose content differs between the two contracts, and `--json` lists them as `changedStorageEntities` (entity coordinates, from `changedElementCoordinates` in `@internal/framework-components/ir`; a coordinate in the unbound namespace leaves out `namespaceId`, as `appliedStatements` does). The field is filled for every migration `migration plan` writes, and a migration written with no operations is one whose `operations` is empty. `noOp` means something else: nothing was written, because the storage hash did not change. `db migrate` and `db update` apply such a migration by moving the marker to the new hash; every runner already does this for an edge between two different hashes. This holds for a baseline with no operations and for the delta after it. Only a greenfield plan (no earlier contract) with no operations is refused, with `MIGRATION.PLANNING_FAILED` of kind `nothingToCreate`, since the contract describes nothing to create. When the storage hash does not change, nothing is written, as before. + An auto-baseline dates the delta package at the current time and the baseline package one minute earlier, so the baseline sorts first. Folder names have minute resolution, so packages planned within the same minute sort by name. **Data loss.** Before it writes either package, including the baseline of an auto-baseline, `migration plan` asks what each operation that would lose data means (see [The questions](#statements)). It does not ask about access: a written package is reviewed before it is applied. diff --git a/docs/reference/codec-authoring-guide.md b/docs/reference/codec-authoring-guide.md index f32eac551a67..587e695ef9ce 100644 --- a/docs/reference/codec-authoring-guide.md +++ b/docs/reference/codec-authoring-guide.md @@ -96,6 +96,7 @@ export class PgTextDescriptor extends PostgresCodecDescriptor { return expression; } override readonly dataType = pgText.id; + override readonly jsonType = 'string' as const; override readonly codecId = 'pg/text@1' as const; override readonly traits = ['equality', 'order', 'textual'] as const; override readonly paramsSchema = undefined; @@ -148,6 +149,7 @@ export class PgVectorDescriptor extends PostgresCodecDescriptor { return CastExpr.as(expression, 'text'); } override readonly dataType = pgvectorVector.id; + override readonly jsonType = 'array' as const; override readonly codecId = 'pg/vector@1' as const; override readonly traits = ['equality'] as const; override readonly paramsSchema: StandardSchemaV1 = pgvectorVectorParams; @@ -234,6 +236,7 @@ export class ArktypeJsonDescriptor extends PostgresCodecDescriptor" field "" uses codec "", which no pack in the contract provides, so its BSON type is unknown and the contract cannot describe the field.` (`Value object ""` for a value object field). Payload (meta): `modelName` or `valueObjectName`, `fieldName`, `codecId`. +### CONTRACT.CODEC_JSON_TYPE_MISSING + +A contract build derives the check of a value object column on Postgres, and a member of the value object uses a codec whose descriptor declares no `jsonType`, as a codec package compiled before codecs declared one does. The codec's registry accepts it, so an app that does not put the codec in a value object keeps working. The codec's author adds `jsonType`, the JSON type the codec's value is written as inside a JSON document. Payload: `codecId`, `valueObject` (`:`), `member`, `property` (`jsonType`). + ### CONTRACT.CAST_REFUSED A value handed to a data type's cast, or to an authoring entry that reads written text, is not one that type takes: it is not in the shape the source type stores, its magnitude is outside the range the receiving type holds, the text is not a boolean, or the text is not a date or time the receiving type holds. A date or time text is refused when it has an offset its type does not hold or lacks one its type needs, has more fraction digits than its type holds (six, or three for `sqlite/datetime@1`, which holds milliseconds), names a date or time that does not exist, or is outside the range of years its type holds; the message shows text the type takes. The date and time codecs raise it from `toDataTypeValue` for a value their type does not hold, such as a `Temporal` value with digits below one microsecond. Raised by a target's or extension's casts and authoring entries. A contract source reading a written value reports it to the author as the PSL diagnostic `PSL_INVALID_LITERAL` when a cast, an authoring entry or a codec's canonical form raised it, and as `PSL_INVALID_DEFAULT_LITERAL` when a codec raised it for a column default. Payload: `why`, `fix`. ### CONTRACT.CHECK_NAME_RESERVED -An authored `@@check` / `check()`'s `name:` prefix matches the shape a derived enforcement check would use for a column of the same table (`
__check` or `
__elem_not_null`), so it cannot be told apart from a derived check once a non-`managed` table strips those. The message and `collidingColumns` meta name the column(s) whose derived-check shape the prefix matches. Raised while building a SQL contract, once the table's real columns are in hand. The fix is to choose a different `name:`. Payload: `tableName`, `prefix`, `collidingColumns`. +An authored `@@check` / `check()`'s `name:` prefix matches the shape a derived enforcement check would use for a column of the same table (`
__check`, `
__elem_not_null` or `
__shape`), so it cannot be told apart from a derived check once a non-`managed` table strips those. The message and `collidingColumns` meta name the column(s) whose derived-check shape the prefix matches. Raised while building a SQL contract, once the table's real columns are in hand. The fix is to choose a different `name:`. Payload: `tableName`, `prefix`, `collidingColumns`. ### CONTRACT.CHECK_ON_STI_VARIANT @@ -689,7 +693,7 @@ An `@@index`, `@@unique`, or `@@fulltext` path that reaches into a composite typ ### PSL.PRISMA6_MONGO_COMPOSITE_MAP_UNSUPPORTED -`@map` on a field of a composite `type`, which the Mongo contract cannot express yet. Removing `@map` renames the stored field, so keep the schema until Prisma 8 supports it. Reported by the Prisma 6 MongoDB contract source (`prisma6Schema`) during `contract emit`, as a finding in the `diagnostics` list of `CONTRACT.SOURCE_LOAD_FAILED`, never on its own. `summary` is `:: `, with only the file when there is no position (the terminal prints the code before it), and `where` carries `path` and, when known, `line`. Payload: none. +`@map` on a field of a composite `type`. Prisma 8 stores each field of a value object under its own name ([ADR 309](../architecture%20docs/adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities%2C%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)), so rename the field to the name it is stored under and remove `@map`. Reported by the Prisma 6 MongoDB contract source (`prisma6Schema`) during `contract emit`, as a finding in the `diagnostics` list of `CONTRACT.SOURCE_LOAD_FAILED`, never on its own. `summary` is `:: `, with only the file when there is no position (the terminal prints the code before it), and `where` carries `path` and, when known, `line`. Payload: none. ### PSL.PRISMA6_MONGO_CONTRACT_INVALID @@ -1204,7 +1208,7 @@ A codec projection used `CodecRef.many` against a SQLite codec descriptor: SQLit ### RUNTIME.CODEC_DESCRIPTOR_INVALID -A codec descriptor handed to a target codec-descriptor registry (Postgres, SQLite) is not a valid descriptor for that target: wrong discriminant, missing target descriptor methods, or a malformed params schema. Extend the target's descriptor class or adapt a generic descriptor with the target's wrapper (`postgresCodec()` / `sqliteCodec()`). Payload: `codecId`. +A codec descriptor handed to a target codec-descriptor registry (Postgres, SQLite) is not a valid descriptor for that target: wrong discriminant, missing target descriptor methods, a Postgres descriptor whose `jsonType` is not a JSON type, or a malformed params schema. Extend the target's descriptor class or adapt a generic descriptor with the target's wrapper (`postgresCodec()` / `sqliteCodec()`). Payload: `codecId`. ### RUNTIME.CODEC_DESCRIPTOR_MISSING @@ -1741,7 +1745,7 @@ An apply command (`db migrate`/`db update`) cannot find a path through the on-di Migration planning (typically during `db init`/`db update`) failed because of conflicts, e.g. the live database already contains objects that clash with the plan. The envelope aggregates each conflict's summary and suggested fix. Payload: `conflicts`; a conflict's `location` and `refusedStatement` leave out `namespaceId` for the unbound namespace, whose id is internal. A conflict of kind `statementRefused` refuses a `--rename` statement the planner cannot carry out, and carries that statement (`refusedStatement`, in namespace, model and field names): moving a model into the unbound namespace, which has no fixed schema; renaming or moving a table, or renaming a column, whose control policy is not `managed`; renaming a namespace with a table whose control policy is not `managed`, a namespace the starting schema does not have or whose new name it already has, Postgres's `public`, which Prisma owns and Postgres puts on every search path (the message says to move each model with `--rename :.` instead), or a schema another contract space also declares (the message names the space); a model with no table; a table or column the starting schema does not have, or already has under the new name, or a table the destination namespace already has; a field that has a column on one side of the statement only; a field rename on a model whose table changes when no statement renames the table (the message gives the steps: change the contract so only the table name changes and emit it, create a migration with `prisma migration new --from `, add `...this.renameTable(...)` to its operations and run its `migration.ts` with `node`, then emit the final contract and plan the field rename with `--from` that migration; with `db update`: change the contract so only the table name changes and emit it, rename the table in the database yourself with the SQL the message gives, check that `db update --dry-run` plans no drop of a table or column and run `db update`, then emit the final contract and run `db update` with the field statement); or a rename that produces an operation the command's operation policy does not allow. On MongoDB the planner refuses: renaming a namespace; renaming `_id`; renaming a variant whose discriminator value changes (the message says to keep the value with `@@base(, "")`); a field whose stored name contains `.` or starts with `$` (renaming from such a name says to rename it in the database yourself; renaming to one says to choose another stored name); renaming a time-series collection (the message says to create the new collection with the same time-series options, copy the documents and leave out the statement); renaming a field of a time-series collection, since MongoDB updates its documents only through the metaField (the message says to keep the field's stored name); removing a field of a time-series collection, a conflict of kind `fieldRemovalRefused` (the message says to keep the field in the contract, optional if new documents leave it out); renaming or removing a field of one root model in a collection that stores several root models without a discriminator, when another of them stores the same field and does not rename it to the same new name in the same plan (removal is a `fieldRemovalRefused` conflict; the message says to move the models to separate collections or carry out the change in a `dataTransform` operation with a filter); a collection the schema being planned from does not have, which includes a view; a collection rename onto a name the schema already has, or onto a name MongoDB rejects (empty, containing `$` or a null character, or starting with `system.`); a field stored in documents on one side of the statement only, or an embedded relation; a collection whose control policy is not `managed`; and a field rename whose collection changes with no statement renaming the model (when the model keeps its name, the message gives the steps: change only `@@map` and emit; with `db update`, rename the collection in `mongosh` and run `db update`; with `migration plan`, plan with `--delete `, make `renameCollection(...)` the first operation in its `migration.ts` and delete the drop, and the create if there is one, run its `migration.ts` with `node`, and apply it with `db migrate`; then rename the field). On Postgres and SQLite, enum values are compared by their stored values: an enum value rename is refused (`statementRefused`) when the destination enum still stores the old value under another member, as in `Cannot rename enum value "Status.ARCHIVED" to "Status.RETIRED": the destination enum still stores "archived", as enum value "Status.HIDDEN"`, since every row that holds it would change; a rename is refused the same way when another member of the origin enum already stores the new stored value and keeps it, as in `Cannot rename enum value "Status.ARCHIVED" to "Status.CLOSED": the origin enum already stores "done", as enum value "Status.DONE"`, since the rename would merge the data of the two members (renames that shift values along, where each value's member moves on in the same run, are planned, last value first); renames that move stored values in a cycle are refused, as in `Cannot rename enum value "Status.ACTIVE" to "Status.X": the renames move stored values in a cycle, "active" to "old" and "old" to "active", …`, and the message says to write the rewrites by hand in a `migration.ts` through a value no member stores, while the next action prints the `migration plan` command without those renames (the conflict's `leaveOut` lists them); and a removed or renamed enum value whose stored value is not a string or a number is refused (`unsupportedOperation`). `db init` applies only additive operations, so when a conflict is an operation its policy refused (the conflict carries `refusedOperationClass`), such as adding a validator to a Mongo collection that already holds documents, the error's next action depends on the refused classes. When `db update` allows all of them (`widening` and `destructive`), the next action is `db update`, which applies them, asking what each operation that would lose data means. When one is `data`, which `db update` does not apply, the next action is `migration plan`, to plan a migration that `db migrate` then applies. -`migration plan` raises it when the planner produces no operations. The conflict's kind names the case, and its `why` is the next action. With an earlier contract (`noDatabaseChange`), the contract changed in a way the database does not see, for example after a Prisma upgrade that changes how `contract.json` records values. If you deploy with migrations, run `prisma migration new --from `, with the earlier contract's hash from the message, to write a migration with no operations, then `prisma db migrate`. If you manage the database with `db init` or `db update`, run `prisma db sign` on each database instead. With no earlier contract (`nothingToCreate`), the contract describes nothing to create; if the database needs something the contract does not describe, run `prisma migration new` and write the operations in `migration.ts` by hand. `migration new` writes a migration only to the emitted contract, so with `--to` the message says to emit the target first, and for a database managed with `db update` it names `prisma db sign `. When the migrations directory is empty and the `db` ref points at a contract that describes nothing to create (`nothingToBaseline`), there is no baseline migration to plan, and no command writes one with no operations yet; keep using `db update`. +`migration plan` raises it, as a conflict of kind `nothingToCreate`, when the planner produces no operations for a first migration: the contract describes nothing to create. If the database needs something the contract does not describe, run `prisma migration new` and write the operations in `migration.ts` by hand. `migration new` writes a migration only to the emitted contract, so with `--to` the message says to emit the target first. When there is an earlier contract, a plan with no operations is not refused: `migration plan` writes the migration with no operations and names the storage entities that changed, and `db migrate` applies it by moving the marker. ### MIGRATION.PLAN_NOT_ARRAY diff --git a/examples/bundle-size/src/mongo/generated/contract.d.ts b/examples/bundle-size/src/mongo/generated/contract.d.ts index 6bee6468e8f1..90239fc4f4e0 100644 --- a/examples/bundle-size/src/mongo/generated/contract.d.ts +++ b/examples/bundle-size/src/mongo/generated/contract.d.ts @@ -37,14 +37,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -121,7 +123,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/bundle-size/src/postgres/generated/contract.d.ts b/examples/bundle-size/src/postgres/generated/contract.d.ts index 2e97bc21082b..6612b4c98861 100644 --- a/examples/bundle-size/src/postgres/generated/contract.d.ts +++ b/examples/bundle-size/src/postgres/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -430,7 +432,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/mongo-blog-leaderboard/src/contract.d.ts b/examples/mongo-blog-leaderboard/src/contract.d.ts index dde93a89227e..cfcf297baede 100644 --- a/examples/mongo-blog-leaderboard/src/contract.d.ts +++ b/examples/mongo-blog-leaderboard/src/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'bd6f4b342df331f80b6e10550b5ee6d9a8b9dc3b279f3eec78b779d6871993d8'>; + StorageHashBase<'4211584674f0ef61d518d3b24b3380e1760d7e46d6dd7098b1b0b1dc19ff3e30'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -53,14 +53,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -231,6 +233,28 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; @@ -357,7 +381,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly bio: { readonly nullable: true; @@ -401,56 +431,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/mongo-blog-leaderboard/src/contract.json b/examples/mongo-blog-leaderboard/src/contract.json index 3043b8028674..491858a73218 100644 --- a/examples/mongo-blog-leaderboard/src/contract.json +++ b/examples/mongo-blog-leaderboard/src/contract.json @@ -180,8 +180,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "bio": { @@ -251,40 +254,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "country": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -490,13 +459,47 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "bd6f4b342df331f80b6e10550b5ee6d9a8b9dc3b279f3eec78b779d6871993d8" + "storageHash": "4211584674f0ef61d518d3b24b3380e1760d7e46d6dd7098b1b0b1dc19ff3e30" }, "capabilities": {}, "extensions": {}, diff --git a/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.json b/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.json index 3cb8739196ad..eb148543a554 100644 --- a/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.json +++ b/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd", + "to": "396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901", "providedInvariants": [], "createdAt": "2026-04-09T10:30:13.948Z", - "migrationHash": "3a28bd528b39d9b9c9a52082485a3274a8de15257cf9296776c290cc7b4e4fb5" + "migrationHash": "a4dbf4e008b176a2fed392de87662f68cd4c2f537b60cf760e234048e6f55abe" } diff --git a/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.ts b/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.ts index ee3167d3c896..24f8a81660b0 100644 --- a/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.ts +++ b/examples/mongo-demo/migrations/app/20260409T1030_migration/migration.ts @@ -1,6 +1,6 @@ import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; -import type { Contract as End } from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract'; -import endContract from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json' with { +import type { Contract as End } from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract'; +import endContract from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json' with { type: 'json', }; diff --git a/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.json b/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.json index dfb26991f67f..8ee13dc3e337 100644 --- a/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.json +++ b/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.json @@ -1,7 +1,7 @@ { - "from": "da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd", - "to": "62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590", + "from": "396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901", + "to": "95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9", "providedInvariants": [], "createdAt": "2026-06-26T16:05:45.955Z", - "migrationHash": "671d9b676330b6af88db75c5566f1fe1a7d7d4d0af4ca282828f093b8dd295c0" + "migrationHash": "08ac0867c606d262ac4dc80b3ad7f1f0afc8418bd72fe957c13c9c549bfc4651" } diff --git a/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.ts b/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.ts index 9471c658b0b7..9284d486566d 100755 --- a/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.ts +++ b/examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { collMod, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; -import type { Contract as End } from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract'; -import endContract from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json' with { +import type { Contract as End } from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract'; +import endContract from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json' with { type: 'json', }; -import type { Contract as Start } from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract'; -import startContract from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json' with { +import type { Contract as Start } from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract'; +import startContract from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json' with { type: 'json', }; diff --git a/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.json b/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.json index 6d6ea9a9ebad..76235940857c 100644 --- a/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.json +++ b/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.json @@ -1,7 +1,7 @@ { - "from": "62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590", - "to": "d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0", + "from": "95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9", + "to": "406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d", "providedInvariants": [], "createdAt": "2026-06-26T19:16:47.657Z", - "migrationHash": "a435f5206064150010733fe0d93690358b92cb892cfdfbd80c1b4aade84c16ef" + "migrationHash": "a77e60df92e1d3ee6a5a000904e3aa1869a382d602279a64bce9038c17c0475d" } diff --git a/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.ts b/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.ts index 146f9deaca22..83da597d716d 100755 --- a/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.ts +++ b/examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; -import type { Contract as Start } from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract'; -import startContract from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json' with { +import type { Contract as End } from '../../snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract'; +import endContract from '../../snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json' with { type: 'json', }; -import type { Contract as End } from '../../snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract'; -import endContract from '../../snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.json' with { +import type { Contract as Start } from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract'; +import startContract from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json' with { type: 'json', }; diff --git a/examples/mongo-demo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.d.ts b/examples/mongo-demo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.d.ts new file mode 100644 index 000000000000..450aa81593d9 --- /dev/null +++ b/examples/mongo-demo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.d.ts @@ -0,0 +1,441 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = {}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['_id', 'email', 'name']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + }; + }; + }; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/examples/mongo-demo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json b/examples/mongo-demo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json new file mode 100644 index 000000000000..5699666ec9cd --- /dev/null +++ b/examples/mongo-demo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"Article":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"summary":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"summary":{"field":"summary"}}}},"Post":{"discriminator":{"field":"kind"},"fields":{"authorId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"content":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"author":{"cardinality":"N:1","nullable":false,"on":{"localFields":["authorId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"posts","fields":{"authorId":{"field":"authorId"},"content":{"field":"content"},"createdAt":{"field":"createdAt"},"id":{"field":"_id"},"kind":{"field":"kind"},"title":{"field":"title"}}},"variants":{"article":{"model":"Article","namespace":"__unbound__"},"tutorial":{"model":"Tutorial","namespace":"__unbound__"}}},"Tutorial":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"difficulty":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"duration":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"difficulty":{"field":"difficulty"},"duration":{"field":"duration"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"bio":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["authorId"]},"to":{"model":"Post","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"bio":{"field":"bio"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"posts":{"model":"Post","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"posts":{"indexes":[{"keys":[{"direction":1,"field":"summary"}],"kind":"mongo-index","partialFilterExpression":{"kind":"article"},"unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"enum":["article"]},"summary":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["kind","summary"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"difficulty":{"bsonType":"string"},"duration":{"bsonType":"int"},"kind":{"enum":["tutorial"]},"title":{"bsonType":"string"}},"required":["difficulty","duration","kind"]}],"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["_id","authorId","content","createdAt","kind","title"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"street":{"bsonType":"string"},"zip":{"bsonType":["null","string"]}},"required":["city","country","street"]}]},"bio":{"bsonType":["null","string"]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/mongo-demo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.d.ts b/examples/mongo-demo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.d.ts new file mode 100644 index 000000000000..80c6ac7a1cf7 --- /dev/null +++ b/examples/mongo-demo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.d.ts @@ -0,0 +1,490 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly role: { + readonly output: 'admin' | 'author' | 'reader'; + readonly input: 'admin' | 'author' | 'reader'; + }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + role: FieldOutputTypes['__unbound__']['User']['role']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'authorId' }]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [ + { readonly direction: -1; readonly field: 'createdAt' }, + { readonly direction: 1; readonly field: 'authorId' }, + ]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + readonly role: { + readonly bsonType: 'string'; + readonly enum: readonly ['admin', 'author', 'reader']; + }; + }; + readonly required: readonly ['_id', 'email', 'name', 'role']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly role: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + readonly role: { readonly field: 'role' }; + }; + }; + }; + }; + readonly enum: { + readonly UserRole: { + readonly codecId: 'mongo/string@1'; + readonly members: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly UserRole: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/examples/mongo-demo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json b/examples/mongo-demo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json new file mode 100644 index 000000000000..83ba3e77dc37 --- /dev/null +++ b/examples/mongo-demo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"UserRole":{"codecId":"mongo/string@1","members":[{"name":"Admin","value":"admin"},{"name":"Author","value":"author"},{"name":"Reader","value":"reader"}]}},"models":{"Article":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"summary":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"summary":{"field":"summary"}}}},"Post":{"discriminator":{"field":"kind"},"fields":{"authorId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"content":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"author":{"cardinality":"N:1","nullable":false,"on":{"localFields":["authorId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"posts","fields":{"authorId":{"field":"authorId"},"content":{"field":"content"},"createdAt":{"field":"createdAt"},"id":{"field":"_id"},"kind":{"field":"kind"},"title":{"field":"title"}}},"variants":{"article":{"model":"Article","namespace":"__unbound__"},"tutorial":{"model":"Tutorial","namespace":"__unbound__"}}},"Tutorial":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"difficulty":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"duration":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"difficulty":{"field":"difficulty"},"duration":{"field":"duration"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"bio":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"role":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"UserRole","namespaceId":"__unbound__","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["authorId"]},"to":{"model":"Post","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"bio":{"field":"bio"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"},"role":{"field":"role"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"posts":{"model":"Post","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"posts":{"indexes":[{"keys":[{"direction":1,"field":"authorId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"createdAt"},{"direction":1,"field":"authorId"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"summary"}],"kind":"mongo-index","partialFilterExpression":{"kind":"article"},"unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"enum":["article"]},"summary":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["kind","summary"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"difficulty":{"bsonType":"string"},"duration":{"bsonType":"int"},"kind":{"enum":["tutorial"]},"title":{"bsonType":"string"}},"required":["difficulty","duration","kind"]}],"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["_id","authorId","content","createdAt","kind","title"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"street":{"bsonType":"string"},"zip":{"bsonType":["null","string"]}},"required":["city","country","street"]}]},"bio":{"bsonType":["null","string"]},"email":{"bsonType":"string"},"name":{"bsonType":"string"},"role":{"bsonType":"string","enum":["admin","author","reader"]}},"required":["_id","email","name","role"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}}},"valueSet":{"UserRole":{"kind":"valueSet","values":["admin","author","reader"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/mongo-demo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts b/examples/mongo-demo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts new file mode 100644 index 000000000000..c61fa21a4db6 --- /dev/null +++ b/examples/mongo-demo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts @@ -0,0 +1,479 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly role: { + readonly output: 'admin' | 'author' | 'reader'; + readonly input: 'admin' | 'author' | 'reader'; + }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + role: FieldOutputTypes['__unbound__']['User']['role']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + readonly role: { + readonly bsonType: 'string'; + readonly enum: readonly ['admin', 'author', 'reader']; + }; + }; + readonly required: readonly ['_id', 'email', 'name', 'role']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly role: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + readonly role: { readonly field: 'role' }; + }; + }; + }; + }; + readonly enum: { + readonly UserRole: { + readonly codecId: 'mongo/string@1'; + readonly members: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly UserRole: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/examples/mongo-demo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json b/examples/mongo-demo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json new file mode 100644 index 000000000000..feed3a57d193 --- /dev/null +++ b/examples/mongo-demo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"UserRole":{"codecId":"mongo/string@1","members":[{"name":"Admin","value":"admin"},{"name":"Author","value":"author"},{"name":"Reader","value":"reader"}]}},"models":{"Article":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"summary":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"summary":{"field":"summary"}}}},"Post":{"discriminator":{"field":"kind"},"fields":{"authorId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"content":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"author":{"cardinality":"N:1","nullable":false,"on":{"localFields":["authorId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"posts","fields":{"authorId":{"field":"authorId"},"content":{"field":"content"},"createdAt":{"field":"createdAt"},"id":{"field":"_id"},"kind":{"field":"kind"},"title":{"field":"title"}}},"variants":{"article":{"model":"Article","namespace":"__unbound__"},"tutorial":{"model":"Tutorial","namespace":"__unbound__"}}},"Tutorial":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"difficulty":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"duration":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"difficulty":{"field":"difficulty"},"duration":{"field":"duration"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"bio":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"role":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"UserRole","namespaceId":"__unbound__","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["authorId"]},"to":{"model":"Post","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"bio":{"field":"bio"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"},"role":{"field":"role"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"posts":{"model":"Post","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"posts":{"indexes":[{"keys":[{"direction":1,"field":"summary"}],"kind":"mongo-index","partialFilterExpression":{"kind":"article"},"unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"enum":["article"]},"summary":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["kind","summary"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"difficulty":{"bsonType":"string"},"duration":{"bsonType":"int"},"kind":{"enum":["tutorial"]},"title":{"bsonType":"string"}},"required":["difficulty","duration","kind"]}],"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["_id","authorId","content","createdAt","kind","title"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"street":{"bsonType":"string"},"zip":{"bsonType":["null","string"]}},"required":["city","country","street"]}]},"bio":{"bsonType":["null","string"]},"email":{"bsonType":"string"},"name":{"bsonType":"string"},"role":{"bsonType":"string","enum":["admin","author","reader"]}},"required":["_id","email","name","role"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}}},"valueSet":{"UserRole":{"kind":"valueSet","values":["admin","author","reader"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/mongo-demo/src/contract.d.ts b/examples/mongo-demo/src/contract.d.ts index af70721dd428..80c6ac7a1cf7 100644 --- a/examples/mongo-demo/src/contract.d.ts +++ b/examples/mongo-demo/src/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0'>; + StorageHashBase<'406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -64,14 +64,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -256,6 +258,28 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; @@ -382,7 +406,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly bio: { readonly nullable: true; @@ -431,28 +461,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly enum: { readonly UserRole: { readonly codecId: 'mongo/string@1'; @@ -476,28 +484,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/mongo-demo/src/contract.json b/examples/mongo-demo/src/contract.json index 8e6eaca148f4..536570edf7cb 100644 --- a/examples/mongo-demo/src/contract.json +++ b/examples/mongo-demo/src/contract.json @@ -199,8 +199,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "bio": { @@ -286,40 +289,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "country": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -552,6 +521,40 @@ } } }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "UserRole": { "kind": "valueSet", @@ -567,7 +570,7 @@ "kind": "mongo-database" } }, - "storageHash": "d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0" + "storageHash": "406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d" }, "capabilities": {}, "extensions": {}, diff --git a/examples/multi-extension-monorepo/app/src/contract.d.ts b/examples/multi-extension-monorepo/app/src/contract.d.ts index 3b6e63ce9c6a..87fd9e1c74c5 100644 --- a/examples/multi-extension-monorepo/app/src/contract.d.ts +++ b/examples/multi-extension-monorepo/app/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -438,7 +440,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/multi-extension-monorepo/packages/audit/migrations/snapshots/d60ebdd877de6283fafbbe0dc9a4fa13c1197009a53ba95ed37ba04fa3640cb1/contract.d.ts b/examples/multi-extension-monorepo/packages/audit/migrations/snapshots/d60ebdd877de6283fafbbe0dc9a4fa13c1197009a53ba95ed37ba04fa3640cb1/contract.d.ts index 796b7442a900..69c08654b1b8 100644 --- a/examples/multi-extension-monorepo/packages/audit/migrations/snapshots/d60ebdd877de6283fafbbe0dc9a4fa13c1197009a53ba95ed37ba04fa3640cb1/contract.d.ts +++ b/examples/multi-extension-monorepo/packages/audit/migrations/snapshots/d60ebdd877de6283fafbbe0dc9a4fa13c1197009a53ba95ed37ba04fa3640cb1/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -438,7 +440,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts b/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts index 796b7442a900..69c08654b1b8 100644 --- a/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts +++ b/examples/multi-extension-monorepo/packages/audit/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -438,7 +440,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/multi-extension-monorepo/packages/feature-flags/migrations/snapshots/9ea058903cdc9636107c83c1a6bbb6c3f28d92d4515cff00c31c2b4e37bb5bd4/contract.d.ts b/examples/multi-extension-monorepo/packages/feature-flags/migrations/snapshots/9ea058903cdc9636107c83c1a6bbb6c3f28d92d4515cff00c31c2b4e37bb5bd4/contract.d.ts index 578d9aa80d33..fa7f1de08e39 100644 --- a/examples/multi-extension-monorepo/packages/feature-flags/migrations/snapshots/9ea058903cdc9636107c83c1a6bbb6c3f28d92d4515cff00c31c2b4e37bb5bd4/contract.d.ts +++ b/examples/multi-extension-monorepo/packages/feature-flags/migrations/snapshots/9ea058903cdc9636107c83c1a6bbb6c3f28d92d4515cff00c31c2b4e37bb5bd4/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -426,7 +428,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts b/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts index 578d9aa80d33..fa7f1de08e39 100644 --- a/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts +++ b/examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -426,7 +428,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/paradedb-demo/src/prisma/contract.d.ts b/examples/paradedb-demo/src/prisma/contract.d.ts index 55aef7a45f14..4bf232c93130 100644 --- a/examples/paradedb-demo/src/prisma/contract.d.ts +++ b/examples/paradedb-demo/src/prisma/contract.d.ts @@ -277,14 +277,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -477,7 +479,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-cloudflare-worker/src/prisma/contract.d.ts b/examples/prisma-8-cloudflare-worker/src/prisma/contract.d.ts index 6b0d7d4a3e13..0803641ee61e 100644 --- a/examples/prisma-8-cloudflare-worker/src/prisma/contract.d.ts +++ b/examples/prisma-8-cloudflare-worker/src/prisma/contract.d.ts @@ -20,11 +20,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-postgres/contract/types'; export type StorageHash = - StorageHashBase<'b596b070c94c65a7c369422bd7b328fa8a3c7b5c33440ff96d6951eab14f40a0'>; + StorageHashBase<'aec35593db935d2bb2a8ce976972281fea82f22acbad88e81f6ec60b1bba210f'>; export type ExecutionHash = ExecutionHashBase<'0a7f0d7c621b64f307b872110f0effe4cd04d5410d04e913f6550ef107d46788'>; export type ProfileHash = @@ -270,15 +270,15 @@ type DefaultLiteralValue = CodecId extends keyo : CodecTypes[CodecId]['json'] : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -289,6 +289,9 @@ export type AddressInput = ValueObjectInputTypes['public']['Address']; type FieldTypeOverrides = { readonly public: { readonly models: { + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -300,17 +303,22 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly user: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -673,11 +681,37 @@ type ContractBase = Omit< }; }; readonly valueSet: { + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; readonly user_type: { readonly kind: 'valueSet'; readonly values: readonly ['admin', 'user']; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -855,7 +889,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly createdAt: { readonly nullable: false; @@ -913,28 +953,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly enum: { readonly user_type: { readonly codecId: 'pg/text@1'; @@ -1013,28 +1031,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-cloudflare-worker/src/prisma/contract.json b/examples/prisma-8-cloudflare-worker/src/prisma/contract.json index a0aa3aa1f77c..68c80593644d 100644 --- a/examples/prisma-8-cloudflare-worker/src/prisma/contract.json +++ b/examples/prisma-8-cloudflare-worker/src/prisma/contract.json @@ -290,8 +290,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "public", + "plane": "storage" } }, "createdAt": { @@ -393,40 +396,6 @@ "table": "user" } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "country": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } @@ -598,6 +567,13 @@ "uniques": [] }, "task": { + "checks": [ + { + "expression": "\"type\" IN ('bug', 'feature')", + "name": "task_type_check_cda3f5e7", + "prefix": "task_type_check" + } + ], "columns": { "createdAt": { "codecId": "pg/timestamptz-temporal@1", @@ -635,7 +611,13 @@ "type": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Task.type", + "namespaceId": "public", + "plane": "storage" + } }, "userId": { "codecId": "pg/text@1", @@ -683,6 +665,11 @@ }, "user": { "checks": [ + { + "expression": "\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE", + "name": "user_address_shape_69432dc1", + "prefix": "user_address_shape" + }, { "expression": "\"kind\" IN ('admin', 'user')", "name": "user_kind_check_836d43ef", @@ -741,7 +728,48 @@ "uniques": [] } }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { + "Task.type": { + "kind": "valueSet", + "values": [ + "bug", + "feature" + ] + }, "user_type": { "kind": "valueSet", "values": [ @@ -755,7 +783,7 @@ "kind": "postgres-schema" } }, - "storageHash": "b596b070c94c65a7c369422bd7b328fa8a3c7b5c33440ff96d6951eab14f40a0" + "storageHash": "aec35593db935d2bb2a8ce976972281fea82f22acbad88e81f6ec60b1bba210f" }, "execution": { "executionHash": "0a7f0d7c621b64f307b872110f0effe4cd04d5410d04e913f6550ef107d46788", diff --git a/examples/prisma-8-demo-mysql/src/prisma/contract.d.ts b/examples/prisma-8-demo-mysql/src/prisma/contract.d.ts index 6dd6955c1b05..ed95e8ace18a 100644 --- a/examples/prisma-8-demo-mysql/src/prisma/contract.d.ts +++ b/examples/prisma-8-demo-mysql/src/prisma/contract.d.ts @@ -49,14 +49,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly __unbound__: { @@ -341,7 +343,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo-sqlite/src/prisma/contract.d.ts b/examples/prisma-8-demo-sqlite/src/prisma/contract.d.ts index 8ec655c64258..f848cdae6967 100644 --- a/examples/prisma-8-demo-sqlite/src/prisma/contract.d.ts +++ b/examples/prisma-8-demo-sqlite/src/prisma/contract.d.ts @@ -140,14 +140,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -685,7 +687,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.json b/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.json index 21aa5ae9040c..7997c3214ad8 100644 --- a/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.json +++ b/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b", + "to": "fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263", "providedInvariants": [], "createdAt": "2026-06-19T15:33:14.992Z", - "migrationHash": "bde42f97a6cbf4ca06e4507692274c8e3ece1e1b0672f050bb78a6a2eaa0ff0d" + "migrationHash": "2c5c33804cfc36206f142a1ee2741fa1e5542244b9754cd569ceca6a6730d307" } diff --git a/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.ts b/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.ts index 5f113150356b..c75627b0937b 100644 --- a/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.ts +++ b/examples/prisma-8-demo/migrations/app/20260422T0720_initial/migration.ts @@ -10,8 +10,8 @@ import { rawSql, unique, } from '@prisma/orm-postgres/target/migration'; -import type { Contract as End } from '../../snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract'; -import endContract from '../../snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json' with { +import type { Contract as End } from '../../snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract'; +import endContract from '../../snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.json' with { type: 'json', }; @@ -86,6 +86,10 @@ export default class M extends Migration { ], constraints: [ primaryKey(['id']), + checkExpression( + 'user_address_shape_69432dc1', + '"address" IS NULL OR jsonb_path_match("address", \'strict $.type() == "null" || ($.type() == "object" && exists($.keyvalue() ? (@.key == "city" && (@.value.type() == "string"))) && exists($.keyvalue() ? (@.key == "country" && (@.value.type() == "string"))) && exists($.keyvalue() ? (@.key == "street" && (@.value.type() == "string"))) && !exists($.keyvalue() ? (@.key == "zip" && !(@.value.type() == "null" || (@.value.type() == "string")))) && !exists($.keyvalue() ? (@.key != "city" && @.key != "country" && @.key != "street" && @.key != "zip")))\') IS TRUE', + ), checkExpression('user_kind_check_836d43ef', "\"kind\" IN ('admin', 'user')"), ], }), @@ -117,7 +121,10 @@ export default class M extends Migration { col('type', 'text', { notNull: true }), col('userId', 'uuid', { notNull: true }), ], - constraints: [primaryKey(['id'])], + constraints: [ + primaryKey(['id']), + checkExpression('task_type_check_cda3f5e7', "\"type\" IN ('bug', 'feature')"), + ], }), this.createTable({ schema: 'public', diff --git a/examples/prisma-8-demo/migrations/app/20260422T0720_initial/ops.json b/examples/prisma-8-demo/migrations/app/20260422T0720_initial/ops.json index 3843b04f54cb..90044b7cea51 100644 --- a/examples/prisma-8-demo/migrations/app/20260422T0720_initial/ops.json +++ b/examples/prisma-8-demo/migrations/app/20260422T0720_initial/ops.json @@ -190,7 +190,7 @@ "execute": [ { "description": "create table \"user\"", - "sql": "CREATE TABLE \"public\".\"user\" (\n \"address\" jsonb,\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"displayName\" text NOT NULL,\n \"email\" text NOT NULL,\n \"id\" uuid NOT NULL,\n \"kind\" text NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"user_kind_check_836d43ef\" CHECK (\"kind\" IN ('admin', 'user'))\n)", + "sql": "CREATE TABLE \"public\".\"user\" (\n \"address\" jsonb,\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"displayName\" text NOT NULL,\n \"email\" text NOT NULL,\n \"id\" uuid NOT NULL,\n \"kind\" text NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"user_address_shape_69432dc1\" CHECK (\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE),\n CONSTRAINT \"user_kind_check_836d43ef\" CHECK (\"kind\" IN ('admin', 'user'))\n)", "params": [] } ], @@ -276,7 +276,7 @@ "execute": [ { "description": "create table \"task\"", - "sql": "CREATE TABLE \"public\".\"task\" (\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"description\" text,\n \"id\" uuid NOT NULL,\n \"status\" text DEFAULT 'open' NOT NULL,\n \"title\" text NOT NULL,\n \"type\" text NOT NULL,\n \"userId\" uuid NOT NULL,\n PRIMARY KEY (\"id\")\n)", + "sql": "CREATE TABLE \"public\".\"task\" (\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"description\" text,\n \"id\" uuid NOT NULL,\n \"status\" text DEFAULT 'open' NOT NULL,\n \"title\" text NOT NULL,\n \"type\" text NOT NULL,\n \"userId\" uuid NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"task_type_check_cda3f5e7\" CHECK (\"type\" IN ('bug', 'feature'))\n)", "params": [] } ], diff --git a/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.json b/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.json index 9f0536ef42d7..e622cfcdf9d1 100644 --- a/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.json +++ b/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.json @@ -1,7 +1,7 @@ { - "from": "58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b", - "to": "d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0", + "from": "fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263", + "to": "db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e", "providedInvariants": [], "createdAt": "2026-08-10T11:08:15.318Z", - "migrationHash": "30ef568500a1d674c45bf47a53c6308e4bc66c2e5f7ea0244b6e75862fe33f19" + "migrationHash": "08c1ab86ecb9a102e4d319103d6351f63b264d37dfa6e955a8ae89b6db8084f8" } diff --git a/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.ts b/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.ts index 446676eed417..5c8e267a2186 100755 --- a/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.ts +++ b/examples/prisma-8-demo/migrations/app/20260810T1108_add_post_engagement_counters/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { col, Migration, MigrationCLI } from '@prisma/orm-postgres/migration'; -import type { Contract as Start } from '../../snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract'; -import startContract from '../../snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json' with { +import type { Contract as End } from '../../snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract'; +import endContract from '../../snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.json' with { type: 'json', }; -import type { Contract as End } from '../../snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract'; -import endContract from '../../snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.json' with { +import type { Contract as Start } from '../../snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract'; +import startContract from '../../snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.json' with { type: 'json', }; diff --git a/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.json b/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.json index b4f9c3bd010b..3fec93e8daab 100644 --- a/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.json +++ b/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.json @@ -1,7 +1,7 @@ { - "from": "d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0", - "to": "0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de", + "from": "db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e", + "to": "705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040", "providedInvariants": [], "createdAt": "2026-09-17T08:18:38.345Z", - "migrationHash": "62eb41b027ffc482395373ed4cec0cea2060e9332bd814fb8e63f5ac7cc7b29b" + "migrationHash": "6d20b99b0fcf0814942532ee11ba97378a74fa794b964e2258b41163bc7911c7" } diff --git a/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.ts b/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.ts index 5851159d1e37..d335973ad496 100755 --- a/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.ts +++ b/examples/prisma-8-demo/migrations/app/20260917T0818_add_post_expires_at/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { col, fn, Migration, MigrationCLI } from '@prisma/orm-postgres/migration'; -import type { Contract as End } from '../../snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract'; -import endContract from '../../snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.json' with { +import type { Contract as End } from '../../snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract'; +import endContract from '../../snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.json' with { type: 'json', }; -import type { Contract as Start } from '../../snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract'; -import startContract from '../../snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.json' with { +import type { Contract as Start } from '../../snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract'; +import startContract from '../../snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.json' with { type: 'json', }; diff --git a/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.json b/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.json index b0a6391a8cb6..4bd71245bc65 100644 --- a/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.json +++ b/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.json @@ -1,7 +1,7 @@ { - "from": "0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de", - "to": "6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f", + "from": "705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040", + "to": "53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5", "providedInvariants": [], "createdAt": "2026-09-22T12:18:55.150Z", - "migrationHash": "27be4eb0ec18aedaf49c8606d19688bb3a7c3ac0e775700b09f52e31a2ad472e" + "migrationHash": "cbe96b340e61c87aa4bb148032c9500140473569e820f4d0f4c29e960f27cda0" } diff --git a/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.ts b/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.ts index 01851cad9f91..08c9514102d9 100755 --- a/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.ts +++ b/examples/prisma-8-demo/migrations/app/20260922T1218_add_post_title_search/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { Migration, MigrationCLI } from '@prisma/orm-postgres/migration'; -import type { Contract as Start } from '../../snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract'; -import startContract from '../../snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.json' with { +import type { Contract as End } from '../../snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract'; +import endContract from '../../snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.json' with { type: 'json', }; -import type { Contract as End } from '../../snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract'; -import endContract from '../../snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.json' with { +import type { Contract as Start } from '../../snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract'; +import startContract from '../../snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.json' with { type: 'json', }; diff --git a/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.json b/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.json index 808b4a75c3a2..8ec5fb6bd543 100644 --- a/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.json +++ b/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.json @@ -1,7 +1,7 @@ { - "from": "6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f", - "to": "0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a", + "from": "53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5", + "to": "e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf", "providedInvariants": [], "createdAt": "2026-10-09T13:54:52.344Z", - "migrationHash": "6fd650eef48eddc461e8cbf14297934b97b479e46de2d392a27b6eb935a40c5e" + "migrationHash": "8d96c62996481cbd9466a0b3df3ff8686345b0343f384bc21d0a7d36114634fe" } diff --git a/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.ts b/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.ts index 127ea966b558..26afa740e0fc 100755 --- a/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.ts +++ b/examples/prisma-8-demo/migrations/app/20261009T1354_add_post_body_search/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { col, Migration, MigrationCLI } from '@prisma/orm-postgres/migration'; -import type { Contract as End } from '../../snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract'; -import endContract from '../../snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.json' with { +import type { Contract as Start } from '../../snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract'; +import startContract from '../../snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.json' with { type: 'json', }; -import type { Contract as Start } from '../../snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract'; -import startContract from '../../snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.json' with { +import type { Contract as End } from '../../snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract'; +import endContract from '../../snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.json' with { type: 'json', }; diff --git a/examples/prisma-8-demo/migrations/snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.json b/examples/prisma-8-demo/migrations/snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.json deleted file mode 100644 index 1b0713a207e8..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"expiresAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"expiresAt":{"column":"expiresAt"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"expiresAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"(now() + '7 days'::interval)","kind":"function"},"nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.json b/examples/prisma-8-demo/migrations/snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.json deleted file mode 100644 index 3d7625e612b5..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"body":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"expiresAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"body":{"column":"body"},"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"expiresAt":{"column":"expiresAt"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"body":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"expiresAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"(now() + '7 days'::interval)","kind":"function"},"nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["title","body"],"name":"post_search_f03270be","options":{"language":"english","weightGroups":[["title"],["body"]]},"prefix":"post_search","type":"fullText","unique":false},{"columns":["title"],"name":"post_title_search_e0dd1131","options":{"language":"english","weightGroups":[["title"]]},"prefix":"post_title_search","type":"fullText","unique":false},{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.d.ts similarity index 96% rename from examples/prisma-8-demo/migrations/snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.d.ts rename to examples/prisma-8-demo/migrations/snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.d.ts index 111a2ad19b9b..0129d1913fa2 100644 --- a/examples/prisma-8-demo/migrations/snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.d.ts +++ b/examples/prisma-8-demo/migrations/snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.d.ts @@ -25,11 +25,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-postgres/contract/types'; export type StorageHash = - StorageHashBase<'6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f'>; + StorageHashBase<'53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5'>; export type ExecutionHash = ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; export type ProfileHash = @@ -293,15 +293,15 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -315,6 +315,9 @@ type FieldTypeOverrides = { readonly Post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; }; + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -326,18 +329,23 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly user: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -857,11 +865,37 @@ type ContractBase = Omit< }; readonly valueSet: { readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; readonly user_type: { readonly kind: 'valueSet'; readonly values: readonly ['admin', 'user']; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -1170,7 +1204,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly createdAt: { readonly nullable: false; @@ -1228,28 +1268,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly enum: { readonly Priority: { readonly codecId: 'pg/int4@1'; @@ -1405,28 +1423,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo/migrations/snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.json b/examples/prisma-8-demo/migrations/snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.json new file mode 100644 index 000000000000..6bf3b40e6ed7 --- /dev/null +++ b/examples/prisma-8-demo/migrations/snapshots/53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"expiresAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"expiresAt":{"column":"expiresAt"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"public","plane":"storage"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"expiresAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"(now() + '7 days'::interval)","kind":"function"},"nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["title"],"name":"post_title_search_e0dd1131","options":{"language":"english","weightGroups":[["title"]]},"prefix":"post_title_search","type":"fullText","unique":false},{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"checks":[{"expression":"\"type\" IN ('bug', 'feature')","name":"task_type_check_cda3f5e7","prefix":"task_type_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Task.type","namespaceId":"public","plane":"storage"}},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE","name":"user_address_shape_69432dc1","prefix":"user_address_shape"},{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"Task.type":{"kind":"valueSet","values":["bug","feature"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"53cf82d46a1c1c79995d8ea54e981886bbdce3d4cd9944506960643877c474f5"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.json b/examples/prisma-8-demo/migrations/snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.json deleted file mode 100644 index 7cfdc5d58ac3..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"expiresAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"expiresAt":{"column":"expiresAt"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"expiresAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"(now() + '7 days'::interval)","kind":"function"},"nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["title"],"name":"post_title_search_e0dd1131","options":{"language":"english","weightGroups":[["title"]]},"prefix":"post_title_search","type":"fullText","unique":false},{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"6e6fbbc22d1d4904fbc8668644c5cabe694d946e550a2f0cdd07aaab308b953f"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.d.ts similarity index 96% rename from examples/prisma-8-demo/migrations/snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.d.ts rename to examples/prisma-8-demo/migrations/snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.d.ts index 96499166b9d7..cb836967d516 100644 --- a/examples/prisma-8-demo/migrations/snapshots/0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de/contract.d.ts +++ b/examples/prisma-8-demo/migrations/snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.d.ts @@ -25,11 +25,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-postgres/contract/types'; export type StorageHash = - StorageHashBase<'0ce16e4140d056a3abddbe18bd7ee5198d392370f0aeb23236216c51505944de'>; + StorageHashBase<'705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040'>; export type ExecutionHash = ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; export type ProfileHash = @@ -293,15 +293,15 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -315,6 +315,9 @@ type FieldTypeOverrides = { readonly Post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; }; + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -326,18 +329,23 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly user: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -846,11 +854,37 @@ type ContractBase = Omit< }; readonly valueSet: { readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; readonly user_type: { readonly kind: 'valueSet'; readonly values: readonly ['admin', 'user']; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -1159,7 +1193,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly createdAt: { readonly nullable: false; @@ -1217,28 +1257,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly enum: { readonly Priority: { readonly codecId: 'pg/int4@1'; @@ -1394,28 +1412,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo/migrations/snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.json b/examples/prisma-8-demo/migrations/snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.json new file mode 100644 index 000000000000..06e0179d5161 --- /dev/null +++ b/examples/prisma-8-demo/migrations/snapshots/705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"expiresAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"expiresAt":{"column":"expiresAt"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"public","plane":"storage"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"expiresAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"(now() + '7 days'::interval)","kind":"function"},"nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"checks":[{"expression":"\"type\" IN ('bug', 'feature')","name":"task_type_check_cda3f5e7","prefix":"task_type_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Task.type","namespaceId":"public","plane":"storage"}},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE","name":"user_address_shape_69432dc1","prefix":"user_address_shape"},{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"Task.type":{"kind":"valueSet","values":["bug","feature"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"705398c87a4f2c7e2ac0a49002b9061963f726b8875ffe53da1feac8df00b040"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e/contract.d.ts deleted file mode 100644 index 80013a282b41..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e/contract.d.ts +++ /dev/null @@ -1,1443 +0,0 @@ -// ⚠️ GENERATED FILE - DO NOT EDIT -// This file is automatically generated by 'prisma contract emit'. -// To regenerate, run: prisma contract emit -import type { - CodecTypes as PgVectorTypes, - Vector, -} from '@prisma/orm-extension-pgvector/codec-types'; -import type { QueryOperationTypes as PgVectorQueryOperationTypes } from '@prisma/orm-extension-pgvector/operation-types'; -import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma/orm-postgres/adapter/operation-types'; -import type { - Bit, - Char, - CodecTypes as PgTypes, - JsonValue, - Numeric, - TimeString, - TimestampString, - TimestamptzString, - Timetz, - VarBit, - Varchar, -} from '@prisma/orm-postgres/target/codec-types'; - -import type { - ContractWithTypeMaps, - RelationKeys, - TypeMaps as TypeMapsType, -} from '@prisma/orm-postgres/family-contract/types'; -import type { - Contract as ContractType, - ExecutionHashBase, - NamespaceId, - ProfileHashBase, - StorageHashBase, -} from '@prisma/orm-postgres/contract/types'; - -export type StorageHash = - StorageHashBase<'9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e'>; -export type ExecutionHash = - ExecutionHashBase<'e592be8e097b3e2b4958da0b3ed54265d1bfa0523295fa03e03af2364cccbd08'>; -export type ProfileHash = - ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; - -export type CodecTypes = PgTypes & PgVectorTypes; -export type LaneCodecTypes = CodecTypes; -export type QueryOperationTypes = PgAdapterQueryOps & - PgVectorQueryOperationTypes; -export type AggregateTypes = { - readonly avg: { - readonly byCodec: { - readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - }; - }; - readonly avgDecimal: { - readonly byCodec: { - readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - }; - }; - readonly count: { - readonly byCodec: {}; - readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; - readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; - }; - readonly countBigInt: { - readonly byCodec: {}; - readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; - readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; - }; - readonly max: { - readonly byCodec: { - readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; - readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; - readonly 'pg/date-temporal@1': { - readonly output: 'pg/date-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; - readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; - readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { - readonly output: 'pg/time-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-string@1': { - readonly output: 'pg/timestamp-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-temporal@1': { - readonly output: 'pg/timestamp-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-string@1': { - readonly output: 'pg/timestamptz-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-temporal@1': { - readonly output: 'pg/timestamptz-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; - readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; - readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - }; - }; - readonly min: { - readonly byCodec: { - readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; - readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; - readonly 'pg/date-temporal@1': { - readonly output: 'pg/date-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; - readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; - readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { - readonly output: 'pg/time-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-string@1': { - readonly output: 'pg/timestamp-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-temporal@1': { - readonly output: 'pg/timestamp-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-string@1': { - readonly output: 'pg/timestamptz-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-temporal@1': { - readonly output: 'pg/timestamptz-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; - readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; - readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - }; - }; - readonly stddev: { - readonly byCodec: { - readonly 'pg/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - }; - }; - readonly sum: { - readonly byCodec: { - readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - }; - }; - readonly sumBigInt: { - readonly byCodec: { - readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - }; - }; -}; -type DefaultLiteralValue = CodecId extends keyof CodecTypes - ? Encoded extends CodecTypes[CodecId]['json'] - ? Encoded - : CodecTypes[CodecId]['json'] - : Encoded; -export type AddressOutput = { - readonly street: CodecTypes['pg/text@1']['output']; - readonly city: CodecTypes['pg/text@1']['output']; - readonly zip: CodecTypes['pg/text@1']['output'] | null; - readonly country: CodecTypes['pg/text@1']['output']; -}; -export type AddressInput = { - readonly street: CodecTypes['pg/text@1']['input']; - readonly city: CodecTypes['pg/text@1']['input']; - readonly zip: CodecTypes['pg/text@1']['input'] | null; - readonly country: CodecTypes['pg/text@1']['input']; -}; -export type FieldOutputTypes = { - readonly public: { - readonly Bug: { - readonly severity: CodecTypes['pg/text@1']['output']; - readonly stepsToRepro: CodecTypes['pg/text@1']['output'] | null; - }; - readonly Feature: { - readonly priority: CodecTypes['pg/text@1']['output']; - readonly targetRelease: CodecTypes['pg/text@1']['output'] | null; - }; - readonly Post: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly title: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - readonly priority: 'low' | 'high' | 'urgent'; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly embedding: Vector<1536> | null; - }; - readonly PostTag: { - readonly postId: CodecTypes['pg/uuid@1']['output']; - readonly tagId: CodecTypes['pg/uuid@1']['output']; - }; - readonly Tag: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly label: CodecTypes['pg/text@1']['output']; - }; - readonly Task: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly title: CodecTypes['pg/text@1']['output']; - readonly description: CodecTypes['pg/text@1']['output'] | null; - readonly status: CodecTypes['pg/text@1']['output']; - readonly type: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - }; - readonly User: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly email: CodecTypes['pg/text@1']['output']; - readonly displayName: CodecTypes['pg/text@1']['output']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly kind: 'admin' | 'user'; - readonly address: AddressOutput | null; - }; - }; -}; -export type FieldInputTypes = { - readonly public: { - readonly Bug: { - readonly severity: CodecTypes['pg/text@1']['input']; - readonly stepsToRepro: CodecTypes['pg/text@1']['input'] | null; - }; - readonly Feature: { - readonly priority: CodecTypes['pg/text@1']['input']; - readonly targetRelease: CodecTypes['pg/text@1']['input'] | null; - }; - readonly Post: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly title: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - readonly priority: 'low' | 'high' | 'urgent'; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly embedding: CodecTypes['pg/vector@1']['input'] | null; - }; - readonly PostTag: { - readonly postId: CodecTypes['pg/uuid@1']['input']; - readonly tagId: CodecTypes['pg/uuid@1']['input']; - }; - readonly Tag: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly label: CodecTypes['pg/text@1']['input']; - }; - readonly Task: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly title: CodecTypes['pg/text@1']['input']; - readonly description: CodecTypes['pg/text@1']['input'] | null; - readonly status: CodecTypes['pg/text@1']['input']; - readonly type: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - }; - readonly User: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly email: CodecTypes['pg/text@1']['input']; - readonly displayName: CodecTypes['pg/text@1']['input']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly kind: 'admin' | 'user'; - readonly address: AddressInput | null; - }; - }; -}; -export type StorageColumnTypes = { - readonly public: { - readonly bug: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly severity: CodecTypes['pg/text@1']['output']; - readonly stepsToRepro: CodecTypes['pg/text@1']['output'] | null; - }; - readonly feature: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly priority: CodecTypes['pg/text@1']['output']; - readonly targetRelease: CodecTypes['pg/text@1']['output'] | null; - }; - readonly post: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly embedding: Vector<1536> | null; - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly priority: 'low' | 'high' | 'urgent'; - readonly title: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - }; - readonly post_tag: { - readonly postId: CodecTypes['pg/uuid@1']['output']; - readonly tagId: CodecTypes['pg/uuid@1']['output']; - }; - readonly tag: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly label: CodecTypes['pg/text@1']['output']; - }; - readonly task: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly description: CodecTypes['pg/text@1']['output'] | null; - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly status: CodecTypes['pg/text@1']['output']; - readonly title: CodecTypes['pg/text@1']['output']; - readonly type: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - }; - readonly user: { - readonly address: CodecTypes['pg/jsonb@1']['output'] | null; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly displayName: CodecTypes['pg/text@1']['output']; - readonly email: CodecTypes['pg/text@1']['output']; - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly kind: 'admin' | 'user'; - }; - }; -}; -export type StorageColumnInputTypes = { - readonly public: { - readonly bug: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly severity: CodecTypes['pg/text@1']['input']; - readonly stepsToRepro: CodecTypes['pg/text@1']['input'] | null; - }; - readonly feature: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly priority: CodecTypes['pg/text@1']['input']; - readonly targetRelease: CodecTypes['pg/text@1']['input'] | null; - }; - readonly post: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly embedding: CodecTypes['pg/vector@1']['input'] | null; - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly priority: 'low' | 'high' | 'urgent'; - readonly title: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - }; - readonly post_tag: { - readonly postId: CodecTypes['pg/uuid@1']['input']; - readonly tagId: CodecTypes['pg/uuid@1']['input']; - }; - readonly tag: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly label: CodecTypes['pg/text@1']['input']; - }; - readonly task: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly description: CodecTypes['pg/text@1']['input'] | null; - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly status: CodecTypes['pg/text@1']['input']; - readonly title: CodecTypes['pg/text@1']['input']; - readonly type: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - }; - readonly user: { - readonly address: CodecTypes['pg/jsonb@1']['input'] | null; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly displayName: CodecTypes['pg/text@1']['input']; - readonly email: CodecTypes['pg/text@1']['input']; - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly kind: 'admin' | 'user'; - }; - }; -}; - -export namespace Models { - export type public_User = { - id: CodecTypes['pg/uuid@1']['output']; - email: CodecTypes['pg/text@1']['output']; - displayName: CodecTypes['pg/text@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - kind: 'admin' | 'user'; - address: AddressOutput | null; - posts: public_Post[]; - tasks: public_AnyTask[]; - readonly [RelationKeys]?: 'posts' | 'tasks'; - }; - export type public_Post = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - userId: CodecTypes['pg/uuid@1']['output']; - priority: 'low' | 'high' | 'urgent'; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - embedding: Vector<1536> | null; - tags: public_Tag[]; - user: public_User; - readonly [RelationKeys]?: 'tags' | 'user'; - }; - export type public_Tag = { - id: CodecTypes['pg/uuid@1']['output']; - label: CodecTypes['pg/text@1']['output']; - posts: public_Post[]; - readonly [RelationKeys]?: 'posts'; - }; - export type public_PostTag = { - postId: CodecTypes['pg/uuid@1']['output']; - tagId: CodecTypes['pg/uuid@1']['output']; - post: public_Post; - tag: public_Tag; - readonly [RelationKeys]?: 'post' | 'tag'; - }; - export type public_Task = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - description: CodecTypes['pg/text@1']['output'] | null; - status: CodecTypes['pg/text@1']['output']; - type: 'bug' | 'feature'; - userId: CodecTypes['pg/uuid@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - user: public_User; - readonly [RelationKeys]?: 'user'; - }; - export type public_Bug = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - description: CodecTypes['pg/text@1']['output'] | null; - status: CodecTypes['pg/text@1']['output']; - type: 'bug'; - userId: CodecTypes['pg/uuid@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - severity: CodecTypes['pg/text@1']['output']; - stepsToRepro: CodecTypes['pg/text@1']['output'] | null; - user: public_User; - readonly [RelationKeys]?: 'user'; - }; - export type public_Feature = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - description: CodecTypes['pg/text@1']['output'] | null; - status: CodecTypes['pg/text@1']['output']; - type: 'feature'; - userId: CodecTypes['pg/uuid@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - priority: CodecTypes['pg/text@1']['output']; - targetRelease: CodecTypes['pg/text@1']['output'] | null; - user: public_User; - readonly [RelationKeys]?: 'user'; - }; - export type public_AnyTask = public_Bug | public_Feature; -} - -export declare const models: { - public: { - User: Models.public_User; - Post: Models.public_Post; - Tag: Models.public_Tag; - PostTag: Models.public_PostTag; - Task: Models.public_Task; - Bug: Models.public_Bug; - Feature: Models.public_Feature; - AnyTask: Models.public_AnyTask; - }; -}; - -export type TypeMaps = TypeMapsType< - CodecTypes, - QueryOperationTypes, - FieldOutputTypes, - FieldInputTypes, - StorageColumnTypes, - StorageColumnInputTypes, - AggregateTypes ->; - -type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly public: { - readonly id: 'public'; - readonly kind: 'postgres-schema'; - readonly entries: { - readonly table: { - readonly bug: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly severity: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly stepsToRepro: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'bug'; - readonly columns: readonly ['id']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly feature: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly priority: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly targetRelease: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'feature'; - readonly columns: readonly ['id']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly post: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly title: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly priority: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'low'>; - }; - }; - readonly createdAt: { - readonly many: false; - readonly dataType: 'pg/timestamptz'; - readonly codecId: 'pg/timestamptz-temporal@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly embedding: { - readonly many: false; - readonly dataType: 'pgvector/vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 1536 }; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly name: 'post_userId_idx_a489d58a'; - readonly prefix: 'post_userId_idx'; - readonly columns: readonly ['userId']; - readonly unique: false; - }, - ]; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly post_tag: { - columns: { - readonly postId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly tagId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - }; - primaryKey: { readonly columns: readonly ['postId', 'tagId'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly name: 'post_tag_postId_idx_a7a72715'; - readonly prefix: 'post_tag_postId_idx'; - readonly columns: readonly ['postId']; - readonly unique: false; - }, - { - readonly name: 'post_tag_tagId_idx_86854244'; - readonly prefix: 'post_tag_tagId_idx'; - readonly columns: readonly ['tagId']; - readonly unique: false; - }, - ]; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post_tag'; - readonly columns: readonly ['postId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['id']; - }; - }, - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post_tag'; - readonly columns: readonly ['tagId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'tag'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly tag: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly label: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['label'] }]; - indexes: readonly []; - foreignKeys: readonly []; - }; - readonly task: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly title: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly description: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly status: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; - }; - }; - readonly type: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly many: false; - readonly dataType: 'pg/timestamptz'; - readonly codecId: 'pg/timestamptz-temporal@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly name: 'task_userId_idx_a489d58a'; - readonly prefix: 'task_userId_idx'; - readonly columns: readonly ['userId']; - readonly unique: false; - }, - ]; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly user: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly email: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly displayName: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly many: false; - readonly dataType: 'pg/timestamptz'; - readonly codecId: 'pg/timestamptz-temporal@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly kind: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly address: { - readonly many: false; - readonly dataType: 'pg/jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; - }; - }; - readonly valueSet: { - readonly Priority: { - readonly kind: 'valueSet'; - readonly values: readonly ['low', 'high', 'urgent']; - }; - readonly user_type: { - readonly kind: 'valueSet'; - readonly values: readonly ['admin', 'user']; - }; - }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, - 'roots' | 'domain' -> & { - readonly target: 'postgres'; - readonly targetFamily: 'sql'; - readonly roots: { - readonly user: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; - readonly post: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; - readonly post_tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'PostTag' }; - readonly task: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - }; - readonly domain: { - readonly namespaces: { - readonly public: { - readonly models: { - readonly Bug: { - readonly fields: { - readonly severity: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly stepsToRepro: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'bug'; - readonly namespaceId: 'public'; - readonly fields: { - readonly severity: { readonly column: 'severity' }; - readonly stepsToRepro: { readonly column: 'stepsToRepro' }; - }; - }; - readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - }; - readonly Feature: { - readonly fields: { - readonly priority: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly targetRelease: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'feature'; - readonly namespaceId: 'public'; - readonly fields: { - readonly priority: { readonly column: 'priority' }; - readonly targetRelease: { readonly column: 'targetRelease' }; - }; - }; - readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - }; - readonly Post: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly title: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly priority: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { - readonly kind: 'scalar'; - readonly codecId: 'pg/timestamptz-temporal@1'; - }; - }; - readonly embedding: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/vector@1' }; - }; - }; - readonly relations: { - readonly tags: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; - readonly cardinality: 'N:M'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['postId']; - }; - readonly through: { - readonly table: 'post_tag'; - readonly namespaceId: 'public'; - readonly parentColumns: readonly ['postId']; - readonly childColumns: readonly ['tagId']; - readonly targetColumns: readonly ['id']; - }; - }; - readonly user: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'post'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly title: { readonly column: 'title' }; - readonly userId: { readonly column: 'userId' }; - readonly priority: { readonly column: 'priority' }; - readonly createdAt: { readonly column: 'createdAt' }; - readonly embedding: { readonly column: 'embedding' }; - }; - }; - }; - readonly PostTag: { - readonly fields: { - readonly postId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly tagId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - }; - readonly relations: { - readonly post: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['postId']; - readonly targetFields: readonly ['id']; - }; - }; - readonly tag: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['tagId']; - readonly targetFields: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'post_tag'; - readonly namespaceId: 'public'; - readonly fields: { - readonly postId: { readonly column: 'postId' }; - readonly tagId: { readonly column: 'tagId' }; - }; - }; - }; - readonly Tag: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly label: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: { - readonly posts: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly cardinality: 'N:M'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['tagId']; - }; - readonly through: { - readonly table: 'post_tag'; - readonly namespaceId: 'public'; - readonly parentColumns: readonly ['tagId']; - readonly childColumns: readonly ['postId']; - readonly targetColumns: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'tag'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly label: { readonly column: 'label' }; - }; - }; - }; - readonly Task: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly title: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly description: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { - readonly kind: 'scalar'; - readonly codecId: 'pg/timestamptz-temporal@1'; - }; - }; - }; - readonly relations: { - readonly user: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'task'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly title: { readonly column: 'title' }; - readonly description: { readonly column: 'description' }; - readonly status: { readonly column: 'status' }; - readonly type: { readonly column: 'type' }; - readonly userId: { readonly column: 'userId' }; - readonly createdAt: { readonly column: 'createdAt' }; - }; - }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly Bug: { readonly value: 'bug' }; - readonly Feature: { readonly value: 'feature' }; - }; - }; - readonly User: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly displayName: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { - readonly kind: 'scalar'; - readonly codecId: 'pg/timestamptz-temporal@1'; - }; - }; - readonly kind: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly address: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; - }; - }; - readonly relations: { - readonly posts: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['userId']; - }; - }; - readonly tasks: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['userId']; - }; - }; - }; - readonly storage: { - readonly table: 'user'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly email: { readonly column: 'email' }; - readonly displayName: { readonly column: 'displayName' }; - readonly createdAt: { readonly column: 'createdAt' }; - readonly kind: { readonly column: 'kind' }; - readonly address: { readonly column: 'address' }; - }; - }; - }; - }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; - readonly enum: { - readonly user_type: { - readonly codecId: 'pg/text@1'; - readonly members: readonly [ - { readonly name: 'admin'; readonly value: 'admin' }, - { readonly name: 'user'; readonly value: 'user' }, - ]; - }; - readonly Priority: { - readonly codecId: 'pg/text@1'; - readonly members: readonly [ - { readonly name: 'Low'; readonly value: 'low' }, - { readonly name: 'High'; readonly value: 'high' }, - { readonly name: 'Urgent'; readonly value: 'urgent' }, - ]; - }; - }; - }; - }; - }; - readonly capabilities: { - readonly postgres: { - readonly distinctOn: true; - readonly jsonAgg: true; - readonly lateral: true; - readonly limit: true; - readonly orderBy: true; - readonly 'pgvector.cosine': true; - readonly returning: true; - }; - readonly sql: { - readonly checkConstraint: true; - readonly defaultInInsert: true; - readonly enums: true; - readonly lateral: true; - readonly returning: true; - readonly scalarList: true; - }; - }; - readonly extensions: { - readonly 'demo/engagement-stats': { - readonly familyId: 'sql'; - readonly id: 'demo/engagement-stats'; - readonly kind: 'extension'; - readonly targetId: 'postgres'; - readonly types: { - readonly aggregateDescriptors: readonly [ - { - readonly input: { readonly kind: 'trait'; readonly trait: 'numeric' }; - readonly lower: unknown; - readonly nullable: true; - readonly operation: 'stddev'; - readonly output: { readonly codecId: 'pg/numeric@1'; readonly kind: 'codec' }; - }, - ]; - }; - readonly version: '0.0.1'; - }; - readonly pgvector: { - readonly capabilities: { readonly postgres: { readonly 'pgvector.cosine': true } }; - readonly familyId: 'sql'; - readonly id: 'pgvector'; - readonly kind: 'extension'; - readonly targetId: 'postgres'; - readonly types: { - readonly codecTypes: { - readonly import: { - readonly alias: 'PgVectorTypes'; - readonly named: 'CodecTypes'; - readonly package: '@internal/extension-pgvector/codec-types'; - }; - readonly typeImports: readonly [ - { - readonly alias: 'Vector'; - readonly named: 'Vector'; - readonly package: '@internal/extension-pgvector/codec-types'; - }, - ]; - }; - readonly operationTypes: { - readonly import: { - readonly alias: 'PgVectorOperationTypes'; - readonly named: 'OperationTypes'; - readonly package: '@internal/extension-pgvector/operation-types'; - }; - }; - readonly queryOperationTypes: { - readonly import: { - readonly alias: 'PgVectorQueryOperationTypes'; - readonly named: 'QueryOperationTypes'; - readonly package: '@internal/extension-pgvector/operation-types'; - }; - }; - }; - readonly version: '0.0.1'; - }; - }; - readonly execution: { - readonly executionHash: ExecutionHash; - readonly mutations: { - readonly defaults: readonly [ - { - readonly ref: { - readonly entry: 'post'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - { - readonly ref: { - readonly entry: 'tag'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - { - readonly ref: { - readonly entry: 'task'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - { - readonly ref: { - readonly entry: 'user'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - ]; - }; - }; - readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; - readonly profileHash: ProfileHash; -}; - -export type Contract = ContractWithTypeMaps; - -export type Namespaces = Contract['storage']['namespaces']; diff --git a/examples/prisma-8-demo/migrations/snapshots/9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e/contract.json b/examples/prisma-8-demo/migrations/snapshots/9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e/contract.json deleted file mode 100644 index a3c017fa8505..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"lateral":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/text@1","members":[{"name":"Low","value":"low"},{"name":"High","value":"high"},{"name":"Urgent","value":"urgent"}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"id":{"column":"id"},"priority":{"column":"priority"},"title":{"column":"title"},"userId":{"column":"userId"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"Bug":{"value":"bug"},"Feature":{"value":"feature"}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}}}}},"execution":{"executionHash":"e592be8e097b3e2b4958da0b3ed54265d1bfa0523295fa03e03af2364cccbd08","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"typeImports":[{"alias":"Vector","named":"Vector","package":"@internal/extension-pgvector/codec-types"}]},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN ('low', 'high', 'urgent')","name":"post_priority_check_09b41aa0","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"low"},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueSet":{"Priority":{"kind":"valueSet","values":["low","high","urgent"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"9fc3736e0efd980892a19f4d704ca30bcd0de243080e78ed1279b1f3a50f9a7e"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a/contract.d.ts deleted file mode 100644 index b894bf47069b..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a/contract.d.ts +++ /dev/null @@ -1,1491 +0,0 @@ -// ⚠️ GENERATED FILE - DO NOT EDIT -// This file is automatically generated by 'prisma contract emit'. -// To regenerate, run: prisma contract emit -import type { - CodecTypes as PgVectorTypes, - Vector, -} from '@prisma/orm-extension-pgvector/codec-types'; -import type { QueryOperationTypes as PgVectorQueryOperationTypes } from '@prisma/orm-extension-pgvector/operation-types'; -import type { QueryOperationTypes as PgAdapterQueryOps } from '@prisma/orm-postgres/adapter/operation-types'; -import type { - Bit, - Char, - CodecTypes as PgTypes, - JsonValue, - Numeric, - TimeString, - TimestampString, - TimestamptzString, - Timetz, - VarBit, - Varchar, -} from '@prisma/orm-postgres/target/codec-types'; - -import type { - ContractWithTypeMaps, - RelationKeys, - TypeMaps as TypeMapsType, -} from '@prisma/orm-postgres/family-contract/types'; -import type { - Contract as ContractType, - ExecutionHashBase, - NamespaceId, - ProfileHashBase, - StorageHashBase, -} from '@prisma/orm-postgres/contract/types'; - -export type StorageHash = - StorageHashBase<'a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a'>; -export type ExecutionHash = - ExecutionHashBase<'e592be8e097b3e2b4958da0b3ed54265d1bfa0523295fa03e03af2364cccbd08'>; -export type ProfileHash = - ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; - -export type CodecTypes = PgTypes & PgVectorTypes; -export type LaneCodecTypes = CodecTypes; -export type QueryOperationTypes = PgAdapterQueryOps & - PgVectorQueryOperationTypes; -export type AggregateTypes = { - readonly avg: { - readonly byCodec: { - readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - }; - }; - readonly avgDecimal: { - readonly byCodec: { - readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - }; - }; - readonly count: { - readonly byCodec: {}; - readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; - readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; - }; - readonly countBigInt: { - readonly byCodec: {}; - readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; - readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; - }; - readonly max: { - readonly byCodec: { - readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; - readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; - readonly 'pg/date-temporal@1': { - readonly output: 'pg/date-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; - readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; - readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { - readonly output: 'pg/time-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-string@1': { - readonly output: 'pg/timestamp-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-temporal@1': { - readonly output: 'pg/timestamp-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-string@1': { - readonly output: 'pg/timestamptz-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-temporal@1': { - readonly output: 'pg/timestamptz-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; - readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; - readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - }; - }; - readonly min: { - readonly byCodec: { - readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; - readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; - readonly 'pg/date-temporal@1': { - readonly output: 'pg/date-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; - readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; - readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { - readonly output: 'pg/time-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-string@1': { - readonly output: 'pg/timestamp-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-temporal@1': { - readonly output: 'pg/timestamp-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-string@1': { - readonly output: 'pg/timestamptz-string@1'; - readonly nullable: true; - }; - readonly 'pg/timestamptz-temporal@1': { - readonly output: 'pg/timestamptz-temporal@1'; - readonly nullable: true; - }; - readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; - readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; - readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; - }; - }; - readonly stddev: { - readonly byCodec: { - readonly 'pg/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'sql/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - }; - }; - readonly sum: { - readonly byCodec: { - readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; - readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; - readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - }; - }; - readonly sumBigInt: { - readonly byCodec: { - readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; - readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; - readonly 'pg/unboundedint@1': { - readonly output: 'pg/unboundedint@1'; - readonly nullable: true; - }; - readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; - }; - }; -}; -type DefaultLiteralValue = CodecId extends keyof CodecTypes - ? Encoded extends CodecTypes[CodecId]['json'] - ? Encoded - : CodecTypes[CodecId]['json'] - : Encoded; -export type AddressOutput = { - readonly street: CodecTypes['pg/text@1']['output']; - readonly city: CodecTypes['pg/text@1']['output']; - readonly zip: CodecTypes['pg/text@1']['output'] | null; - readonly country: CodecTypes['pg/text@1']['output']; -}; -export type AddressInput = { - readonly street: CodecTypes['pg/text@1']['input']; - readonly city: CodecTypes['pg/text@1']['input']; - readonly zip: CodecTypes['pg/text@1']['input'] | null; - readonly country: CodecTypes['pg/text@1']['input']; -}; -export type FieldOutputTypes = { - readonly public: { - readonly Bug: { - readonly severity: CodecTypes['pg/text@1']['output']; - readonly stepsToRepro: CodecTypes['pg/text@1']['output'] | null; - }; - readonly Feature: { - readonly priority: CodecTypes['pg/text@1']['output']; - readonly targetRelease: CodecTypes['pg/text@1']['output'] | null; - }; - readonly Post: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly title: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - readonly priority: 'low' | 'high' | 'urgent'; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly embedding: Vector<1536> | null; - readonly viewCount: CodecTypes['pg/int8number@1']['output'] | null; - readonly impressionCount: CodecTypes['pg/int8@1']['output'] | null; - readonly reachScore: CodecTypes['pg/unboundedint@1']['output'] | null; - }; - readonly PostTag: { - readonly postId: CodecTypes['pg/uuid@1']['output']; - readonly tagId: CodecTypes['pg/uuid@1']['output']; - }; - readonly Tag: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly label: CodecTypes['pg/text@1']['output']; - }; - readonly Task: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly title: CodecTypes['pg/text@1']['output']; - readonly description: CodecTypes['pg/text@1']['output'] | null; - readonly status: CodecTypes['pg/text@1']['output']; - readonly type: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - }; - readonly User: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly email: CodecTypes['pg/text@1']['output']; - readonly displayName: CodecTypes['pg/text@1']['output']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly kind: 'admin' | 'user'; - readonly address: AddressOutput | null; - }; - }; -}; -export type FieldInputTypes = { - readonly public: { - readonly Bug: { - readonly severity: CodecTypes['pg/text@1']['input']; - readonly stepsToRepro: CodecTypes['pg/text@1']['input'] | null; - }; - readonly Feature: { - readonly priority: CodecTypes['pg/text@1']['input']; - readonly targetRelease: CodecTypes['pg/text@1']['input'] | null; - }; - readonly Post: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly title: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - readonly priority: 'low' | 'high' | 'urgent'; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly embedding: CodecTypes['pg/vector@1']['input'] | null; - readonly viewCount: CodecTypes['pg/int8number@1']['input'] | null; - readonly impressionCount: CodecTypes['pg/int8@1']['input'] | null; - readonly reachScore: CodecTypes['pg/unboundedint@1']['input'] | null; - }; - readonly PostTag: { - readonly postId: CodecTypes['pg/uuid@1']['input']; - readonly tagId: CodecTypes['pg/uuid@1']['input']; - }; - readonly Tag: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly label: CodecTypes['pg/text@1']['input']; - }; - readonly Task: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly title: CodecTypes['pg/text@1']['input']; - readonly description: CodecTypes['pg/text@1']['input'] | null; - readonly status: CodecTypes['pg/text@1']['input']; - readonly type: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - }; - readonly User: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly email: CodecTypes['pg/text@1']['input']; - readonly displayName: CodecTypes['pg/text@1']['input']; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly kind: 'admin' | 'user'; - readonly address: AddressInput | null; - }; - }; -}; -export type StorageColumnTypes = { - readonly public: { - readonly bug: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly severity: CodecTypes['pg/text@1']['output']; - readonly stepsToRepro: CodecTypes['pg/text@1']['output'] | null; - }; - readonly feature: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly priority: CodecTypes['pg/text@1']['output']; - readonly targetRelease: CodecTypes['pg/text@1']['output'] | null; - }; - readonly post: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly embedding: Vector<1536> | null; - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly impressionCount: CodecTypes['pg/int8@1']['output'] | null; - readonly priority: 'low' | 'high' | 'urgent'; - readonly reachScore: CodecTypes['pg/unboundedint@1']['output'] | null; - readonly title: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - readonly viewCount: CodecTypes['pg/int8number@1']['output'] | null; - }; - readonly post_tag: { - readonly postId: CodecTypes['pg/uuid@1']['output']; - readonly tagId: CodecTypes['pg/uuid@1']['output']; - }; - readonly tag: { - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly label: CodecTypes['pg/text@1']['output']; - }; - readonly task: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly description: CodecTypes['pg/text@1']['output'] | null; - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly status: CodecTypes['pg/text@1']['output']; - readonly title: CodecTypes['pg/text@1']['output']; - readonly type: CodecTypes['pg/text@1']['output']; - readonly userId: CodecTypes['pg/uuid@1']['output']; - }; - readonly user: { - readonly address: CodecTypes['pg/jsonb@1']['output'] | null; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - readonly displayName: CodecTypes['pg/text@1']['output']; - readonly email: CodecTypes['pg/text@1']['output']; - readonly id: CodecTypes['pg/uuid@1']['output']; - readonly kind: 'admin' | 'user'; - }; - }; -}; -export type StorageColumnInputTypes = { - readonly public: { - readonly bug: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly severity: CodecTypes['pg/text@1']['input']; - readonly stepsToRepro: CodecTypes['pg/text@1']['input'] | null; - }; - readonly feature: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly priority: CodecTypes['pg/text@1']['input']; - readonly targetRelease: CodecTypes['pg/text@1']['input'] | null; - }; - readonly post: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly embedding: CodecTypes['pg/vector@1']['input'] | null; - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly impressionCount: CodecTypes['pg/int8@1']['input'] | null; - readonly priority: 'low' | 'high' | 'urgent'; - readonly reachScore: CodecTypes['pg/unboundedint@1']['input'] | null; - readonly title: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - readonly viewCount: CodecTypes['pg/int8number@1']['input'] | null; - }; - readonly post_tag: { - readonly postId: CodecTypes['pg/uuid@1']['input']; - readonly tagId: CodecTypes['pg/uuid@1']['input']; - }; - readonly tag: { - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly label: CodecTypes['pg/text@1']['input']; - }; - readonly task: { - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly description: CodecTypes['pg/text@1']['input'] | null; - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly status: CodecTypes['pg/text@1']['input']; - readonly title: CodecTypes['pg/text@1']['input']; - readonly type: CodecTypes['pg/text@1']['input']; - readonly userId: CodecTypes['pg/uuid@1']['input']; - }; - readonly user: { - readonly address: CodecTypes['pg/jsonb@1']['input'] | null; - readonly createdAt: CodecTypes['pg/timestamptz-temporal@1']['input']; - readonly displayName: CodecTypes['pg/text@1']['input']; - readonly email: CodecTypes['pg/text@1']['input']; - readonly id: CodecTypes['pg/uuid@1']['input']; - readonly kind: 'admin' | 'user'; - }; - }; -}; - -export namespace Models { - export type public_User = { - id: CodecTypes['pg/uuid@1']['output']; - email: CodecTypes['pg/text@1']['output']; - displayName: CodecTypes['pg/text@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - kind: 'admin' | 'user'; - address: AddressOutput | null; - posts: public_Post[]; - tasks: public_AnyTask[]; - readonly [RelationKeys]?: 'posts' | 'tasks'; - }; - export type public_Post = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - userId: CodecTypes['pg/uuid@1']['output']; - priority: 'low' | 'high' | 'urgent'; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - embedding: Vector<1536> | null; - viewCount: CodecTypes['pg/int8number@1']['output'] | null; - impressionCount: CodecTypes['pg/int8@1']['output'] | null; - reachScore: CodecTypes['pg/unboundedint@1']['output'] | null; - tags: public_Tag[]; - user: public_User; - readonly [RelationKeys]?: 'tags' | 'user'; - }; - export type public_Tag = { - id: CodecTypes['pg/uuid@1']['output']; - label: CodecTypes['pg/text@1']['output']; - posts: public_Post[]; - readonly [RelationKeys]?: 'posts'; - }; - export type public_PostTag = { - postId: CodecTypes['pg/uuid@1']['output']; - tagId: CodecTypes['pg/uuid@1']['output']; - post: public_Post; - tag: public_Tag; - readonly [RelationKeys]?: 'post' | 'tag'; - }; - export type public_Task = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - description: CodecTypes['pg/text@1']['output'] | null; - status: CodecTypes['pg/text@1']['output']; - type: 'bug' | 'feature'; - userId: CodecTypes['pg/uuid@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - user: public_User; - readonly [RelationKeys]?: 'user'; - }; - export type public_Bug = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - description: CodecTypes['pg/text@1']['output'] | null; - status: CodecTypes['pg/text@1']['output']; - type: 'bug'; - userId: CodecTypes['pg/uuid@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - severity: CodecTypes['pg/text@1']['output']; - stepsToRepro: CodecTypes['pg/text@1']['output'] | null; - user: public_User; - readonly [RelationKeys]?: 'user'; - }; - export type public_Feature = { - id: CodecTypes['pg/uuid@1']['output']; - title: CodecTypes['pg/text@1']['output']; - description: CodecTypes['pg/text@1']['output'] | null; - status: CodecTypes['pg/text@1']['output']; - type: 'feature'; - userId: CodecTypes['pg/uuid@1']['output']; - createdAt: CodecTypes['pg/timestamptz-temporal@1']['output']; - priority: CodecTypes['pg/text@1']['output']; - targetRelease: CodecTypes['pg/text@1']['output'] | null; - user: public_User; - readonly [RelationKeys]?: 'user'; - }; - export type public_AnyTask = public_Bug | public_Feature; -} - -export declare const models: { - public: { - User: Models.public_User; - Post: Models.public_Post; - Tag: Models.public_Tag; - PostTag: Models.public_PostTag; - Task: Models.public_Task; - Bug: Models.public_Bug; - Feature: Models.public_Feature; - AnyTask: Models.public_AnyTask; - }; -}; - -export type TypeMaps = TypeMapsType< - CodecTypes, - QueryOperationTypes, - FieldOutputTypes, - FieldInputTypes, - StorageColumnTypes, - StorageColumnInputTypes, - AggregateTypes ->; - -type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly public: { - readonly id: 'public'; - readonly kind: 'postgres-schema'; - readonly entries: { - readonly table: { - readonly bug: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly severity: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly stepsToRepro: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'bug'; - readonly columns: readonly ['id']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly feature: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly priority: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly targetRelease: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'feature'; - readonly columns: readonly ['id']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly post: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly title: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly priority: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'low'>; - }; - }; - readonly createdAt: { - readonly many: false; - readonly dataType: 'pg/timestamptz'; - readonly codecId: 'pg/timestamptz-temporal@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly embedding: { - readonly many: false; - readonly dataType: 'pgvector/vector'; - readonly codecId: 'pg/vector@1'; - readonly nullable: true; - readonly typeParams: { readonly length: 1536 }; - }; - readonly viewCount: { - readonly many: false; - readonly dataType: 'pg/int8'; - readonly codecId: 'pg/int8number@1'; - readonly nullable: true; - }; - readonly impressionCount: { - readonly many: false; - readonly dataType: 'pg/int8'; - readonly codecId: 'pg/int8@1'; - readonly nullable: true; - }; - readonly reachScore: { - readonly many: false; - readonly dataType: 'pg/numeric'; - readonly codecId: 'pg/unboundedint@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly name: 'post_userId_idx_a489d58a'; - readonly prefix: 'post_userId_idx'; - readonly columns: readonly ['userId']; - readonly unique: false; - }, - ]; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly post_tag: { - columns: { - readonly postId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly tagId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - }; - primaryKey: { readonly columns: readonly ['postId', 'tagId'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly name: 'post_tag_postId_idx_a7a72715'; - readonly prefix: 'post_tag_postId_idx'; - readonly columns: readonly ['postId']; - readonly unique: false; - }, - { - readonly name: 'post_tag_tagId_idx_86854244'; - readonly prefix: 'post_tag_tagId_idx'; - readonly columns: readonly ['tagId']; - readonly unique: false; - }, - ]; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post_tag'; - readonly columns: readonly ['postId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post'; - readonly columns: readonly ['id']; - }; - }, - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'post_tag'; - readonly columns: readonly ['tagId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'tag'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly tag: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly label: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly [{ readonly columns: readonly ['label'] }]; - indexes: readonly []; - foreignKeys: readonly []; - }; - readonly task: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly title: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly description: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: true; - }; - readonly status: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - readonly default: { - readonly kind: 'literal'; - readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; - }; - }; - readonly type: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly userId: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly many: false; - readonly dataType: 'pg/timestamptz'; - readonly codecId: 'pg/timestamptz-temporal@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly [ - { - readonly name: 'task_userId_idx_a489d58a'; - readonly prefix: 'task_userId_idx'; - readonly columns: readonly ['userId']; - readonly unique: false; - }, - ]; - foreignKeys: readonly [ - { - readonly source: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'task'; - readonly columns: readonly ['userId']; - }; - readonly target: { - readonly namespaceId: 'public' & NamespaceId; - readonly tableName: 'user'; - readonly columns: readonly ['id']; - }; - }, - ]; - }; - readonly user: { - columns: { - readonly id: { - readonly many: false; - readonly dataType: 'pg/uuid'; - readonly codecId: 'pg/uuid@1'; - readonly nullable: false; - }; - readonly email: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly displayName: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly createdAt: { - readonly many: false; - readonly dataType: 'pg/timestamptz'; - readonly codecId: 'pg/timestamptz-temporal@1'; - readonly nullable: false; - readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; - }; - readonly kind: { - readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; - }; - readonly address: { - readonly many: false; - readonly dataType: 'pg/jsonb'; - readonly codecId: 'pg/jsonb@1'; - readonly nullable: true; - }; - }; - primaryKey: { readonly columns: readonly ['id'] }; - uniques: readonly []; - indexes: readonly []; - foreignKeys: readonly []; - }; - }; - readonly valueSet: { - readonly Priority: { - readonly kind: 'valueSet'; - readonly values: readonly ['low', 'high', 'urgent']; - }; - readonly user_type: { - readonly kind: 'valueSet'; - readonly values: readonly ['admin', 'user']; - }; - }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, - 'roots' | 'domain' -> & { - readonly target: 'postgres'; - readonly targetFamily: 'sql'; - readonly roots: { - readonly user: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; - readonly post: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; - readonly post_tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'PostTag' }; - readonly task: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - }; - readonly domain: { - readonly namespaces: { - readonly public: { - readonly models: { - readonly Bug: { - readonly fields: { - readonly severity: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly stepsToRepro: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'bug'; - readonly namespaceId: 'public'; - readonly fields: { - readonly severity: { readonly column: 'severity' }; - readonly stepsToRepro: { readonly column: 'stepsToRepro' }; - }; - }; - readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - }; - readonly Feature: { - readonly fields: { - readonly priority: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly targetRelease: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: Record; - readonly storage: { - readonly table: 'feature'; - readonly namespaceId: 'public'; - readonly fields: { - readonly priority: { readonly column: 'priority' }; - readonly targetRelease: { readonly column: 'targetRelease' }; - }; - }; - readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - }; - readonly Post: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly title: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly priority: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { - readonly kind: 'scalar'; - readonly codecId: 'pg/timestamptz-temporal@1'; - }; - }; - readonly embedding: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/vector@1' }; - }; - readonly viewCount: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int8number@1' }; - }; - readonly impressionCount: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int8@1' }; - }; - readonly reachScore: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/unboundedint@1' }; - }; - }; - readonly relations: { - readonly tags: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; - readonly cardinality: 'N:M'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['postId']; - }; - readonly through: { - readonly table: 'post_tag'; - readonly namespaceId: 'public'; - readonly parentColumns: readonly ['postId']; - readonly childColumns: readonly ['tagId']; - readonly targetColumns: readonly ['id']; - }; - }; - readonly user: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'post'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly title: { readonly column: 'title' }; - readonly userId: { readonly column: 'userId' }; - readonly priority: { readonly column: 'priority' }; - readonly createdAt: { readonly column: 'createdAt' }; - readonly embedding: { readonly column: 'embedding' }; - readonly viewCount: { readonly column: 'viewCount' }; - readonly impressionCount: { readonly column: 'impressionCount' }; - readonly reachScore: { readonly column: 'reachScore' }; - }; - }; - }; - readonly PostTag: { - readonly fields: { - readonly postId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly tagId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - }; - readonly relations: { - readonly post: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['postId']; - readonly targetFields: readonly ['id']; - }; - }; - readonly tag: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['tagId']; - readonly targetFields: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'post_tag'; - readonly namespaceId: 'public'; - readonly fields: { - readonly postId: { readonly column: 'postId' }; - readonly tagId: { readonly column: 'tagId' }; - }; - }; - }; - readonly Tag: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly label: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - readonly relations: { - readonly posts: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly cardinality: 'N:M'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['tagId']; - }; - readonly through: { - readonly table: 'post_tag'; - readonly namespaceId: 'public'; - readonly parentColumns: readonly ['tagId']; - readonly childColumns: readonly ['postId']; - readonly targetColumns: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'tag'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly label: { readonly column: 'label' }; - }; - }; - }; - readonly Task: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly title: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly description: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { - readonly kind: 'scalar'; - readonly codecId: 'pg/timestamptz-temporal@1'; - }; - }; - }; - readonly relations: { - readonly user: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['id']; - }; - }; - }; - readonly storage: { - readonly table: 'task'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly title: { readonly column: 'title' }; - readonly description: { readonly column: 'description' }; - readonly status: { readonly column: 'status' }; - readonly type: { readonly column: 'type' }; - readonly userId: { readonly column: 'userId' }; - readonly createdAt: { readonly column: 'createdAt' }; - }; - }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly Bug: { readonly value: 'bug' }; - readonly Feature: { readonly value: 'feature' }; - }; - }; - readonly User: { - readonly fields: { - readonly id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly displayName: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { - readonly kind: 'scalar'; - readonly codecId: 'pg/timestamptz-temporal@1'; - }; - }; - readonly kind: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly address: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; - }; - }; - readonly relations: { - readonly posts: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['userId']; - }; - }; - readonly tasks: { - readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['id']; - readonly targetFields: readonly ['userId']; - }; - }; - }; - readonly storage: { - readonly table: 'user'; - readonly namespaceId: 'public'; - readonly fields: { - readonly id: { readonly column: 'id' }; - readonly email: { readonly column: 'email' }; - readonly displayName: { readonly column: 'displayName' }; - readonly createdAt: { readonly column: 'createdAt' }; - readonly kind: { readonly column: 'kind' }; - readonly address: { readonly column: 'address' }; - }; - }; - }; - }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; - readonly enum: { - readonly user_type: { - readonly codecId: 'pg/text@1'; - readonly members: readonly [ - { readonly name: 'admin'; readonly value: 'admin' }, - { readonly name: 'user'; readonly value: 'user' }, - ]; - }; - readonly Priority: { - readonly codecId: 'pg/text@1'; - readonly members: readonly [ - { readonly name: 'Low'; readonly value: 'low' }, - { readonly name: 'High'; readonly value: 'high' }, - { readonly name: 'Urgent'; readonly value: 'urgent' }, - ]; - }; - }; - }; - }; - }; - readonly capabilities: { - readonly postgres: { - readonly distinctOn: true; - readonly jsonAgg: true; - readonly lateral: true; - readonly limit: true; - readonly orderBy: true; - readonly 'pgvector.cosine': true; - readonly returning: true; - }; - readonly sql: { - readonly checkConstraint: true; - readonly defaultInInsert: true; - readonly enums: true; - readonly lateral: true; - readonly returning: true; - readonly scalarList: true; - }; - }; - readonly extensions: { - readonly 'demo/engagement-stats': { - readonly familyId: 'sql'; - readonly id: 'demo/engagement-stats'; - readonly kind: 'extension'; - readonly targetId: 'postgres'; - readonly types: { - readonly aggregateDescriptors: readonly [ - { - readonly input: { readonly kind: 'trait'; readonly trait: 'numeric' }; - readonly lower: unknown; - readonly nullable: true; - readonly operation: 'stddev'; - readonly output: { readonly codecId: 'pg/numeric@1'; readonly kind: 'codec' }; - }, - ]; - }; - readonly version: '0.0.1'; - }; - readonly pgvector: { - readonly capabilities: { readonly postgres: { readonly 'pgvector.cosine': true } }; - readonly familyId: 'sql'; - readonly id: 'pgvector'; - readonly kind: 'extension'; - readonly targetId: 'postgres'; - readonly types: { - readonly codecTypes: { - readonly import: { - readonly alias: 'PgVectorTypes'; - readonly named: 'CodecTypes'; - readonly package: '@internal/extension-pgvector/codec-types'; - }; - readonly typeImports: readonly [ - { - readonly alias: 'Vector'; - readonly named: 'Vector'; - readonly package: '@internal/extension-pgvector/codec-types'; - }, - ]; - }; - readonly operationTypes: { - readonly import: { - readonly alias: 'PgVectorOperationTypes'; - readonly named: 'OperationTypes'; - readonly package: '@internal/extension-pgvector/operation-types'; - }; - }; - readonly queryOperationTypes: { - readonly import: { - readonly alias: 'PgVectorQueryOperationTypes'; - readonly named: 'QueryOperationTypes'; - readonly package: '@internal/extension-pgvector/operation-types'; - }; - }; - }; - readonly version: '0.0.1'; - }; - }; - readonly execution: { - readonly executionHash: ExecutionHash; - readonly mutations: { - readonly defaults: readonly [ - { - readonly ref: { - readonly entry: 'post'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - { - readonly ref: { - readonly entry: 'tag'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - { - readonly ref: { - readonly entry: 'task'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - { - readonly ref: { - readonly entry: 'user'; - readonly field: 'id'; - readonly namespace: 'public'; - }; - readonly onCreate: { readonly kind: 'generator'; readonly id: 'uuidv4' }; - }, - ]; - }; - }; - readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; - readonly profileHash: ProfileHash; -}; - -export type Contract = ContractWithTypeMaps; - -export type Namespaces = Contract['storage']['namespaces']; diff --git a/examples/prisma-8-demo/migrations/snapshots/a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a/contract.json b/examples/prisma-8-demo/migrations/snapshots/a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a/contract.json deleted file mode 100644 index 705edd640296..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"lateral":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/text@1","members":[{"name":"Low","value":"low"},{"name":"High","value":"high"},{"name":"Urgent","value":"urgent"}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"Bug":{"value":"bug"},"Feature":{"value":"feature"}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}}}}},"execution":{"executionHash":"e592be8e097b3e2b4958da0b3ed54265d1bfa0523295fa03e03af2364cccbd08","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"typeImports":[{"alias":"Vector","named":"Vector","package":"@internal/extension-pgvector/codec-types"}]},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN ('low', 'high', 'urgent')","name":"post_priority_check_09b41aa0","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"low"},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueSet":{"Priority":{"kind":"valueSet","values":["low","high","urgent"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"a6e86cb536b51ac3b7542cb02dbf9f5004139e8dc5ee1b33a554c436d8cd2b2a"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.json b/examples/prisma-8-demo/migrations/snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.json deleted file mode 100644 index 133dfbc8f71f..000000000000 --- a/examples/prisma-8-demo/migrations/snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.d.ts similarity index 96% rename from examples/prisma-8-demo/migrations/snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.d.ts rename to examples/prisma-8-demo/migrations/snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.d.ts index 6f22860d05c2..c70d5c511845 100644 --- a/examples/prisma-8-demo/migrations/snapshots/d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0/contract.d.ts +++ b/examples/prisma-8-demo/migrations/snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.d.ts @@ -25,11 +25,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-postgres/contract/types'; export type StorageHash = - StorageHashBase<'d44ab6f7caddcc384332c669737008aec7ec8c9f5b013cb502a03c2eb04be9c0'>; + StorageHashBase<'db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e'>; export type ExecutionHash = ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; export type ProfileHash = @@ -293,15 +293,15 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -315,6 +315,9 @@ type FieldTypeOverrides = { readonly Post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; }; + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -326,18 +329,23 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly user: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -835,11 +843,37 @@ type ContractBase = Omit< }; readonly valueSet: { readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; readonly user_type: { readonly kind: 'valueSet'; readonly values: readonly ['admin', 'user']; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -1140,7 +1174,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly createdAt: { readonly nullable: false; @@ -1198,28 +1238,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly enum: { readonly Priority: { readonly codecId: 'pg/int4@1'; @@ -1375,28 +1393,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo/migrations/snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.json b/examples/prisma-8-demo/migrations/snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.json new file mode 100644 index 000000000000..e3befc2b1c54 --- /dev/null +++ b/examples/prisma-8-demo/migrations/snapshots/db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"public","plane":"storage"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"checks":[{"expression":"\"type\" IN ('bug', 'feature')","name":"task_type_check_cda3f5e7","prefix":"task_type_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Task.type","namespaceId":"public","plane":"storage"}},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE","name":"user_address_shape_69432dc1","prefix":"user_address_shape"},{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"Task.type":{"kind":"valueSet","values":["bug","feature"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"db733e1df89f66886f7dd6a2ea54fc5be48e65d81aadd193d1e7f220ccf14f5e"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.d.ts similarity index 96% rename from examples/prisma-8-demo/migrations/snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.d.ts rename to examples/prisma-8-demo/migrations/snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.d.ts index d180da08d5e9..f84d528883aa 100644 --- a/examples/prisma-8-demo/migrations/snapshots/0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a/contract.d.ts +++ b/examples/prisma-8-demo/migrations/snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.d.ts @@ -25,11 +25,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-postgres/contract/types'; export type StorageHash = - StorageHashBase<'0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a'>; + StorageHashBase<'e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf'>; export type ExecutionHash = ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; export type ProfileHash = @@ -293,15 +293,15 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -315,6 +315,9 @@ type FieldTypeOverrides = { readonly Post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; }; + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -326,18 +329,23 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly user: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -875,11 +883,37 @@ type ContractBase = Omit< }; readonly valueSet: { readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; readonly user_type: { readonly kind: 'valueSet'; readonly values: readonly ['admin', 'user']; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -1193,7 +1227,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly createdAt: { readonly nullable: false; @@ -1251,28 +1291,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly enum: { readonly Priority: { readonly codecId: 'pg/int4@1'; @@ -1428,28 +1446,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo/migrations/snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.json b/examples/prisma-8-demo/migrations/snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.json new file mode 100644 index 000000000000..12ad86c15dc9 --- /dev/null +++ b/examples/prisma-8-demo/migrations/snapshots/e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"body":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"expiresAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"impressionCount":{"nullable":true,"type":{"codecId":"pg/int8@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"reachScore":{"nullable":true,"type":{"codecId":"pg/unboundedint@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"viewCount":{"nullable":true,"type":{"codecId":"pg/int8number@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"body":{"column":"body"},"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"expiresAt":{"column":"expiresAt"},"id":{"column":"id"},"impressionCount":{"column":"impressionCount"},"priority":{"column":"priority"},"reachScore":{"column":"reachScore"},"title":{"column":"title"},"userId":{"column":"userId"},"viewCount":{"column":"viewCount"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"public","plane":"storage"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"body":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"expiresAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"(now() + '7 days'::interval)","kind":"function"},"nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"impressionCount":{"codecId":"pg/int8@1","dataType":"pg/int8","nullable":true},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"reachScore":{"codecId":"pg/unboundedint@1","dataType":"pg/numeric","nullable":true},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"viewCount":{"codecId":"pg/int8number@1","dataType":"pg/int8","nullable":true}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["title","body"],"name":"post_search_f03270be","options":{"language":"english","weightGroups":[["title"],["body"]]},"prefix":"post_search","type":"fullText","unique":false},{"columns":["title"],"name":"post_title_search_e0dd1131","options":{"language":"english","weightGroups":[["title"]]},"prefix":"post_title_search","type":"fullText","unique":false},{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"checks":[{"expression":"\"type\" IN ('bug', 'feature')","name":"task_type_check_cda3f5e7","prefix":"task_type_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Task.type","namespaceId":"public","plane":"storage"}},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE","name":"user_address_shape_69432dc1","prefix":"user_address_shape"},{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"Task.type":{"kind":"valueSet","values":["bug","feature"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/migrations/snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.d.ts b/examples/prisma-8-demo/migrations/snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.d.ts new file mode 100644 index 000000000000..2a5d5f3a2c78 --- /dev/null +++ b/examples/prisma-8-demo/migrations/snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.d.ts @@ -0,0 +1,1365 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as PgVectorTypes, + ParameterizedOutputTypesForTypeParams as PgVectorParameterizedOutputTypes, +} from '@prisma/orm-extension-pgvector/codec-types'; +import type { QueryOperationTypes as PgVectorQueryOperationTypes } from '@prisma/orm-extension-pgvector/operation-types'; +import type { + CodecTypes as PgTypes, + ParameterizedOutputTypesForTypeParams as PgParameterizedOutputTypes, +} from '@prisma/orm-postgres/target/codec-types'; +import type { QueryOperationTypes as PgTargetQueryOps } from '@prisma/orm-postgres/target/operation-types'; + +import type { + ColumnTypesFromStorage, + ContractWithTypeMaps, + RelationKeys, + TypeMaps as TypeMapsType, +} from '@prisma/orm-postgres/family-contract/types'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-postgres/contract/types'; + +export type StorageHash = + StorageHashBase<'fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263'>; +export type ExecutionHash = + ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; +export type ProfileHash = + ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; + +export type CodecTypes = PgTypes & PgVectorTypes; +export type LaneCodecTypes = CodecTypes; +export type QueryOperationTypes = PgTargetQueryOps & + PgVectorQueryOperationTypes; +export type AggregateTypes = { + readonly avg: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + }; + }; + readonly avgDecimal: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + }; + }; + readonly count: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + }; + readonly countBigInt: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + }; + readonly max: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly min: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly stddev: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + }; + }; + readonly sum: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + }; + }; + readonly sumBigInt: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + }; + }; +}; +type DefaultLiteralValue = CodecId extends keyof CodecTypes + ? Encoded extends CodecTypes[CodecId]['json'] + ? Encoded + : CodecTypes[CodecId]['json'] + : Encoded; +type ParameterizedOutputTypeFunctions = + PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['public']['Address']; +export type AddressInput = ValueObjectInputTypes['public']['Address']; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Post: { + readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; + }; + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; + readonly User: { + readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; +type ColumnTypeOverrides = { + readonly public: { + readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; + readonly user: { + readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; + }; + }; +}; +export type StorageColumnTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'output' +>; +export type StorageColumnInputTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'input' +>; + +export namespace Models { + export type public_Bug = { + createdAt: FieldOutputTypes['public']['Task']['createdAt']; + description: FieldOutputTypes['public']['Task']['description']; + id: FieldOutputTypes['public']['Task']['id']; + status: FieldOutputTypes['public']['Task']['status']; + title: FieldOutputTypes['public']['Task']['title']; + type: 'bug'; + userId: FieldOutputTypes['public']['Task']['userId']; + severity: FieldOutputTypes['public']['Bug']['severity']; + stepsToRepro: FieldOutputTypes['public']['Bug']['stepsToRepro']; + user: public_User; + readonly [RelationKeys]?: 'user'; + }; + export type public_Feature = { + createdAt: FieldOutputTypes['public']['Task']['createdAt']; + description: FieldOutputTypes['public']['Task']['description']; + id: FieldOutputTypes['public']['Task']['id']; + status: FieldOutputTypes['public']['Task']['status']; + title: FieldOutputTypes['public']['Task']['title']; + type: 'feature'; + userId: FieldOutputTypes['public']['Task']['userId']; + priority: FieldOutputTypes['public']['Feature']['priority']; + targetRelease: FieldOutputTypes['public']['Feature']['targetRelease']; + user: public_User; + readonly [RelationKeys]?: 'user'; + }; + export type public_Post = { + createdAt: FieldOutputTypes['public']['Post']['createdAt']; + embedding: FieldOutputTypes['public']['Post']['embedding']; + id: FieldOutputTypes['public']['Post']['id']; + priority: FieldOutputTypes['public']['Post']['priority']; + title: FieldOutputTypes['public']['Post']['title']; + userId: FieldOutputTypes['public']['Post']['userId']; + tags: public_Tag[]; + user: public_User; + readonly [RelationKeys]?: 'tags' | 'user'; + }; + export type public_PostTag = { + postId: FieldOutputTypes['public']['PostTag']['postId']; + tagId: FieldOutputTypes['public']['PostTag']['tagId']; + post: public_Post; + tag: public_Tag; + readonly [RelationKeys]?: 'post' | 'tag'; + }; + export type public_Tag = { + id: FieldOutputTypes['public']['Tag']['id']; + label: FieldOutputTypes['public']['Tag']['label']; + posts: public_Post[]; + readonly [RelationKeys]?: 'posts'; + }; + export type public_Task = { + createdAt: FieldOutputTypes['public']['Task']['createdAt']; + description: FieldOutputTypes['public']['Task']['description']; + id: FieldOutputTypes['public']['Task']['id']; + status: FieldOutputTypes['public']['Task']['status']; + title: FieldOutputTypes['public']['Task']['title']; + type: 'bug' | 'feature'; + userId: FieldOutputTypes['public']['Task']['userId']; + user: public_User; + readonly [RelationKeys]?: 'user'; + }; + export type public_User = { + address: FieldOutputTypes['public']['User']['address']; + createdAt: FieldOutputTypes['public']['User']['createdAt']; + displayName: FieldOutputTypes['public']['User']['displayName']; + email: FieldOutputTypes['public']['User']['email']; + id: FieldOutputTypes['public']['User']['id']; + kind: FieldOutputTypes['public']['User']['kind']; + posts: public_Post[]; + tasks: public_AnyTask[]; + readonly [RelationKeys]?: 'posts' | 'tasks'; + }; + export type public_AnyTask = public_Bug | public_Feature; +} + +export declare const models: { + public: { + Bug: Models.public_Bug; + Feature: Models.public_Feature; + Post: Models.public_Post; + PostTag: Models.public_PostTag; + Tag: Models.public_Tag; + Task: Models.public_Task; + User: Models.public_User; + AnyTask: Models.public_AnyTask; + }; +}; + +export type TypeMaps = TypeMapsType< + CodecTypes, + QueryOperationTypes, + FieldOutputTypes, + FieldInputTypes, + StorageColumnTypes, + StorageColumnInputTypes, + AggregateTypes +>; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly public: { + readonly id: 'public'; + readonly kind: 'postgres-schema'; + readonly entries: { + readonly table: { + readonly bug: { + columns: { + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly severity: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly stepsToRepro: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'bug'; + readonly columns: readonly ['id']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly feature: { + columns: { + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly priority: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly targetRelease: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'feature'; + readonly columns: readonly ['id']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly post: { + columns: { + readonly createdAt: { + readonly dataType: 'pg/timestamptz'; + readonly codecId: 'pg/timestamptz-temporal@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly many: false; + }; + readonly embedding: { + readonly dataType: 'pgvector/vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 1536 }; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly priority: { + readonly dataType: 'pg/int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/int4@1', 0>; + }; + readonly many: false; + }; + readonly title: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly userId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly name: 'post_userId_idx_a489d58a'; + readonly prefix: 'post_userId_idx'; + readonly columns: readonly ['userId']; + readonly unique: false; + }, + ]; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['userId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly post_tag: { + columns: { + readonly postId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly tagId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['postId', 'tagId'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly name: 'post_tag_postId_idx_a7a72715'; + readonly prefix: 'post_tag_postId_idx'; + readonly columns: readonly ['postId']; + readonly unique: false; + }, + { + readonly name: 'post_tag_tagId_idx_86854244'; + readonly prefix: 'post_tag_tagId_idx'; + readonly columns: readonly ['tagId']; + readonly unique: false; + }, + ]; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post_tag'; + readonly columns: readonly ['postId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['id']; + }; + }, + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post_tag'; + readonly columns: readonly ['tagId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'tag'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly tag: { + columns: { + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly label: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['label'] }]; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly task: { + columns: { + readonly createdAt: { + readonly dataType: 'pg/timestamptz'; + readonly codecId: 'pg/timestamptz-temporal@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly many: false; + }; + readonly description: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly status: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; + }; + readonly many: false; + }; + readonly title: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly type: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly userId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly name: 'task_userId_idx_a489d58a'; + readonly prefix: 'task_userId_idx'; + readonly columns: readonly ['userId']; + readonly unique: false; + }, + ]; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['userId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly user: { + columns: { + readonly address: { + readonly dataType: 'pg/jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; + readonly many: false; + }; + readonly createdAt: { + readonly dataType: 'pg/timestamptz'; + readonly codecId: 'pg/timestamptz-temporal@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly many: false; + }; + readonly displayName: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly email: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly kind: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + }; + readonly valueSet: { + readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; + readonly user_type: { + readonly kind: 'valueSet'; + readonly values: readonly ['admin', 'user']; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'postgres'; + readonly targetFamily: 'sql'; + readonly roots: { + readonly post: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly post_tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'PostTag' }; + readonly tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; + readonly task: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + readonly user: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly public: { + readonly models: { + readonly Bug: { + readonly fields: { + readonly severity: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly stepsToRepro: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'bug'; + readonly namespaceId: 'public'; + readonly fields: { + readonly severity: { readonly column: 'severity' }; + readonly stepsToRepro: { readonly column: 'stepsToRepro' }; + }; + }; + readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + }; + readonly Feature: { + readonly fields: { + readonly priority: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly targetRelease: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'feature'; + readonly namespaceId: 'public'; + readonly fields: { + readonly priority: { readonly column: 'priority' }; + readonly targetRelease: { readonly column: 'targetRelease' }; + }; + }; + readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + }; + readonly Post: { + readonly fields: { + readonly createdAt: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/timestamptz-temporal@1'; + }; + }; + readonly embedding: { + readonly nullable: true; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/vector@1'; + readonly typeParams: { readonly length: 1536 }; + }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly priority: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int4@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + }; + readonly relations: { + readonly tags: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; + readonly cardinality: 'N:M'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['postId']; + }; + readonly through: { + readonly table: 'post_tag'; + readonly namespaceId: 'public'; + readonly parentColumns: readonly ['postId']; + readonly childColumns: readonly ['tagId']; + readonly targetColumns: readonly ['id']; + }; + }; + readonly user: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['userId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'post'; + readonly namespaceId: 'public'; + readonly fields: { + readonly createdAt: { readonly column: 'createdAt' }; + readonly embedding: { readonly column: 'embedding' }; + readonly id: { readonly column: 'id' }; + readonly priority: { readonly column: 'priority' }; + readonly title: { readonly column: 'title' }; + readonly userId: { readonly column: 'userId' }; + }; + }; + }; + readonly PostTag: { + readonly fields: { + readonly postId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly tagId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + }; + readonly relations: { + readonly post: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['postId']; + readonly targetFields: readonly ['id']; + }; + }; + readonly tag: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['tagId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'post_tag'; + readonly namespaceId: 'public'; + readonly fields: { + readonly postId: { readonly column: 'postId' }; + readonly tagId: { readonly column: 'tagId' }; + }; + }; + }; + readonly Tag: { + readonly fields: { + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly label: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly cardinality: 'N:M'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['tagId']; + }; + readonly through: { + readonly table: 'post_tag'; + readonly namespaceId: 'public'; + readonly parentColumns: readonly ['tagId']; + readonly childColumns: readonly ['postId']; + readonly targetColumns: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'tag'; + readonly namespaceId: 'public'; + readonly fields: { + readonly id: { readonly column: 'id' }; + readonly label: { readonly column: 'label' }; + }; + }; + }; + readonly Task: { + readonly fields: { + readonly createdAt: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/timestamptz-temporal@1'; + }; + }; + readonly description: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly type: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + }; + readonly relations: { + readonly user: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['userId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'task'; + readonly namespaceId: 'public'; + readonly fields: { + readonly createdAt: { readonly column: 'createdAt' }; + readonly description: { readonly column: 'description' }; + readonly id: { readonly column: 'id' }; + readonly status: { readonly column: 'status' }; + readonly title: { readonly column: 'title' }; + readonly type: { readonly column: 'type' }; + readonly userId: { readonly column: 'userId' }; + }; + }; + readonly discriminator: { readonly field: 'type' }; + readonly variants: { + readonly bug: { readonly namespace: 'public' & NamespaceId; readonly model: 'Bug' }; + readonly feature: { + readonly namespace: 'public' & NamespaceId; + readonly model: 'Feature'; + }; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/timestamptz-temporal@1'; + }; + }; + readonly displayName: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['userId']; + }; + }; + readonly tasks: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['userId']; + }; + }; + }; + readonly storage: { + readonly table: 'user'; + readonly namespaceId: 'public'; + readonly fields: { + readonly address: { readonly column: 'address' }; + readonly createdAt: { readonly column: 'createdAt' }; + readonly displayName: { readonly column: 'displayName' }; + readonly email: { readonly column: 'email' }; + readonly id: { readonly column: 'id' }; + readonly kind: { readonly column: 'kind' }; + }; + }; + }; + }; + readonly enum: { + readonly Priority: { + readonly codecId: 'pg/int4@1'; + readonly members: readonly [ + { readonly name: 'Low'; readonly value: 0 }, + { readonly name: 'High'; readonly value: 1 }, + { readonly name: 'Urgent'; readonly value: 2 }, + ]; + }; + readonly user_type: { + readonly codecId: 'pg/text@1'; + readonly members: readonly [ + { readonly name: 'admin'; readonly value: 'admin' }, + { readonly name: 'user'; readonly value: 'user' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly Priority: readonly [ + { readonly name: 'Low'; readonly value: 0 }, + { readonly name: 'High'; readonly value: 1 }, + { readonly name: 'Urgent'; readonly value: 2 }, + ]; + readonly user_type: readonly [ + { readonly name: 'admin'; readonly value: 'admin' }, + { readonly name: 'user'; readonly value: 'user' }, + ]; + }; + }; + }; + }; + readonly capabilities: { + readonly postgres: { + readonly distinctOn: true; + readonly forKeyShare: true; + readonly forNoKeyUpdate: true; + readonly jsonAgg: true; + readonly lateral: true; + readonly limit: true; + readonly orderBy: true; + readonly 'pgvector.cosine': true; + readonly returning: true; + }; + readonly sql: { + readonly checkConstraint: true; + readonly defaultInInsert: true; + readonly enums: true; + readonly forShare: true; + readonly forUpdate: true; + readonly insertOnConflictSkip: true; + readonly insertOnConflictWithoutTarget: true; + readonly lateral: true; + readonly lockNowait: true; + readonly lockOf: true; + readonly lockSkipLocked: true; + readonly returning: true; + readonly scalarList: true; + }; + }; + readonly extensions: { + readonly 'demo/engagement-stats': { + readonly familyId: 'sql'; + readonly id: 'demo/engagement-stats'; + readonly kind: 'extension'; + readonly targetId: 'postgres'; + readonly types: { + readonly aggregateDescriptors: readonly [ + { + readonly input: { readonly kind: 'trait'; readonly trait: 'numeric' }; + readonly lower: unknown; + readonly nullable: true; + readonly operation: 'stddev'; + readonly output: { readonly codecId: 'pg/numeric@1'; readonly kind: 'codec' }; + }, + ]; + }; + readonly version: '0.0.1'; + }; + readonly pgvector: { + readonly capabilities: { readonly postgres: { readonly 'pgvector.cosine': true } }; + readonly familyId: 'sql'; + readonly id: 'pgvector'; + readonly kind: 'extension'; + readonly targetId: 'postgres'; + readonly types: { + readonly codecTypes: { + readonly import: { + readonly alias: 'PgVectorTypes'; + readonly named: 'CodecTypes'; + readonly package: '@internal/extension-pgvector/codec-types'; + }; + readonly parameterizedOutputTypes: { + readonly alias: 'PgVectorParameterizedOutputTypes'; + readonly named: 'ParameterizedOutputTypesForTypeParams'; + readonly package: '@internal/extension-pgvector/codec-types'; + }; + }; + readonly operationTypes: { + readonly import: { + readonly alias: 'PgVectorOperationTypes'; + readonly named: 'OperationTypes'; + readonly package: '@internal/extension-pgvector/operation-types'; + }; + }; + readonly queryOperationTypes: { + readonly import: { + readonly alias: 'PgVectorQueryOperationTypes'; + readonly named: 'QueryOperationTypes'; + readonly package: '@internal/extension-pgvector/operation-types'; + }; + }; + }; + readonly version: '0.0.1'; + }; + }; + readonly execution: { + readonly executionHash: ExecutionHash; + readonly mutations: { + readonly defaults: readonly [ + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'post'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'tag'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'task'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'user'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + ]; + }; + }; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = ContractWithTypeMaps; + +export type Namespaces = Contract['storage']['namespaces']; diff --git a/examples/prisma-8-demo/migrations/snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.json b/examples/prisma-8-demo/migrations/snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.json new file mode 100644 index 000000000000..d6662051f1a1 --- /dev/null +++ b/examples/prisma-8-demo/migrations/snapshots/fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"id":{"column":"id"},"priority":{"column":"priority"},"title":{"column":"title"},"userId":{"column":"userId"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"public","plane":"storage"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"checks":[{"expression":"\"type\" IN ('bug', 'feature')","name":"task_type_check_cda3f5e7","prefix":"task_type_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Task.type","namespaceId":"public","plane":"storage"}},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE","name":"user_address_shape_69432dc1","prefix":"user_address_shape"},{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"Task.type":{"kind":"valueSet","values":["bug","feature"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"fc039db97d116c1f5e6e56af25521c69e05232b49dc6e0058c9e3d3fb93eb263"},"target":"postgres","targetFamily":"sql"} diff --git a/examples/prisma-8-demo/src/prisma/contract.d.ts b/examples/prisma-8-demo/src/prisma/contract.d.ts index d180da08d5e9..f84d528883aa 100644 --- a/examples/prisma-8-demo/src/prisma/contract.d.ts +++ b/examples/prisma-8-demo/src/prisma/contract.d.ts @@ -25,11 +25,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-postgres/contract/types'; export type StorageHash = - StorageHashBase<'0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a'>; + StorageHashBase<'e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf'>; export type ExecutionHash = ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; export type ProfileHash = @@ -293,15 +293,15 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -315,6 +315,9 @@ type FieldTypeOverrides = { readonly Post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; }; + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly User: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -326,18 +329,23 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; readonly user: { readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; }; @@ -875,11 +883,37 @@ type ContractBase = Omit< }; readonly valueSet: { readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; readonly user_type: { readonly kind: 'valueSet'; readonly values: readonly ['admin', 'user']; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -1193,7 +1227,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly createdAt: { readonly nullable: false; @@ -1251,28 +1291,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly enum: { readonly Priority: { readonly codecId: 'pg/int4@1'; @@ -1428,28 +1446,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-demo/src/prisma/contract.json b/examples/prisma-8-demo/src/prisma/contract.json index 08c9ebb3e98a..bba1c4b65e80 100644 --- a/examples/prisma-8-demo/src/prisma/contract.json +++ b/examples/prisma-8-demo/src/prisma/contract.json @@ -546,8 +546,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "public", + "plane": "storage" } }, "createdAt": { @@ -649,40 +652,6 @@ "table": "user" } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "country": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } @@ -1058,6 +1027,13 @@ ] }, "task": { + "checks": [ + { + "expression": "\"type\" IN ('bug', 'feature')", + "name": "task_type_check_cda3f5e7", + "prefix": "task_type_check" + } + ], "columns": { "createdAt": { "codecId": "pg/timestamptz-temporal@1", @@ -1095,7 +1071,13 @@ "type": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Task.type", + "namespaceId": "public", + "plane": "storage" + } }, "userId": { "codecId": "pg/uuid@1", @@ -1143,6 +1125,11 @@ }, "user": { "checks": [ + { + "expression": "\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"country\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"country\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE", + "name": "user_address_shape_69432dc1", + "prefix": "user_address_shape" + }, { "expression": "\"kind\" IN ('admin', 'user')", "name": "user_kind_check_836d43ef", @@ -1201,6 +1188,40 @@ "uniques": [] } }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "Priority": { "kind": "valueSet", @@ -1210,6 +1231,13 @@ 2 ] }, + "Task.type": { + "kind": "valueSet", + "values": [ + "bug", + "feature" + ] + }, "user_type": { "kind": "valueSet", "values": [ @@ -1223,7 +1251,7 @@ "kind": "postgres-schema" } }, - "storageHash": "0ce1b6cf3e03ac1498f39eb8ef08d36d73ee2f0bc7fe368b97f1497ba050501a" + "storageHash": "e1916bd73bb2388302d81a1eaa17125c0cedb6ce47b489a7adeeb41c2ef50ebf" }, "execution": { "executionHash": "eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655", diff --git a/examples/prisma-8-postgis-demo/migrations/snapshots/28b5e5f98ce0e148f03e345518c9c53cb7a04a82e8e58de74a87c1b4ae08cf65/contract.d.ts b/examples/prisma-8-postgis-demo/migrations/snapshots/28b5e5f98ce0e148f03e345518c9c53cb7a04a82e8e58de74a87c1b4ae08cf65/contract.d.ts index f7a5c544b529..5108faf8108f 100644 --- a/examples/prisma-8-postgis-demo/migrations/snapshots/28b5e5f98ce0e148f03e345518c9c53cb7a04a82e8e58de74a87c1b4ae08cf65/contract.d.ts +++ b/examples/prisma-8-postgis-demo/migrations/snapshots/28b5e5f98ce0e148f03e345518c9c53cb7a04a82e8e58de74a87c1b4ae08cf65/contract.d.ts @@ -279,14 +279,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -649,7 +651,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/prisma-8-postgis-demo/src/prisma/contract.d.ts b/examples/prisma-8-postgis-demo/src/prisma/contract.d.ts index f7a5c544b529..5108faf8108f 100644 --- a/examples/prisma-8-postgis-demo/src/prisma/contract.d.ts +++ b/examples/prisma-8-postgis-demo/src/prisma/contract.d.ts @@ -279,14 +279,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -649,7 +651,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/prisma7-adoption/generated/prisma8/contract.d.ts b/examples/prisma7-adoption/generated/prisma8/contract.d.ts index 7282c2ae09be..54ca7df0688b 100644 --- a/examples/prisma7-adoption/generated/prisma8/contract.d.ts +++ b/examples/prisma7-adoption/generated/prisma8/contract.d.ts @@ -284,14 +284,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -897,7 +899,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/react-router-demo/src/prisma/contract.d.ts b/examples/react-router-demo/src/prisma/contract.d.ts index 3a4e6815649e..e85a20c6c87f 100644 --- a/examples/react-router-demo/src/prisma/contract.d.ts +++ b/examples/react-router-demo/src/prisma/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -590,7 +592,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/examples/retail-store/migrations/app/20260513T0505_initial/migration.json b/examples/retail-store/migrations/app/20260513T0505_initial/migration.json index cfeabaf07388..40d95ab97a4e 100644 --- a/examples/retail-store/migrations/app/20260513T0505_initial/migration.json +++ b/examples/retail-store/migrations/app/20260513T0505_initial/migration.json @@ -1,7 +1,7 @@ { "from": null, - "to": "6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595", + "to": "917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f", "providedInvariants": [], "createdAt": "2026-05-13T05:05:47.654Z", - "migrationHash": "cd39aeeef127de0031bdd02d0b9053b60fd9ada3593ff258229f7589187c636a" + "migrationHash": "886de0ddc5bc235badc57ae7ae88bf4b360c26823e57a2667ce4f9d5b151f12a" } diff --git a/examples/retail-store/migrations/app/20260513T0505_initial/migration.ts b/examples/retail-store/migrations/app/20260513T0505_initial/migration.ts index c3eca8e45f1b..f142f7d8f49a 100755 --- a/examples/retail-store/migrations/app/20260513T0505_initial/migration.ts +++ b/examples/retail-store/migrations/app/20260513T0505_initial/migration.ts @@ -5,8 +5,8 @@ import { Migration, MigrationCLI, } from '@prisma/orm-mongo/target/migration'; -import type { Contract as End } from '../../snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract'; -import endContract from '../../snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.json' with { +import type { Contract as End } from '../../snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract'; +import endContract from '../../snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.json' with { type: 'json', }; diff --git a/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.json b/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.json index 872485bfe627..dfa8eca04a51 100644 --- a/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.json +++ b/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.json @@ -1,7 +1,7 @@ { - "from": "6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595", - "to": "977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3", + "from": "917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f", + "to": "32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78", "providedInvariants": [], "createdAt": "2026-05-13T05:07:50.592Z", - "migrationHash": "0ec54ea9455e425ad0a4df92cf5c674aaac04dbcf81fcc6a64d35618ea08402b" + "migrationHash": "7b8a1159dfd91a4ca23ea45e5fb96f6b6ddfe3fc565f00811af1251963292486" } diff --git a/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.ts b/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.ts index dcee5d04670d..2c25737eda59 100755 --- a/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.ts +++ b/examples/retail-store/migrations/app/20260513T0507_add_product_category_index/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; -import type { Contract as End } from '../../snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract'; -import endContract from '../../snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.json' with { +import type { Contract as End } from '../../snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract'; +import endContract from '../../snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.json' with { type: 'json', }; -import type { Contract as Start } from '../../snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract'; -import startContract from '../../snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.json' with { +import type { Contract as Start } from '../../snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract'; +import startContract from '../../snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.json' with { type: 'json', }; diff --git a/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.json b/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.json index f882b213f3f2..115dcae34373 100644 --- a/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.json +++ b/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.json @@ -1,7 +1,7 @@ { - "from": "977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3", - "to": "9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc", + "from": "32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78", + "to": "c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467", "providedInvariants": [], "createdAt": "2026-05-13T05:08:23.957Z", - "migrationHash": "2bce4ded744ff835ac51e0c815c7567047ccc1acc25069e0bebadaf72fd5f7d7" + "migrationHash": "21376cd04dd2ac1a87eea87af86728fce737c346bb0acfd066ff3842525daa0e" } diff --git a/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts b/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts index ecc6f3fa9f55..11167ec9355b 100755 --- a/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts +++ b/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/migration.ts @@ -13,12 +13,12 @@ import { MigrationCLI, setValidation, } from '@prisma/orm-mongo/target/migration'; -import type { Contract as End } from '../../snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract'; -import endContract from '../../snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.json' with { +import type { Contract as Start } from '../../snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract'; +import startContract from '../../snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.json' with { type: 'json', }; -import type { Contract as Start } from '../../snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract'; -import startContract from '../../snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.json' with { +import type { Contract as End } from '../../snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract'; +import endContract from '../../snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.json' with { type: 'json', }; diff --git a/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/ops.json b/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/ops.json index 52e7218845ac..662bc61d3ce0 100644 --- a/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/ops.json +++ b/examples/retail-store/migrations/app/20260513T0508_backfill_product_status/ops.json @@ -124,7 +124,7 @@ }, "meta": { "target": "mongo", - "storageHash": "9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc", + "storageHash": "c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467", "lane": "mongo-pipeline" } }, @@ -155,7 +155,7 @@ }, "meta": { "target": "mongo", - "storageHash": "9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc", + "storageHash": "c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467", "lane": "mongo-raw" } } @@ -185,7 +185,7 @@ }, "meta": { "target": "mongo", - "storageHash": "9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc", + "storageHash": "c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467", "lane": "mongo-pipeline" } }, diff --git a/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.json b/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.json index 8a6b19d15a71..a4b442280405 100644 --- a/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.json +++ b/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.json @@ -1,7 +1,7 @@ { - "from": "9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc", - "to": "41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e", + "from": "c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467", + "to": "2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5", "providedInvariants": [], "createdAt": "2026-06-28T09:31:36.433Z", - "migrationHash": "951cb0076901c679918a5b9029595c0abec72768ebe216ddb327d9066b3e3650" + "migrationHash": "057aed8cf407d30641357e655e7efc330ae7c6cf5b4352e9edf6f20e5c18e487" } diff --git a/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.ts b/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.ts index 70a5adeadb6f..317b0871a34c 100755 --- a/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.ts +++ b/examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.ts @@ -1,11 +1,11 @@ #!/usr/bin/env -S node import { collMod, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; -import type { Contract as Start } from '../../snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract'; -import startContract from '../../snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.json' with { +import type { Contract as End } from '../../snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract'; +import endContract from '../../snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.json' with { type: 'json', }; -import type { Contract as End } from '../../snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract'; -import endContract from '../../snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.json' with { +import type { Contract as Start } from '../../snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract'; +import startContract from '../../snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.json' with { type: 'json', }; diff --git a/examples/retail-store/migrations/snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.d.ts b/examples/retail-store/migrations/snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.d.ts similarity index 82% rename from examples/retail-store/migrations/snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.d.ts rename to examples/retail-store/migrations/snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.d.ts index 4bf316302d46..1d1e6c05a177 100644 --- a/examples/retail-store/migrations/snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.d.ts +++ b/examples/retail-store/migrations/snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e'>; + StorageHashBase<'2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -82,14 +82,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -669,6 +671,160 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly postalCode: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly streetAndNumber: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly CartItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Image: { + readonly fields: { + readonly url: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly InvoiceLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly lineTotal: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly unitPrice: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + }; + }; + readonly OrderLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Price: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly currency: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly StatusEntry: { + readonly fields: { + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly timestamp: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + }; + }; + }; }; }; }; @@ -732,7 +888,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CartItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly userId: { @@ -825,7 +987,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'InvoiceLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly orderId: { @@ -920,7 +1088,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'OrderLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly shippingAddress: { @@ -929,7 +1103,13 @@ type ContractBase = Omit< }; readonly statusHistory: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'StatusEntry'; + }; readonly many: { readonly elementNullable: false }; }; readonly type: { @@ -1007,7 +1187,13 @@ type ContractBase = Omit< }; readonly image: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; }; readonly name: { readonly nullable: false; @@ -1015,7 +1201,13 @@ type ContractBase = Omit< }; readonly price: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; }; readonly primaryCategory: { readonly nullable: false; @@ -1070,7 +1262,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly email: { readonly nullable: false; @@ -1154,136 +1352,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly enum: { readonly OrderType: { readonly codecId: 'mongo/string@1'; @@ -1318,136 +1386,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/retail-store/migrations/snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.json b/examples/retail-store/migrations/snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.json new file mode 100644 index 000000000000..14ba6239f9d8 --- /dev/null +++ b/examples/retail-store/migrations/snapshots/2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"OrderType":{"codecId":"mongo/string@1","members":[{"name":"Delivery","value":"delivery"},{"name":"Pickup","value":"pickup"}]},"ProductStatus":{"codecId":"mongo/string@1","members":[{"name":"Active","value":"active"},{"name":"Discontinued","value":"discontinued"},{"name":"OutOfStock","value":"out-of-stock"}]}},"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"CartItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"InvoiceLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"OrderLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"StatusEntry","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"OrderType","namespaceId":"__unbound__","plane":"domain"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"embedding":{"many":{"elementNullable":false},"nullable":true,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"ProductStatus","namespaceId":"__unbound__","plane":"domain"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"embedding":{"field":"embedding"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"status":{"field":"status"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string","enum":["delivery","pickup"]},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"embedding":{"bsonType":["null","array"],"items":{"bsonType":"double"}},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"status":{"bsonType":"string","enum":["active","discontinued","out-of-stock"]},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","status","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}},"valueSet":{"OrderType":{"kind":"valueSet","values":["delivery","pickup"]},"ProductStatus":{"kind":"valueSet","values":["active","discontinued","out-of-stock"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.d.ts b/examples/retail-store/migrations/snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.d.ts similarity index 81% rename from examples/retail-store/migrations/snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.d.ts rename to examples/retail-store/migrations/snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.d.ts index a0c8f0590190..65c76ed39bf5 100644 --- a/examples/retail-store/migrations/snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.d.ts +++ b/examples/retail-store/migrations/snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595'>; + StorageHashBase<'32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -65,14 +65,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -529,6 +531,13 @@ type ContractBase = Omit< ]; readonly kind: 'mongo-index'; }, + { + readonly keys: readonly [ + { readonly direction: 1; readonly field: 'primaryCategory' }, + { readonly direction: 1; readonly field: 'articleType' }, + ]; + readonly kind: 'mongo-index'; + }, { readonly keys: readonly [ { readonly direction: 'hashed'; readonly field: 'code' }, @@ -631,6 +640,160 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly postalCode: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly streetAndNumber: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly CartItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Image: { + readonly fields: { + readonly url: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly InvoiceLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly lineTotal: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly unitPrice: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + }; + }; + readonly OrderLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Price: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly currency: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly StatusEntry: { + readonly fields: { + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly timestamp: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + }; + }; + }; }; }; }; @@ -694,7 +857,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CartItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly userId: { @@ -787,7 +956,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'InvoiceLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly orderId: { @@ -882,7 +1057,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'OrderLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly shippingAddress: { @@ -891,7 +1072,13 @@ type ContractBase = Omit< }; readonly statusHistory: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'StatusEntry'; + }; readonly many: { readonly elementNullable: false }; }; readonly type: { @@ -964,7 +1151,13 @@ type ContractBase = Omit< }; readonly image: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; }; readonly name: { readonly nullable: false; @@ -972,7 +1165,13 @@ type ContractBase = Omit< }; readonly price: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; }; readonly primaryCategory: { readonly nullable: false; @@ -1021,7 +1220,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly email: { readonly nullable: false; @@ -1105,272 +1310,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/retail-store/migrations/snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.json b/examples/retail-store/migrations/snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.json new file mode 100644 index 000000000000..efc89272ddd9 --- /dev/null +++ b/examples/retail-store/migrations/snapshots/32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"CartItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"InvoiceLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"OrderLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"StatusEntry","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"32b49a010a6bec06827848a71ade1588d7863e429644edc402becee50580be78"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.json b/examples/retail-store/migrations/snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.json deleted file mode 100644 index 8f4dbd2ca7cf..000000000000 --- a/examples/retail-store/migrations/snapshots/41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"OrderType":{"codecId":"mongo/string@1","members":[{"name":"Delivery","value":"delivery"},{"name":"Pickup","value":"pickup"}]},"ProductStatus":{"codecId":"mongo/string@1","members":[{"name":"Active","value":"active"},{"name":"Discontinued","value":"discontinued"},{"name":"OutOfStock","value":"out-of-stock"}]}},"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"CartItem"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"InvoiceLineItem"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"OrderLineItem"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"StatusEntry"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"OrderType","namespaceId":"__unbound__","plane":"domain"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"embedding":{"many":{"elementNullable":false},"nullable":true,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"ProductStatus","namespaceId":"__unbound__","plane":"domain"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"embedding":{"field":"embedding"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"status":{"field":"status"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string","enum":["delivery","pickup"]},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"embedding":{"bsonType":["null","array"],"items":{"bsonType":"double"}},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"status":{"bsonType":"string","enum":["active","discontinued","out-of-stock"]},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","status","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueSet":{"OrderType":{"kind":"valueSet","values":["delivery","pickup"]},"ProductStatus":{"kind":"valueSet","values":["active","discontinued","out-of-stock"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.json b/examples/retail-store/migrations/snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.json deleted file mode 100644 index 78264e5b0e0c..000000000000 --- a/examples/retail-store/migrations/snapshots/6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"CartItem"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"InvoiceLineItem"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"OrderLineItem"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"StatusEntry"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"6969147309c36a2e1cef69c2077abfa7ddb568d171ad99f03506d9f428b0a595"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.d.ts b/examples/retail-store/migrations/snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.d.ts similarity index 80% rename from examples/retail-store/migrations/snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.d.ts rename to examples/retail-store/migrations/snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.d.ts index bd59ba46526a..2d1156267de5 100644 --- a/examples/retail-store/migrations/snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.d.ts +++ b/examples/retail-store/migrations/snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc'>; + StorageHashBase<'917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -65,14 +65,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -137,13 +139,11 @@ export namespace Models { brand: FieldOutputTypes['__unbound__']['Product']['brand']; code: FieldOutputTypes['__unbound__']['Product']['code']; description: FieldOutputTypes['__unbound__']['Product']['description']; - embedding: FieldOutputTypes['__unbound__']['Product']['embedding']; id: FieldOutputTypes['__unbound__']['Product']['id']; image: FieldOutputTypes['__unbound__']['Product']['image']; name: FieldOutputTypes['__unbound__']['Product']['name']; price: FieldOutputTypes['__unbound__']['Product']['price']; primaryCategory: FieldOutputTypes['__unbound__']['Product']['primaryCategory']; - status: FieldOutputTypes['__unbound__']['Product']['status']; subCategory: FieldOutputTypes['__unbound__']['Product']['subCategory']; readonly [RelationKeys]?: never; }; @@ -531,13 +531,6 @@ type ContractBase = Omit< ]; readonly kind: 'mongo-index'; }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'primaryCategory' }, - { readonly direction: 1; readonly field: 'articleType' }, - ]; - readonly kind: 'mongo-index'; - }, { readonly keys: readonly [ { readonly direction: 'hashed'; readonly field: 'code' }, @@ -556,10 +549,6 @@ type ContractBase = Omit< readonly brand: { readonly bsonType: 'string' }; readonly code: { readonly bsonType: 'string' }; readonly description: { readonly bsonType: 'string' }; - readonly embedding: { - readonly bsonType: readonly ['null', 'array']; - readonly items: { readonly bsonType: 'double' }; - }; readonly image: { readonly additionalProperties: false; readonly bsonType: 'object'; @@ -577,7 +566,6 @@ type ContractBase = Omit< readonly required: readonly ['amount', 'currency']; }; readonly primaryCategory: { readonly bsonType: 'string' }; - readonly status: { readonly bsonType: 'string' }; readonly subCategory: { readonly bsonType: 'string' }; }; readonly required: readonly [ @@ -590,7 +578,6 @@ type ContractBase = Omit< 'name', 'price', 'primaryCategory', - 'status', 'subCategory', ]; }; @@ -646,6 +633,160 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly postalCode: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly streetAndNumber: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly CartItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Image: { + readonly fields: { + readonly url: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly InvoiceLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly lineTotal: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly unitPrice: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + }; + }; + readonly OrderLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Price: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly currency: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly StatusEntry: { + readonly fields: { + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly timestamp: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + }; + }; + }; }; }; }; @@ -709,7 +850,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CartItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly userId: { @@ -802,7 +949,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'InvoiceLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly orderId: { @@ -897,7 +1050,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'OrderLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly shippingAddress: { @@ -906,7 +1065,13 @@ type ContractBase = Omit< }; readonly statusHistory: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'StatusEntry'; + }; readonly many: { readonly elementNullable: false }; }; readonly type: { @@ -973,18 +1138,19 @@ type ContractBase = Omit< readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; }; - readonly embedding: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - readonly many: { readonly elementNullable: false }; - }; readonly id: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; }; readonly image: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; }; readonly name: { readonly nullable: false; @@ -992,16 +1158,18 @@ type ContractBase = Omit< }; readonly price: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; }; readonly primaryCategory: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; }; - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; readonly subCategory: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; @@ -1015,13 +1183,11 @@ type ContractBase = Omit< readonly brand: { readonly field: 'brand' }; readonly code: { readonly field: 'code' }; readonly description: { readonly field: 'description' }; - readonly embedding: { readonly field: 'embedding' }; readonly id: { readonly field: '_id' }; readonly image: { readonly field: 'image' }; readonly name: { readonly field: 'name' }; readonly price: { readonly field: 'price' }; readonly primaryCategory: { readonly field: 'primaryCategory' }; - readonly status: { readonly field: 'status' }; readonly subCategory: { readonly field: 'subCategory' }; }; }; @@ -1047,7 +1213,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly email: { readonly nullable: false; @@ -1131,272 +1303,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/retail-store/migrations/snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.json b/examples/retail-store/migrations/snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.json new file mode 100644 index 000000000000..199fa0bca020 --- /dev/null +++ b/examples/retail-store/migrations/snapshots/917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"CartItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"InvoiceLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"OrderLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"StatusEntry","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"917a8cff4030fa9140b93367a663f33d66cd14e92547b51e23a9ff82ffabf27f"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810/contract.d.ts b/examples/retail-store/migrations/snapshots/9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810/contract.d.ts deleted file mode 100644 index 57f5e63aac57..000000000000 --- a/examples/retail-store/migrations/snapshots/9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810/contract.d.ts +++ /dev/null @@ -1,1475 +0,0 @@ -// ⚠️ GENERATED FILE - DO NOT EDIT -// This file is automatically generated by 'prisma contract emit'. -// To regenerate, run: prisma contract emit -import type { CodecTypes as MongoCodecTypes } from '@prisma/orm-mongo/target/codec-types'; - -import type { - MongoCollection, - MongoContractWithTypeMaps, - MongoTypeMaps, - RelationKeys, -} from '@prisma/orm-mongo/family-contract'; -import type { - Contract as ContractType, - ExecutionHashBase, - NamespaceId, - ProfileHashBase, - StorageHashBase, -} from '@prisma/orm-mongo/contract/types'; - -export type StorageHash = - StorageHashBase<'9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810'>; -export type ExecutionHash = ExecutionHashBase; -export type ProfileHash = - ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; - -export type CodecTypes = MongoCodecTypes; - -export type AddressOutput = { - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; - readonly postalCode: CodecTypes['mongo/string@1']['output']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['output']; -}; -export type AddressInput = { - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; - readonly postalCode: CodecTypes['mongo/string@1']['input']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['input']; -}; -export type CartItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly productId: CodecTypes['mongo/string@1']['output']; -}; -export type CartItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly productId: CodecTypes['mongo/string@1']['input']; -}; -export type ImageOutput = { readonly url: CodecTypes['mongo/string@1']['output'] }; -export type ImageInput = { readonly url: CodecTypes['mongo/string@1']['input'] }; -export type InvoiceLineItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly lineTotal: CodecTypes['mongo/double@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly unitPrice: CodecTypes['mongo/double@1']['output']; -}; -export type InvoiceLineItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly lineTotal: CodecTypes['mongo/double@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly unitPrice: CodecTypes['mongo/double@1']['input']; -}; -export type OrderLineItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly productId: CodecTypes['mongo/string@1']['output']; -}; -export type OrderLineItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly productId: CodecTypes['mongo/string@1']['input']; -}; -export type PriceOutput = { - readonly amount: CodecTypes['mongo/double@1']['output']; - readonly currency: CodecTypes['mongo/string@1']['output']; -}; -export type PriceInput = { - readonly amount: CodecTypes['mongo/double@1']['input']; - readonly currency: CodecTypes['mongo/string@1']['input']; -}; -export type StatusEntryOutput = { - readonly status: CodecTypes['mongo/string@1']['output']; - readonly timestamp: CodecTypes['mongo/date@1']['output']; -}; -export type StatusEntryInput = { - readonly status: CodecTypes['mongo/string@1']['input']; - readonly timestamp: CodecTypes['mongo/date@1']['input']; -}; -export type FieldOutputTypes = { - readonly __unbound__: { - readonly AddToCartEvent: { - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly productId: CodecTypes['mongo/string@1']['output']; - }; - readonly Cart: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly userId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly Event: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly sessionId: CodecTypes['mongo/string@1']['output']; - readonly timestamp: CodecTypes['mongo/date@1']['output']; - readonly type: CodecTypes['mongo/string@1']['output']; - readonly userId: CodecTypes['mongo/string@1']['output']; - }; - readonly Invoice: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly issuedAt: CodecTypes['mongo/date@1']['output']; - readonly items: ReadonlyArray; - readonly orderId: CodecTypes['mongo/objectId@1']['output']; - readonly subtotal: CodecTypes['mongo/double@1']['output']; - readonly tax: CodecTypes['mongo/double@1']['output']; - readonly total: CodecTypes['mongo/double@1']['output']; - }; - readonly Location: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly postalCode: CodecTypes['mongo/string@1']['output']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['output']; - }; - readonly Order: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly shippingAddress: CodecTypes['mongo/string@1']['output']; - readonly statusHistory: ReadonlyArray; - readonly type: CodecTypes['mongo/string@1']['output']; - readonly userId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly Product: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly articleType: CodecTypes['mongo/string@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly code: CodecTypes['mongo/string@1']['output']; - readonly description: CodecTypes['mongo/string@1']['output']; - readonly embedding: ReadonlyArray | null; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly primaryCategory: CodecTypes['mongo/string@1']['output']; - readonly status: CodecTypes['mongo/string@1']['output']; - readonly subCategory: CodecTypes['mongo/string@1']['output']; - }; - readonly SearchEvent: { readonly query: CodecTypes['mongo/string@1']['output'] }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly address: AddressOutput | null; - readonly email: CodecTypes['mongo/string@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - }; - readonly ViewProductEvent: { - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly exitMethod: CodecTypes['mongo/string@1']['output'] | null; - readonly productId: CodecTypes['mongo/string@1']['output']; - readonly subCategory: CodecTypes['mongo/string@1']['output']; - }; - }; -}; -export type FieldInputTypes = { - readonly __unbound__: { - readonly AddToCartEvent: { - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly productId: CodecTypes['mongo/string@1']['input']; - }; - readonly Cart: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly userId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly Event: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly sessionId: CodecTypes['mongo/string@1']['input']; - readonly timestamp: CodecTypes['mongo/date@1']['input']; - readonly type: CodecTypes['mongo/string@1']['input']; - readonly userId: CodecTypes['mongo/string@1']['input']; - }; - readonly Invoice: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly issuedAt: CodecTypes['mongo/date@1']['input']; - readonly items: ReadonlyArray; - readonly orderId: CodecTypes['mongo/objectId@1']['input']; - readonly subtotal: CodecTypes['mongo/double@1']['input']; - readonly tax: CodecTypes['mongo/double@1']['input']; - readonly total: CodecTypes['mongo/double@1']['input']; - }; - readonly Location: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly postalCode: CodecTypes['mongo/string@1']['input']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['input']; - }; - readonly Order: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly shippingAddress: CodecTypes['mongo/string@1']['input']; - readonly statusHistory: ReadonlyArray; - readonly type: CodecTypes['mongo/string@1']['input']; - readonly userId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly Product: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly articleType: CodecTypes['mongo/string@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly code: CodecTypes['mongo/string@1']['input']; - readonly description: CodecTypes['mongo/string@1']['input']; - readonly embedding: ReadonlyArray | null; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly primaryCategory: CodecTypes['mongo/string@1']['input']; - readonly status: CodecTypes['mongo/string@1']['input']; - readonly subCategory: CodecTypes['mongo/string@1']['input']; - }; - readonly SearchEvent: { readonly query: CodecTypes['mongo/string@1']['input'] }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly address: AddressInput | null; - readonly email: CodecTypes['mongo/string@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - }; - readonly ViewProductEvent: { - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly exitMethod: CodecTypes['mongo/string@1']['input'] | null; - readonly productId: CodecTypes['mongo/string@1']['input']; - readonly subCategory: CodecTypes['mongo/string@1']['input']; - }; - }; -}; - -export namespace Models { - export type unbound_AddToCartEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'add-to-cart'; - userId: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - productId: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_Cart = { - _id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - userId: CodecTypes['mongo/objectId@1']['output']; - user: unbound_User; - readonly [RelationKeys]?: 'user'; - }; - export type unbound_Event = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'add-to-cart' | 'search' | 'view-product'; - userId: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_Invoice = { - _id: CodecTypes['mongo/objectId@1']['output']; - issuedAt: CodecTypes['mongo/date@1']['output']; - items: ReadonlyArray; - orderId: CodecTypes['mongo/objectId@1']['output']; - subtotal: CodecTypes['mongo/double@1']['output']; - tax: CodecTypes['mongo/double@1']['output']; - total: CodecTypes['mongo/double@1']['output']; - order: unbound_Order; - readonly [RelationKeys]?: 'order'; - }; - export type unbound_Location = { - _id: CodecTypes['mongo/objectId@1']['output']; - city: CodecTypes['mongo/string@1']['output']; - country: CodecTypes['mongo/string@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - postalCode: CodecTypes['mongo/string@1']['output']; - streetAndNumber: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_Order = { - _id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - shippingAddress: CodecTypes['mongo/string@1']['output']; - statusHistory: ReadonlyArray; - type: CodecTypes['mongo/string@1']['output']; - userId: CodecTypes['mongo/objectId@1']['output']; - invoices: unbound_Invoice[]; - user: unbound_User; - readonly [RelationKeys]?: 'invoices' | 'user'; - }; - export type unbound_Product = { - _id: CodecTypes['mongo/objectId@1']['output']; - articleType: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - code: CodecTypes['mongo/string@1']['output']; - description: CodecTypes['mongo/string@1']['output']; - embedding: ReadonlyArray | null; - image: ImageOutput; - name: CodecTypes['mongo/string@1']['output']; - price: PriceOutput; - primaryCategory: CodecTypes['mongo/string@1']['output']; - status: CodecTypes['mongo/string@1']['output']; - subCategory: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_SearchEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'search'; - userId: CodecTypes['mongo/string@1']['output']; - query: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_User = { - _id: CodecTypes['mongo/objectId@1']['output']; - address: AddressOutput | null; - email: CodecTypes['mongo/string@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - carts: unbound_Cart[]; - orders: unbound_Order[]; - readonly [RelationKeys]?: 'carts' | 'orders'; - }; - export type unbound_ViewProductEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'view-product'; - userId: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - exitMethod: CodecTypes['mongo/string@1']['output'] | null; - productId: CodecTypes['mongo/string@1']['output']; - subCategory: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_AnyEvent = - unbound_AddToCartEvent | unbound_SearchEvent | unbound_ViewProductEvent; -} - -export declare const models: { - __unbound__: { - AddToCartEvent: Models.unbound_AddToCartEvent; - Cart: Models.unbound_Cart; - Event: Models.unbound_Event; - Invoice: Models.unbound_Invoice; - Location: Models.unbound_Location; - Order: Models.unbound_Order; - Product: Models.unbound_Product; - SearchEvent: Models.unbound_SearchEvent; - User: Models.unbound_User; - ViewProductEvent: Models.unbound_ViewProductEvent; - AnyEvent: Models.unbound_AnyEvent; - }; -}; - -export type TypeMaps = MongoTypeMaps; - -type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-database'; - readonly entries: { - readonly collection: { - readonly carts: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'userId' }]; - readonly kind: 'mongo-index'; - readonly unique: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly brand: { readonly bsonType: 'string' }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly productId: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'amount', - 'brand', - 'image', - 'name', - 'price', - 'productId', - ]; - }; - }; - readonly userId: { readonly bsonType: 'objectId' }; - }; - readonly required: readonly ['_id', 'items', 'userId']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly events: { - readonly indexes: readonly [ - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'userId' }, - { readonly direction: -1; readonly field: 'timestamp' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly expireAfterSeconds: 7776000; - readonly keys: readonly [{ readonly direction: 1; readonly field: 'timestamp' }]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly bsonType: 'object'; - readonly oneOf: readonly [ - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly brand: { readonly bsonType: 'string' }; - readonly exitMethod: { readonly bsonType: readonly ['null', 'string'] }; - readonly productId: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly subCategory: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['view-product'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['brand', 'productId', 'subCategory', 'type']; - }, - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly query: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['search'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['query', 'type']; - }, - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly brand: { readonly bsonType: 'string' }; - readonly productId: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['add-to-cart'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['brand', 'productId', 'type']; - }, - ]; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly bsonType: 'string' }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['_id', 'sessionId', 'timestamp', 'type', 'userId']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly invoices: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'orderId' }]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [{ readonly direction: -1; readonly field: 'issuedAt' }]; - readonly kind: 'mongo-index'; - readonly sparse: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly issuedAt: { readonly bsonType: 'date' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly lineTotal: { readonly bsonType: 'double' }; - readonly name: { readonly bsonType: 'string' }; - readonly unitPrice: { readonly bsonType: 'double' }; - }; - readonly required: readonly ['amount', 'lineTotal', 'name', 'unitPrice']; - }; - }; - readonly orderId: { readonly bsonType: 'objectId' }; - readonly subtotal: { readonly bsonType: 'double' }; - readonly tax: { readonly bsonType: 'double' }; - readonly total: { readonly bsonType: 'double' }; - }; - readonly required: readonly [ - '_id', - 'issuedAt', - 'items', - 'orderId', - 'subtotal', - 'tax', - 'total', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly locations: { - readonly indexes: readonly [ - { - readonly collation: { readonly locale: 'en'; readonly strength: 2 }; - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'city' }, - { readonly direction: 1; readonly field: 'country' }, - ]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly city: { readonly bsonType: 'string' }; - readonly country: { readonly bsonType: 'string' }; - readonly name: { readonly bsonType: 'string' }; - readonly postalCode: { readonly bsonType: 'string' }; - readonly streetAndNumber: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - '_id', - 'city', - 'country', - 'name', - 'postalCode', - 'streetAndNumber', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly orders: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'userId' }]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly brand: { readonly bsonType: 'string' }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly productId: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'amount', - 'brand', - 'image', - 'name', - 'price', - 'productId', - ]; - }; - }; - readonly shippingAddress: { readonly bsonType: 'string' }; - readonly statusHistory: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly status: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - }; - readonly required: readonly ['status', 'timestamp']; - }; - }; - readonly type: { readonly bsonType: 'string' }; - readonly userId: { readonly bsonType: 'objectId' }; - }; - readonly required: readonly [ - '_id', - 'items', - 'shippingAddress', - 'statusHistory', - 'type', - 'userId', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly products: { - readonly indexes: readonly [ - { - readonly keys: readonly [ - { readonly direction: 'text'; readonly field: 'name' }, - { readonly direction: 'text'; readonly field: 'description' }, - ]; - readonly kind: 'mongo-index'; - readonly weights: { readonly description: 1; readonly name: 10 }; - }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'brand' }, - { readonly direction: 1; readonly field: 'subCategory' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'primaryCategory' }, - { readonly direction: 1; readonly field: 'articleType' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [ - { readonly direction: 'hashed'; readonly field: 'code' }, - ]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly articleType: { readonly bsonType: 'string' }; - readonly brand: { readonly bsonType: 'string' }; - readonly code: { readonly bsonType: 'string' }; - readonly description: { readonly bsonType: 'string' }; - readonly embedding: { - readonly bsonType: 'array'; - readonly items: { readonly bsonType: 'double' }; - }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly primaryCategory: { readonly bsonType: 'string' }; - readonly status: { readonly bsonType: 'string' }; - readonly subCategory: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - '_id', - 'articleType', - 'brand', - 'code', - 'description', - 'image', - 'name', - 'price', - 'primaryCategory', - 'status', - 'subCategory', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly users: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'email' }]; - readonly kind: 'mongo-index'; - readonly unique: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly address: { - readonly oneOf: readonly [ - { readonly bsonType: 'null' }, - { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly city: { readonly bsonType: 'string' }; - readonly country: { readonly bsonType: 'string' }; - readonly postalCode: { readonly bsonType: 'string' }; - readonly streetAndNumber: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'city', - 'country', - 'postalCode', - 'streetAndNumber', - ]; - }, - ]; - }; - readonly email: { readonly bsonType: 'string' }; - readonly name: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['_id', 'email', 'name']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, - 'roots' | 'domain' -> & { - readonly target: 'mongo'; - readonly targetFamily: 'mongo'; - readonly roots: { - readonly carts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Cart' }; - readonly events: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Event' }; - readonly invoices: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Invoice'; - }; - readonly locations: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Location'; - }; - readonly orders: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Order' }; - readonly products: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Product'; - }; - readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; - }; - readonly domain: { - readonly namespaces: { - readonly __unbound__: { - readonly models: { - readonly AddToCartEvent: { - readonly fields: { - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - readonly Cart: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly user: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'carts' }; - }; - readonly Event: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly sessionId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly AddToCartEvent: { readonly value: 'add-to-cart' }; - readonly SearchEvent: { readonly value: 'search' }; - readonly ViewProductEvent: { readonly value: 'view-product' }; - }; - }; - readonly Invoice: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly issuedAt: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly orderId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly subtotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly tax: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly total: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - readonly relations: { - readonly order: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Order'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['orderId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'invoices' }; - }; - readonly Location: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'locations' }; - }; - readonly Order: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly shippingAddress: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly statusHistory: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; - readonly many: { readonly elementNullable: false }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly invoices: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Invoice'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['orderId']; - }; - }; - readonly user: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'orders' }; - }; - readonly Product: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly articleType: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly code: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly description: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly embedding: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly primaryCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly subCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'products' }; - }; - readonly SearchEvent: { - readonly fields: { - readonly query: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - readonly User: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly address: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: { - readonly carts: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Cart'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['userId']; - }; - }; - readonly orders: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Order'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['userId']; - }; - }; - }; - readonly storage: { readonly collection: 'users' }; - }; - readonly ViewProductEvent: { - readonly fields: { - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly exitMethod: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly subCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; - }; - }; - }; - readonly capabilities: {}; - readonly extensions: {}; - readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; - readonly profileHash: ProfileHash; -}; - -export type Contract = MongoContractWithTypeMaps; diff --git a/examples/retail-store/migrations/snapshots/9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810/contract.json b/examples/retail-store/migrations/snapshots/9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810/contract.json deleted file mode 100644 index c5b7c73eb437..000000000000 --- a/examples/retail-store/migrations/snapshots/9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"}},"Cart":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"CartItem"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["_id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts"}},"Event":{"discriminator":{"field":"type"},"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"},"variants":{"AddToCartEvent":{"value":"add-to-cart"},"SearchEvent":{"value":"search"},"ViewProductEvent":{"value":"view-product"}}},"Invoice":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"InvoiceLineItem"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["_id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices"}},"Location":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations"}},"Order":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"OrderLineItem"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"StatusEntry"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["_id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["_id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders"}},"Product":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"embedding":{"many":{"elementNullable":false},"nullable":true,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products"}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"}},"User":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["_id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["_id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users"}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"embedding":{"bsonType":"array","items":{"bsonType":"double"}},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"status":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","status","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"9414a8f88a64f9decc0e019967459e49da083f011cf91898094b7bccab6c1810"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.json b/examples/retail-store/migrations/snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.json deleted file mode 100644 index 44c00bb96821..000000000000 --- a/examples/retail-store/migrations/snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"CartItem"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"InvoiceLineItem"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"OrderLineItem"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"StatusEntry"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.json b/examples/retail-store/migrations/snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.json deleted file mode 100644 index b78ef490a2de..000000000000 --- a/examples/retail-store/migrations/snapshots/9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"CartItem"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"InvoiceLineItem"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"OrderLineItem"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"StatusEntry"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"embedding":{"many":{"elementNullable":false},"nullable":true,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"embedding":{"field":"embedding"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"status":{"field":"status"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"embedding":{"bsonType":["null","array"],"items":{"bsonType":"double"}},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"status":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","status","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"9af6d90aaa4cd03bf7eb2040a1c1b5c1225a2b33883ea3d0552f312f4684f8dc"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b/contract.d.ts b/examples/retail-store/migrations/snapshots/bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b/contract.d.ts deleted file mode 100644 index b6382c0be504..000000000000 --- a/examples/retail-store/migrations/snapshots/bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b/contract.d.ts +++ /dev/null @@ -1,1498 +0,0 @@ -// ⚠️ GENERATED FILE - DO NOT EDIT -// This file is automatically generated by 'prisma contract emit'. -// To regenerate, run: prisma contract emit -import type { CodecTypes as MongoCodecTypes } from '@prisma/orm-mongo/target/codec-types'; - -import type { - MongoCollection, - MongoContractWithTypeMaps, - MongoTypeMaps, - RelationKeys, -} from '@prisma/orm-mongo/family-contract'; -import type { - Contract as ContractType, - ExecutionHashBase, - NamespaceId, - ProfileHashBase, - StorageHashBase, -} from '@prisma/orm-mongo/contract/types'; - -export type StorageHash = - StorageHashBase<'bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b'>; -export type ExecutionHash = ExecutionHashBase; -export type ProfileHash = - ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; - -export type CodecTypes = MongoCodecTypes; - -export type AddressOutput = { - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; - readonly postalCode: CodecTypes['mongo/string@1']['output']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['output']; -}; -export type AddressInput = { - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; - readonly postalCode: CodecTypes['mongo/string@1']['input']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['input']; -}; -export type CartItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly productId: CodecTypes['mongo/string@1']['output']; -}; -export type CartItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly productId: CodecTypes['mongo/string@1']['input']; -}; -export type ImageOutput = { readonly url: CodecTypes['mongo/string@1']['output'] }; -export type ImageInput = { readonly url: CodecTypes['mongo/string@1']['input'] }; -export type InvoiceLineItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly lineTotal: CodecTypes['mongo/double@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly unitPrice: CodecTypes['mongo/double@1']['output']; -}; -export type InvoiceLineItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly lineTotal: CodecTypes['mongo/double@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly unitPrice: CodecTypes['mongo/double@1']['input']; -}; -export type OrderLineItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly productId: CodecTypes['mongo/string@1']['output']; -}; -export type OrderLineItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly productId: CodecTypes['mongo/string@1']['input']; -}; -export type PriceOutput = { - readonly amount: CodecTypes['mongo/double@1']['output']; - readonly currency: CodecTypes['mongo/string@1']['output']; -}; -export type PriceInput = { - readonly amount: CodecTypes['mongo/double@1']['input']; - readonly currency: CodecTypes['mongo/string@1']['input']; -}; -export type StatusEntryOutput = { - readonly status: CodecTypes['mongo/string@1']['output']; - readonly timestamp: CodecTypes['mongo/date@1']['output']; -}; -export type StatusEntryInput = { - readonly status: CodecTypes['mongo/string@1']['input']; - readonly timestamp: CodecTypes['mongo/date@1']['input']; -}; -export type FieldOutputTypes = { - readonly __unbound__: { - readonly AddToCartEvent: { - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly productId: CodecTypes['mongo/string@1']['output']; - }; - readonly Cart: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly userId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly Event: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly sessionId: CodecTypes['mongo/string@1']['output']; - readonly timestamp: CodecTypes['mongo/date@1']['output']; - readonly type: CodecTypes['mongo/string@1']['output']; - readonly userId: CodecTypes['mongo/string@1']['output']; - }; - readonly Invoice: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly issuedAt: CodecTypes['mongo/date@1']['output']; - readonly items: ReadonlyArray; - readonly orderId: CodecTypes['mongo/objectId@1']['output']; - readonly subtotal: CodecTypes['mongo/double@1']['output']; - readonly tax: CodecTypes['mongo/double@1']['output']; - readonly total: CodecTypes['mongo/double@1']['output']; - }; - readonly Location: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly postalCode: CodecTypes['mongo/string@1']['output']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['output']; - }; - readonly Order: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly shippingAddress: CodecTypes['mongo/string@1']['output']; - readonly statusHistory: ReadonlyArray; - readonly type: 'delivery' | 'pickup'; - readonly userId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly Product: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly articleType: CodecTypes['mongo/string@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly code: CodecTypes['mongo/string@1']['output']; - readonly description: CodecTypes['mongo/string@1']['output']; - readonly embedding: ReadonlyArray | null; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly primaryCategory: CodecTypes['mongo/string@1']['output']; - readonly status: 'active' | 'discontinued' | 'out-of-stock'; - readonly subCategory: CodecTypes['mongo/string@1']['output']; - }; - readonly SearchEvent: { readonly query: CodecTypes['mongo/string@1']['output'] }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly address: AddressOutput | null; - readonly email: CodecTypes['mongo/string@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - }; - readonly ViewProductEvent: { - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly exitMethod: CodecTypes['mongo/string@1']['output'] | null; - readonly productId: CodecTypes['mongo/string@1']['output']; - readonly subCategory: CodecTypes['mongo/string@1']['output']; - }; - }; -}; -export type FieldInputTypes = { - readonly __unbound__: { - readonly AddToCartEvent: { - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly productId: CodecTypes['mongo/string@1']['input']; - }; - readonly Cart: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly userId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly Event: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly sessionId: CodecTypes['mongo/string@1']['input']; - readonly timestamp: CodecTypes['mongo/date@1']['input']; - readonly type: CodecTypes['mongo/string@1']['input']; - readonly userId: CodecTypes['mongo/string@1']['input']; - }; - readonly Invoice: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly issuedAt: CodecTypes['mongo/date@1']['input']; - readonly items: ReadonlyArray; - readonly orderId: CodecTypes['mongo/objectId@1']['input']; - readonly subtotal: CodecTypes['mongo/double@1']['input']; - readonly tax: CodecTypes['mongo/double@1']['input']; - readonly total: CodecTypes['mongo/double@1']['input']; - }; - readonly Location: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly postalCode: CodecTypes['mongo/string@1']['input']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['input']; - }; - readonly Order: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly shippingAddress: CodecTypes['mongo/string@1']['input']; - readonly statusHistory: ReadonlyArray; - readonly type: 'delivery' | 'pickup'; - readonly userId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly Product: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly articleType: CodecTypes['mongo/string@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly code: CodecTypes['mongo/string@1']['input']; - readonly description: CodecTypes['mongo/string@1']['input']; - readonly embedding: ReadonlyArray | null; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly primaryCategory: CodecTypes['mongo/string@1']['input']; - readonly status: 'active' | 'discontinued' | 'out-of-stock'; - readonly subCategory: CodecTypes['mongo/string@1']['input']; - }; - readonly SearchEvent: { readonly query: CodecTypes['mongo/string@1']['input'] }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly address: AddressInput | null; - readonly email: CodecTypes['mongo/string@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - }; - readonly ViewProductEvent: { - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly exitMethod: CodecTypes['mongo/string@1']['input'] | null; - readonly productId: CodecTypes['mongo/string@1']['input']; - readonly subCategory: CodecTypes['mongo/string@1']['input']; - }; - }; -}; - -export namespace Models { - export type unbound_AddToCartEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'add-to-cart'; - userId: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - productId: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_Cart = { - _id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - userId: CodecTypes['mongo/objectId@1']['output']; - user: unbound_User; - readonly [RelationKeys]?: 'user'; - }; - export type unbound_Event = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'add-to-cart' | 'search' | 'view-product'; - userId: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_Invoice = { - _id: CodecTypes['mongo/objectId@1']['output']; - issuedAt: CodecTypes['mongo/date@1']['output']; - items: ReadonlyArray; - orderId: CodecTypes['mongo/objectId@1']['output']; - subtotal: CodecTypes['mongo/double@1']['output']; - tax: CodecTypes['mongo/double@1']['output']; - total: CodecTypes['mongo/double@1']['output']; - order: unbound_Order; - readonly [RelationKeys]?: 'order'; - }; - export type unbound_Location = { - _id: CodecTypes['mongo/objectId@1']['output']; - city: CodecTypes['mongo/string@1']['output']; - country: CodecTypes['mongo/string@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - postalCode: CodecTypes['mongo/string@1']['output']; - streetAndNumber: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_Order = { - _id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - shippingAddress: CodecTypes['mongo/string@1']['output']; - statusHistory: ReadonlyArray; - type: 'delivery' | 'pickup'; - userId: CodecTypes['mongo/objectId@1']['output']; - invoices: unbound_Invoice[]; - user: unbound_User; - readonly [RelationKeys]?: 'invoices' | 'user'; - }; - export type unbound_Product = { - _id: CodecTypes['mongo/objectId@1']['output']; - articleType: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - code: CodecTypes['mongo/string@1']['output']; - description: CodecTypes['mongo/string@1']['output']; - embedding: ReadonlyArray | null; - image: ImageOutput; - name: CodecTypes['mongo/string@1']['output']; - price: PriceOutput; - primaryCategory: CodecTypes['mongo/string@1']['output']; - status: 'active' | 'discontinued' | 'out-of-stock'; - subCategory: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_SearchEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'search'; - userId: CodecTypes['mongo/string@1']['output']; - query: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_User = { - _id: CodecTypes['mongo/objectId@1']['output']; - address: AddressOutput | null; - email: CodecTypes['mongo/string@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - carts: unbound_Cart[]; - orders: unbound_Order[]; - readonly [RelationKeys]?: 'carts' | 'orders'; - }; - export type unbound_ViewProductEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'view-product'; - userId: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - exitMethod: CodecTypes['mongo/string@1']['output'] | null; - productId: CodecTypes['mongo/string@1']['output']; - subCategory: CodecTypes['mongo/string@1']['output']; - readonly [RelationKeys]?: never; - }; - export type unbound_AnyEvent = - unbound_AddToCartEvent | unbound_SearchEvent | unbound_ViewProductEvent; -} - -export declare const models: { - __unbound__: { - AddToCartEvent: Models.unbound_AddToCartEvent; - Cart: Models.unbound_Cart; - Event: Models.unbound_Event; - Invoice: Models.unbound_Invoice; - Location: Models.unbound_Location; - Order: Models.unbound_Order; - Product: Models.unbound_Product; - SearchEvent: Models.unbound_SearchEvent; - User: Models.unbound_User; - ViewProductEvent: Models.unbound_ViewProductEvent; - AnyEvent: Models.unbound_AnyEvent; - }; -}; - -export type TypeMaps = MongoTypeMaps; - -type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-database'; - readonly entries: { - readonly collection: { - readonly carts: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'userId' }]; - readonly kind: 'mongo-index'; - readonly unique: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly brand: { readonly bsonType: 'string' }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly productId: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'amount', - 'brand', - 'image', - 'name', - 'price', - 'productId', - ]; - }; - }; - readonly userId: { readonly bsonType: 'objectId' }; - }; - readonly required: readonly ['_id', 'items', 'userId']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly events: { - readonly indexes: readonly [ - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'userId' }, - { readonly direction: -1; readonly field: 'timestamp' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly expireAfterSeconds: 7776000; - readonly keys: readonly [{ readonly direction: 1; readonly field: 'timestamp' }]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly bsonType: 'object'; - readonly oneOf: readonly [ - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly brand: { readonly bsonType: 'string' }; - readonly exitMethod: { readonly bsonType: readonly ['null', 'string'] }; - readonly productId: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly subCategory: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['view-product'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['brand', 'productId', 'subCategory', 'type']; - }, - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly query: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['search'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['query', 'type']; - }, - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly brand: { readonly bsonType: 'string' }; - readonly productId: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['add-to-cart'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['brand', 'productId', 'type']; - }, - ]; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly bsonType: 'string' }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['_id', 'sessionId', 'timestamp', 'type', 'userId']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly invoices: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'orderId' }]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [{ readonly direction: -1; readonly field: 'issuedAt' }]; - readonly kind: 'mongo-index'; - readonly sparse: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly issuedAt: { readonly bsonType: 'date' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly lineTotal: { readonly bsonType: 'double' }; - readonly name: { readonly bsonType: 'string' }; - readonly unitPrice: { readonly bsonType: 'double' }; - }; - readonly required: readonly ['amount', 'lineTotal', 'name', 'unitPrice']; - }; - }; - readonly orderId: { readonly bsonType: 'objectId' }; - readonly subtotal: { readonly bsonType: 'double' }; - readonly tax: { readonly bsonType: 'double' }; - readonly total: { readonly bsonType: 'double' }; - }; - readonly required: readonly [ - '_id', - 'issuedAt', - 'items', - 'orderId', - 'subtotal', - 'tax', - 'total', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly locations: { - readonly indexes: readonly [ - { - readonly collation: { readonly locale: 'en'; readonly strength: 2 }; - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'city' }, - { readonly direction: 1; readonly field: 'country' }, - ]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly city: { readonly bsonType: 'string' }; - readonly country: { readonly bsonType: 'string' }; - readonly name: { readonly bsonType: 'string' }; - readonly postalCode: { readonly bsonType: 'string' }; - readonly streetAndNumber: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - '_id', - 'city', - 'country', - 'name', - 'postalCode', - 'streetAndNumber', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly orders: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'userId' }]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly brand: { readonly bsonType: 'string' }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly productId: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'amount', - 'brand', - 'image', - 'name', - 'price', - 'productId', - ]; - }; - }; - readonly shippingAddress: { readonly bsonType: 'string' }; - readonly statusHistory: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly status: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - }; - readonly required: readonly ['status', 'timestamp']; - }; - }; - readonly type: { - readonly bsonType: 'string'; - readonly enum: readonly ['delivery', 'pickup']; - }; - readonly userId: { readonly bsonType: 'objectId' }; - }; - readonly required: readonly [ - '_id', - 'items', - 'shippingAddress', - 'statusHistory', - 'type', - 'userId', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly products: { - readonly indexes: readonly [ - { - readonly keys: readonly [ - { readonly direction: 'text'; readonly field: 'name' }, - { readonly direction: 'text'; readonly field: 'description' }, - ]; - readonly kind: 'mongo-index'; - readonly weights: { readonly description: 1; readonly name: 10 }; - }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'brand' }, - { readonly direction: 1; readonly field: 'subCategory' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'primaryCategory' }, - { readonly direction: 1; readonly field: 'articleType' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [ - { readonly direction: 'hashed'; readonly field: 'code' }, - ]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly articleType: { readonly bsonType: 'string' }; - readonly brand: { readonly bsonType: 'string' }; - readonly code: { readonly bsonType: 'string' }; - readonly description: { readonly bsonType: 'string' }; - readonly embedding: { - readonly bsonType: 'array'; - readonly items: { readonly bsonType: 'double' }; - }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly primaryCategory: { readonly bsonType: 'string' }; - readonly status: { - readonly bsonType: 'string'; - readonly enum: readonly ['active', 'discontinued', 'out-of-stock']; - }; - readonly subCategory: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - '_id', - 'articleType', - 'brand', - 'code', - 'description', - 'image', - 'name', - 'price', - 'primaryCategory', - 'status', - 'subCategory', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly users: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'email' }]; - readonly kind: 'mongo-index'; - readonly unique: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly address: { - readonly oneOf: readonly [ - { readonly bsonType: 'null' }, - { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly city: { readonly bsonType: 'string' }; - readonly country: { readonly bsonType: 'string' }; - readonly postalCode: { readonly bsonType: 'string' }; - readonly streetAndNumber: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'city', - 'country', - 'postalCode', - 'streetAndNumber', - ]; - }, - ]; - }; - readonly email: { readonly bsonType: 'string' }; - readonly name: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['_id', 'email', 'name']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, - 'roots' | 'domain' -> & { - readonly target: 'mongo'; - readonly targetFamily: 'mongo'; - readonly roots: { - readonly carts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Cart' }; - readonly events: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Event' }; - readonly invoices: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Invoice'; - }; - readonly locations: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Location'; - }; - readonly orders: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Order' }; - readonly products: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Product'; - }; - readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; - }; - readonly domain: { - readonly namespaces: { - readonly __unbound__: { - readonly models: { - readonly AddToCartEvent: { - readonly fields: { - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - readonly Cart: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly user: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'carts' }; - }; - readonly Event: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly sessionId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly AddToCartEvent: { readonly value: 'add-to-cart' }; - readonly SearchEvent: { readonly value: 'search' }; - readonly ViewProductEvent: { readonly value: 'view-product' }; - }; - }; - readonly Invoice: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly issuedAt: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly orderId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly subtotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly tax: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly total: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - readonly relations: { - readonly order: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Order'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['orderId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'invoices' }; - }; - readonly Location: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'locations' }; - }; - readonly Order: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly shippingAddress: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly statusHistory: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; - readonly many: { readonly elementNullable: false }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly invoices: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Invoice'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['orderId']; - }; - }; - readonly user: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'orders' }; - }; - readonly Product: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly articleType: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly code: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly description: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly embedding: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly primaryCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly subCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'products' }; - }; - readonly SearchEvent: { - readonly fields: { - readonly query: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - readonly User: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly address: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: { - readonly carts: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Cart'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['userId']; - }; - }; - readonly orders: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Order'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['userId']; - }; - }; - }; - readonly storage: { readonly collection: 'users' }; - }; - readonly ViewProductEvent: { - readonly fields: { - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly exitMethod: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly subCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; - readonly enum: { - readonly OrderType: { - readonly codecId: 'mongo/string@1'; - readonly members: readonly [ - { readonly name: 'Delivery'; readonly value: 'delivery' }, - { readonly name: 'Pickup'; readonly value: 'pickup' }, - ]; - }; - readonly ProductStatus: { - readonly codecId: 'mongo/string@1'; - readonly members: readonly [ - { readonly name: 'Active'; readonly value: 'active' }, - { readonly name: 'Discontinued'; readonly value: 'discontinued' }, - { readonly name: 'OutOfStock'; readonly value: 'out-of-stock' }, - ]; - }; - }; - }; - }; - }; - readonly capabilities: {}; - readonly extensions: {}; - readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; - readonly profileHash: ProfileHash; -}; - -export type Contract = MongoContractWithTypeMaps; diff --git a/examples/retail-store/migrations/snapshots/bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b/contract.json b/examples/retail-store/migrations/snapshots/bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b/contract.json deleted file mode 100644 index 4f3461da3fe3..000000000000 --- a/examples/retail-store/migrations/snapshots/bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"OrderType":{"codecId":"mongo/string@1","members":[{"name":"Delivery","value":"delivery"},{"name":"Pickup","value":"pickup"}]},"ProductStatus":{"codecId":"mongo/string@1","members":[{"name":"Active","value":"active"},{"name":"Discontinued","value":"discontinued"},{"name":"OutOfStock","value":"out-of-stock"}]}},"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"}},"Cart":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"CartItem"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["_id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts"}},"Event":{"discriminator":{"field":"type"},"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"},"variants":{"AddToCartEvent":{"value":"add-to-cart"},"SearchEvent":{"value":"search"},"ViewProductEvent":{"value":"view-product"}}},"Invoice":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"InvoiceLineItem"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["_id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices"}},"Location":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations"}},"Order":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"OrderLineItem"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"kind":"valueObject","name":"StatusEntry"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"OrderType","namespaceId":"__unbound__","plane":"domain"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["_id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["_id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders"}},"Product":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"embedding":{"many":{"elementNullable":false},"nullable":true,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"ProductStatus","namespaceId":"__unbound__","plane":"domain"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products"}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"}},"User":{"fields":{"_id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"address":{"nullable":true,"type":{"kind":"valueObject","name":"Address"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["_id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["_id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users"}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events"}}},"valueObjects":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"kind":"valueObject","name":"Image"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"kind":"valueObject","name":"Price"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string","enum":["delivery","pickup"]},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"embedding":{"bsonType":"array","items":{"bsonType":"double"}},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"status":{"bsonType":"string","enum":["active","discontinued","out-of-stock"]},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","status","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueSet":{"OrderType":{"kind":"valueSet","values":["delivery","pickup"]},"ProductStatus":{"kind":"valueSet","values":["active","discontinued","out-of-stock"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/migrations/snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.d.ts b/examples/retail-store/migrations/snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.d.ts similarity index 82% rename from examples/retail-store/migrations/snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.d.ts rename to examples/retail-store/migrations/snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.d.ts index 87331f9dc03d..419f49b78331 100644 --- a/examples/retail-store/migrations/snapshots/977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3/contract.d.ts +++ b/examples/retail-store/migrations/snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'977a060afe52c4a56f93f2f33d65b8b6b4cc4ded04d16fe6b4f8e7e9e61192d3'>; + StorageHashBase<'c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -65,14 +65,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -137,11 +139,13 @@ export namespace Models { brand: FieldOutputTypes['__unbound__']['Product']['brand']; code: FieldOutputTypes['__unbound__']['Product']['code']; description: FieldOutputTypes['__unbound__']['Product']['description']; + embedding: FieldOutputTypes['__unbound__']['Product']['embedding']; id: FieldOutputTypes['__unbound__']['Product']['id']; image: FieldOutputTypes['__unbound__']['Product']['image']; name: FieldOutputTypes['__unbound__']['Product']['name']; price: FieldOutputTypes['__unbound__']['Product']['price']; primaryCategory: FieldOutputTypes['__unbound__']['Product']['primaryCategory']; + status: FieldOutputTypes['__unbound__']['Product']['status']; subCategory: FieldOutputTypes['__unbound__']['Product']['subCategory']; readonly [RelationKeys]?: never; }; @@ -554,6 +558,10 @@ type ContractBase = Omit< readonly brand: { readonly bsonType: 'string' }; readonly code: { readonly bsonType: 'string' }; readonly description: { readonly bsonType: 'string' }; + readonly embedding: { + readonly bsonType: readonly ['null', 'array']; + readonly items: { readonly bsonType: 'double' }; + }; readonly image: { readonly additionalProperties: false; readonly bsonType: 'object'; @@ -571,6 +579,7 @@ type ContractBase = Omit< readonly required: readonly ['amount', 'currency']; }; readonly primaryCategory: { readonly bsonType: 'string' }; + readonly status: { readonly bsonType: 'string' }; readonly subCategory: { readonly bsonType: 'string' }; }; readonly required: readonly [ @@ -583,6 +592,7 @@ type ContractBase = Omit< 'name', 'price', 'primaryCategory', + 'status', 'subCategory', ]; }; @@ -638,6 +648,160 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly postalCode: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly streetAndNumber: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly CartItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Image: { + readonly fields: { + readonly url: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly InvoiceLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly lineTotal: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly unitPrice: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + }; + }; + readonly OrderLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Price: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly currency: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly StatusEntry: { + readonly fields: { + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly timestamp: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + }; + }; + }; }; }; }; @@ -701,7 +865,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CartItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly userId: { @@ -794,7 +964,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'InvoiceLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly orderId: { @@ -889,7 +1065,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'OrderLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly shippingAddress: { @@ -898,7 +1080,13 @@ type ContractBase = Omit< }; readonly statusHistory: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'StatusEntry'; + }; readonly many: { readonly elementNullable: false }; }; readonly type: { @@ -965,13 +1153,24 @@ type ContractBase = Omit< readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; }; + readonly embedding: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + readonly many: { readonly elementNullable: false }; + }; readonly id: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; }; readonly image: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; }; readonly name: { readonly nullable: false; @@ -979,12 +1178,22 @@ type ContractBase = Omit< }; readonly price: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; }; readonly primaryCategory: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; }; + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; readonly subCategory: { readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; @@ -998,11 +1207,13 @@ type ContractBase = Omit< readonly brand: { readonly field: 'brand' }; readonly code: { readonly field: 'code' }; readonly description: { readonly field: 'description' }; + readonly embedding: { readonly field: 'embedding' }; readonly id: { readonly field: '_id' }; readonly image: { readonly field: 'image' }; readonly name: { readonly field: 'name' }; readonly price: { readonly field: 'price' }; readonly primaryCategory: { readonly field: 'primaryCategory' }; + readonly status: { readonly field: 'status' }; readonly subCategory: { readonly field: 'subCategory' }; }; }; @@ -1028,7 +1239,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly email: { readonly nullable: false; @@ -1112,272 +1329,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/retail-store/migrations/snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.json b/examples/retail-store/migrations/snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.json new file mode 100644 index 000000000000..ece0d2834161 --- /dev/null +++ b/examples/retail-store/migrations/snapshots/c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"AddToCartEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"productId":{"field":"productId"}}}},"Cart":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"CartItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"carts","fields":{"id":{"field":"_id"},"items":{"field":"items"},"userId":{"field":"userId"}}}},"Event":{"discriminator":{"field":"type"},"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"sessionId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"id":{"field":"_id"},"sessionId":{"field":"sessionId"},"timestamp":{"field":"timestamp"},"type":{"field":"type"},"userId":{"field":"userId"}}},"variants":{"add-to-cart":{"model":"AddToCartEvent","namespace":"__unbound__"},"search":{"model":"SearchEvent","namespace":"__unbound__"},"view-product":{"model":"ViewProductEvent","namespace":"__unbound__"}}},"Invoice":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"issuedAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"InvoiceLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"orderId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"subtotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"tax":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"total":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}},"relations":{"order":{"cardinality":"N:1","nullable":false,"on":{"localFields":["orderId"],"targetFields":["id"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"invoices","fields":{"id":{"field":"_id"},"issuedAt":{"field":"issuedAt"},"items":{"field":"items"},"orderId":{"field":"orderId"},"subtotal":{"field":"subtotal"},"tax":{"field":"tax"},"total":{"field":"total"}}}},"Location":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"locations","fields":{"city":{"field":"city"},"country":{"field":"country"},"id":{"field":"_id"},"name":{"field":"name"},"postalCode":{"field":"postalCode"},"streetAndNumber":{"field":"streetAndNumber"}}}},"Order":{"fields":{"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"items":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"OrderLineItem","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"shippingAddress":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"statusHistory":{"many":{"elementNullable":false},"nullable":false,"type":{"entityKind":"valueObject","entityName":"StatusEntry","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"type":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}}},"relations":{"invoices":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["orderId"]},"to":{"model":"Invoice","namespace":"__unbound__"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"orders","fields":{"id":{"field":"_id"},"items":{"field":"items"},"shippingAddress":{"field":"shippingAddress"},"statusHistory":{"field":"statusHistory"},"type":{"field":"type"},"userId":{"field":"userId"}}}},"Product":{"fields":{"articleType":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"code":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"description":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"embedding":{"many":{"elementNullable":false},"nullable":true,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"primaryCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"products","fields":{"articleType":{"field":"articleType"},"brand":{"field":"brand"},"code":{"field":"code"},"description":{"field":"description"},"embedding":{"field":"embedding"},"id":{"field":"_id"},"image":{"field":"image"},"name":{"field":"name"},"price":{"field":"price"},"primaryCategory":{"field":"primaryCategory"},"status":{"field":"status"},"subCategory":{"field":"subCategory"}}}},"SearchEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"query":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"query":{"field":"query"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"carts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Cart","namespace":"__unbound__"}},"orders":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Order","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}},"ViewProductEvent":{"base":{"model":"Event","namespace":"__unbound__"},"fields":{"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"exitMethod":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"subCategory":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"events","fields":{"brand":{"field":"brand"},"exitMethod":{"field":"exitMethod"},"productId":{"field":"productId"},"subCategory":{"field":"subCategory"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"carts":{"model":"Cart","namespace":"__unbound__"},"events":{"model":"Event","namespace":"__unbound__"},"invoices":{"model":"Invoice","namespace":"__unbound__"},"locations":{"model":"Location","namespace":"__unbound__"},"orders":{"model":"Order","namespace":"__unbound__"},"products":{"model":"Product","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"carts":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"userId":{"bsonType":"objectId"}},"required":["_id","items","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"events":{"indexes":[{"keys":[{"direction":1,"field":"userId"},{"direction":-1,"field":"timestamp"}],"kind":"mongo-index"},{"expireAfterSeconds":7776000,"keys":[{"direction":1,"field":"timestamp"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"exitMethod":{"bsonType":["null","string"]},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"subCategory":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["view-product"]},"userId":{"bsonType":"string"}},"required":["brand","productId","subCategory","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"query":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["search"]},"userId":{"bsonType":"string"}},"required":["query","type"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"brand":{"bsonType":"string"},"productId":{"bsonType":"string"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"enum":["add-to-cart"]},"userId":{"bsonType":"string"}},"required":["brand","productId","type"]}],"properties":{"_id":{"bsonType":"objectId"},"sessionId":{"bsonType":"string"},"timestamp":{"bsonType":"date"},"type":{"bsonType":"string"},"userId":{"bsonType":"string"}},"required":["_id","sessionId","timestamp","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"invoices":{"indexes":[{"keys":[{"direction":1,"field":"orderId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"issuedAt"}],"kind":"mongo-index","sparse":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"issuedAt":{"bsonType":"date"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"lineTotal":{"bsonType":"double"},"name":{"bsonType":"string"},"unitPrice":{"bsonType":"double"}},"required":["amount","lineTotal","name","unitPrice"]}},"orderId":{"bsonType":"objectId"},"subtotal":{"bsonType":"double"},"tax":{"bsonType":"double"},"total":{"bsonType":"double"}},"required":["_id","issuedAt","items","orderId","subtotal","tax","total"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"locations":{"indexes":[{"collation":{"locale":"en","strength":2},"keys":[{"direction":1,"field":"city"},{"direction":1,"field":"country"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"city":{"bsonType":"string"},"country":{"bsonType":"string"},"name":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["_id","city","country","name","postalCode","streetAndNumber"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"orders":{"indexes":[{"keys":[{"direction":1,"field":"userId"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"items":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"int"},"brand":{"bsonType":"string"},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"productId":{"bsonType":"string"}},"required":["amount","brand","image","name","price","productId"]}},"shippingAddress":{"bsonType":"string"},"statusHistory":{"bsonType":"array","items":{"additionalProperties":false,"bsonType":"object","properties":{"status":{"bsonType":"string"},"timestamp":{"bsonType":"date"}},"required":["status","timestamp"]}},"type":{"bsonType":"string"},"userId":{"bsonType":"objectId"}},"required":["_id","items","shippingAddress","statusHistory","type","userId"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"products":{"indexes":[{"keys":[{"direction":"text","field":"name"},{"direction":"text","field":"description"}],"kind":"mongo-index","weights":{"description":1,"name":10}},{"keys":[{"direction":1,"field":"brand"},{"direction":1,"field":"subCategory"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"primaryCategory"},{"direction":1,"field":"articleType"}],"kind":"mongo-index"},{"keys":[{"direction":"hashed","field":"code"}],"kind":"mongo-index"}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"articleType":{"bsonType":"string"},"brand":{"bsonType":"string"},"code":{"bsonType":"string"},"description":{"bsonType":"string"},"embedding":{"bsonType":["null","array"],"items":{"bsonType":"double"}},"image":{"additionalProperties":false,"bsonType":"object","properties":{"url":{"bsonType":"string"}},"required":["url"]},"name":{"bsonType":"string"},"price":{"additionalProperties":false,"bsonType":"object","properties":{"amount":{"bsonType":"double"},"currency":{"bsonType":"string"}},"required":["amount","currency"]},"primaryCategory":{"bsonType":"string"},"status":{"bsonType":"string"},"subCategory":{"bsonType":"string"}},"required":["_id","articleType","brand","code","description","image","name","price","primaryCategory","status","subCategory"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"indexes":[{"keys":[{"direction":1,"field":"email"}],"kind":"mongo-index","unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"postalCode":{"bsonType":"string"},"streetAndNumber":{"bsonType":"string"}},"required":["city","country","postalCode","streetAndNumber"]}]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"postalCode":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"streetAndNumber":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"CartItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Image":{"fields":{"url":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"InvoiceLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"lineTotal":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"unitPrice":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}}}},"OrderLineItem":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}},"brand":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"image":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Image","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"price":{"nullable":false,"type":{"entityKind":"valueObject","entityName":"Price","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"productId":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"Price":{"fields":{"amount":{"nullable":false,"type":{"codecId":"mongo/double@1","kind":"scalar"}},"currency":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}},"StatusEntry":{"fields":{"status":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"timestamp":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}}}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"c529f34639ba48ec978d133093cd95ac2a5a76196ea39b3fa71179a9ef2c3467"},"target":"mongo","targetFamily":"mongo"} diff --git a/examples/retail-store/src/contract.d.ts b/examples/retail-store/src/contract.d.ts index 4bf316302d46..1d1e6c05a177 100644 --- a/examples/retail-store/src/contract.d.ts +++ b/examples/retail-store/src/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@prisma/orm-mongo/contract/types'; export type StorageHash = - StorageHashBase<'41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e'>; + StorageHashBase<'2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -82,14 +82,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -669,6 +671,160 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly postalCode: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly streetAndNumber: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly CartItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Image: { + readonly fields: { + readonly url: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly InvoiceLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly lineTotal: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly unitPrice: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + }; + }; + readonly OrderLineItem: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + readonly brand: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly image: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly price: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; + }; + readonly productId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Price: { + readonly fields: { + readonly amount: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly currency: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly StatusEntry: { + readonly fields: { + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly timestamp: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + }; + }; + }; }; }; }; @@ -732,7 +888,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CartItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly userId: { @@ -825,7 +987,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'InvoiceLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly orderId: { @@ -920,7 +1088,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'OrderLineItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly shippingAddress: { @@ -929,7 +1103,13 @@ type ContractBase = Omit< }; readonly statusHistory: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'StatusEntry'; + }; readonly many: { readonly elementNullable: false }; }; readonly type: { @@ -1007,7 +1187,13 @@ type ContractBase = Omit< }; readonly image: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Image'; + }; }; readonly name: { readonly nullable: false; @@ -1015,7 +1201,13 @@ type ContractBase = Omit< }; readonly price: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Price'; + }; }; readonly primaryCategory: { readonly nullable: false; @@ -1070,7 +1262,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly email: { readonly nullable: false; @@ -1154,136 +1352,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly enum: { readonly OrderType: { readonly codecId: 'mongo/string@1'; @@ -1318,136 +1386,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/examples/retail-store/src/contract.json b/examples/retail-store/src/contract.json index bbd2f0a2193b..0b7f73438f44 100644 --- a/examples/retail-store/src/contract.json +++ b/examples/retail-store/src/contract.json @@ -118,8 +118,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CartItem", "kind": "valueObject", - "name": "CartItem" + "namespaceId": "__unbound__", + "plane": "storage" } }, "userId": { @@ -262,8 +265,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "InvoiceLineItem", "kind": "valueObject", - "name": "InvoiceLineItem" + "namespaceId": "__unbound__", + "plane": "storage" } }, "orderId": { @@ -425,8 +431,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "OrderLineItem", "kind": "valueObject", - "name": "OrderLineItem" + "namespaceId": "__unbound__", + "plane": "storage" } }, "shippingAddress": { @@ -442,8 +451,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "StatusEntry", "kind": "valueObject", - "name": "StatusEntry" + "namespaceId": "__unbound__", + "plane": "storage" } }, "type": { @@ -574,8 +586,11 @@ "image": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Image", "kind": "valueObject", - "name": "Image" + "namespaceId": "__unbound__", + "plane": "storage" } }, "name": { @@ -588,8 +603,11 @@ "price": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Price", "kind": "valueObject", - "name": "Price" + "namespaceId": "__unbound__", + "plane": "storage" } }, "primaryCategory": { @@ -692,8 +710,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "email": { @@ -822,211 +843,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "country": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "postalCode": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "streetAndNumber": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "CartItem": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - } - }, - "brand": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "image": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Image" - } - }, - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "price": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Price" - } - }, - "productId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "Image": { - "fields": { - "url": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "InvoiceLineItem": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - } - }, - "lineTotal": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - } - }, - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "unitPrice": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - } - } - } - }, - "OrderLineItem": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - } - }, - "brand": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "image": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Image" - } - }, - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "price": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Price" - } - }, - "productId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "Price": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - } - }, - "currency": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "StatusEntry": { - "fields": { - "status": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "timestamp": { - "nullable": false, - "type": { - "codecId": "mongo/date@1", - "kind": "scalar" - } - } - } - } } } } @@ -1792,6 +1608,223 @@ } } }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "postalCode": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "streetAndNumber": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "CartItem": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + } + }, + "brand": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "image": { + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "Image", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "price": { + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "Price", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "productId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "Image": { + "fields": { + "url": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "InvoiceLineItem": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + } + }, + "lineTotal": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "unitPrice": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + } + } + } + }, + "OrderLineItem": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + } + }, + "brand": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "image": { + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "Image", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "price": { + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "Price", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "productId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "Price": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + } + }, + "currency": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "StatusEntry": { + "fields": { + "status": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "timestamp": { + "nullable": false, + "type": { + "codecId": "mongo/date@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "OrderType": { "kind": "valueSet", @@ -1814,7 +1847,7 @@ "kind": "mongo-database" } }, - "storageHash": "41bd5540ea833c009b5eacf6db7d489e88bdd5062554398ba8aa2657fa19d27e" + "storageHash": "2f0e67cbc2a193e280f07c26972a484af4b62d3472c018c73bfd095749d4d0f5" }, "capabilities": {}, "extensions": {}, diff --git a/examples/supabase/src/contract.d.ts b/examples/supabase/src/contract.d.ts index f4af435b244d..1bb95533ebba 100644 --- a/examples/supabase/src/contract.d.ts +++ b/examples/supabase/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -460,7 +462,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/1-framework/0-foundation/contract/README.md b/packages/1-framework/0-foundation/contract/README.md index e3ace3e78d87..b2315f201705 100644 --- a/packages/1-framework/0-foundation/contract/README.md +++ b/packages/1-framework/0-foundation/contract/README.md @@ -55,6 +55,7 @@ For SQL contracts, use `@internal/sql-contract-ts/schema-sql` instead. - `./testing`: Test helpers for creating contract fixtures - `./validate-contract`: Contract structure validation - `./validate-domain`: Domain model validation +- `./validate-storage`: Validation of the value objects in storage (`validateStorageValueObjects`) ## Type System diff --git a/packages/1-framework/0-foundation/contract/package.json b/packages/1-framework/0-foundation/contract/package.json index 855a50173123..70844a2c28f9 100644 --- a/packages/1-framework/0-foundation/contract/package.json +++ b/packages/1-framework/0-foundation/contract/package.json @@ -52,6 +52,7 @@ "./types": "./dist/types.mjs", "./validate-domain": "./dist/validate-domain.mjs", "./validate-once": "./dist/validate-once.mjs", + "./validate-storage": "./dist/validate-storage.mjs", "./package.json": "./package.json" }, "engines": { diff --git a/packages/1-framework/0-foundation/contract/src/contract-types.ts b/packages/1-framework/0-foundation/contract/src/contract-types.ts index 9d054d5dde45..421fbe1972ae 100644 --- a/packages/1-framework/0-foundation/contract/src/contract-types.ts +++ b/packages/1-framework/0-foundation/contract/src/contract-types.ts @@ -43,7 +43,7 @@ export interface Contract { readonly targetFamily: string; readonly roots: Record; /** - * Application plane (ADR 221): `domain.namespaces..{ models, valueObjects }`. + * Application plane (ADR 221): `domain.namespaces..{ models, enum }`. */ readonly domain: ApplicationDomain; readonly storage: TStorage; @@ -63,17 +63,33 @@ type ExactlyOneNamespace> = keyof T extends in : never; type NamespaceValueObjectsOf = TNamespace extends { - readonly valueObjects?: infer VO; + readonly entries: { readonly valueObject?: infer VO }; } ? VO extends Record ? VO : Record : Record; -/** Value-object map when the contract declares exactly one domain namespace. */ +/** The value object of `TStorage` that `TReference` names, at `namespaces[namespaceId].entries.valueObject[entityName]`, or `never` when storage does not have it. */ +export type ContractValueObjectAt< + TStorage, + TReference extends { readonly namespaceId: string; readonly entityName: string }, +> = TStorage extends { readonly namespaces: infer Namespaces } + ? TReference['namespaceId'] extends keyof Namespaces + ? Namespaces[TReference['namespaceId']] extends { + readonly entries: { readonly valueObject: infer VOs }; + } + ? TReference['entityName'] extends keyof VOs + ? VOs[TReference['entityName']] + : never + : never + : never + : never; + +/** Value-object map of the storage plane when the contract declares exactly one storage namespace. */ export type ContractValueObjectDefinitions = NamespaceValueObjectsOf< - TContract['domain']['namespaces'][ExactlyOneNamespace] + TContract['storage']['namespaces'][ExactlyOneNamespace] > extends infer Projected ? Projected extends Record ? Projected diff --git a/packages/1-framework/0-foundation/contract/src/contract-value-object-schema.ts b/packages/1-framework/0-foundation/contract/src/contract-value-object-schema.ts new file mode 100644 index 000000000000..8775cc2a60ca --- /dev/null +++ b/packages/1-framework/0-foundation/contract/src/contract-value-object-schema.ts @@ -0,0 +1,57 @@ +import { type } from 'arktype'; + +const ScalarFieldTypeSchema = type({ + '+': 'reject', + kind: "'scalar'", + codecId: 'string', + 'typeParams?': 'Record', +}); + +const ValueObjectReferenceSchema = type({ + '+': 'reject', + kind: "'valueObject'", + plane: "'storage'", + namespaceId: 'string', + entityKind: "'valueObject'", + entityName: 'string', +}); + +const UnionFieldTypeSchema = type({ + '+': 'reject', + kind: "'union'", + members: ScalarFieldTypeSchema.or(ValueObjectReferenceSchema).array(), +}); + +/** + * The type of a domain field or of a value object's member, `ContractFieldType`: a scalar with its codec, a value object by its storage coordinate, or a union of those. A union holds no union. + */ +export const ContractFieldTypeSchema = ScalarFieldTypeSchema.or(ValueObjectReferenceSchema).or( + UnionFieldTypeSchema, +); + +const StorageValueSetReferenceSchema = type({ + '+': 'reject', + plane: "'storage'", + namespaceId: 'string', + entityKind: "'valueSet'", + entityName: 'string', + 'spaceId?': 'string', +}); + +/** A value object's member, `ContractValueObjectField`. */ +export const ContractValueObjectFieldSchema = type({ + '+': 'reject', + nullable: 'boolean', + type: ContractFieldTypeSchema, + 'many?': type('false').or({ '+': 'reject', elementNullable: 'boolean' }), + 'dict?': 'true', + 'valueSet?': StorageValueSetReferenceSchema, +}); + +/** + * A value object, `ContractValueObject`: the storage entity under `storage.namespaces..entries.valueObject.` (ADR 309). The framework defines this entity kind, since domain fields refer to it; both families validate their entries against this schema. + */ +export const ContractValueObjectSchema = type({ + '+': 'reject', + fields: type({ '[string]': ContractValueObjectFieldSchema }), +}); diff --git a/packages/1-framework/0-foundation/contract/src/domain-envelope.ts b/packages/1-framework/0-foundation/contract/src/domain-envelope.ts index 4e50e54648ef..21cda0420f77 100644 --- a/packages/1-framework/0-foundation/contract/src/domain-envelope.ts +++ b/packages/1-framework/0-foundation/contract/src/domain-envelope.ts @@ -1,14 +1,15 @@ -import type { ContractEnum, ContractModelBase, ContractValueObject } from './domain-types'; +import type { ContractEnum, ContractModelBase } from './domain-types'; +import type { StorageBase } from './types'; export { UNBOUND_DOMAIN_NAMESPACE_ID } from './default-namespace'; /** - * One namespace's application-domain entities — models and optional value - * objects keyed by entity name within that namespace coordinate. + * One namespace's application-domain entities — models and optional enums + * keyed by entity name within that namespace coordinate. Value objects are + * storage entities (ADR 309). */ export interface ApplicationDomainNamespace { readonly models: Record; - readonly valueObjects?: Record; readonly enum?: Record; } @@ -20,6 +21,10 @@ export interface ApplicationDomain { readonly namespaces: Readonly>; } +/** + * What reading a contract's domain fields needs: the domain, and the storage namespaces that hold the value objects its fields refer to (ADR 309). + */ export type ContractWithDomain = { readonly domain: ApplicationDomain; + readonly storage: Pick; }; diff --git a/packages/1-framework/0-foundation/contract/src/domain-namespace-access.ts b/packages/1-framework/0-foundation/contract/src/domain-namespace-access.ts index 6ca1bda9c576..9816bc47fe23 100644 --- a/packages/1-framework/0-foundation/contract/src/domain-namespace-access.ts +++ b/packages/1-framework/0-foundation/contract/src/domain-namespace-access.ts @@ -1,7 +1,7 @@ import { contractError } from './contract-errors'; import { soleDomainNamespaceId } from './default-namespace'; import type { ApplicationDomain } from './domain-envelope'; -import type { ContractModelBase, ContractValueObject } from './domain-types'; +import type { ContractModelBase } from './domain-types'; /** * Models map for the contract's single domain namespace. Throws when the @@ -22,13 +22,3 @@ export function domainModelsAtDefaultNamespace( } return domainNamespace.models; } - -/** - * Value objects for the contract's single domain namespace, when present. - * Throws when the contract does not declare exactly one namespace. - */ -export function domainValueObjectsAtDefaultNamespace( - domain: ApplicationDomain, -): Record | undefined { - return domain.namespaces[soleDomainNamespaceId(domain)]?.valueObjects; -} diff --git a/packages/1-framework/0-foundation/contract/src/domain-types.ts b/packages/1-framework/0-foundation/contract/src/domain-types.ts index 2b6be3cb200f..2cfbc0b1c030 100644 --- a/packages/1-framework/0-foundation/contract/src/domain-types.ts +++ b/packages/1-framework/0-foundation/contract/src/domain-types.ts @@ -1,4 +1,5 @@ import type { CrossReference } from './cross-reference'; +import type { EntityCoordinate } from './entity-coordinate'; import type { JsonValue } from './types'; import type { ValueSetRef } from './value-set-ref'; @@ -8,17 +9,17 @@ export type ScalarFieldType = { readonly typeParams?: Record; }; -export type ValueObjectFieldType = { +/** The type of a field that refers to a value object: the coordinate of the value object in storage (ADR 309). Not to be confused with {@link ContractValueObjectField}, a field of a value object. */ +export interface ValueObjectReference extends EntityCoordinate<'storage', 'valueObject'> { readonly kind: 'valueObject'; - readonly name: string; -}; +} export type UnionFieldType = { readonly kind: 'union'; - readonly members: ReadonlyArray; + readonly members: ReadonlyArray; }; -export type ContractFieldType = ScalarFieldType | ValueObjectFieldType | UnionFieldType; +export type ContractFieldType = ScalarFieldType | ValueObjectReference | UnionFieldType; export type ContractField = { readonly nullable: boolean; @@ -92,8 +93,15 @@ export type ContractDiscriminator = { readonly field: string; }; +/** + * A field of a value object, which is a storage entity (ADR 309). Storage never refers to the domain plane, so a member an enum types refers to the enum's storage value set. + */ +export interface ContractValueObjectField extends Omit { + readonly valueSet?: EntityCoordinate<'storage', 'valueSet'> & { readonly spaceId?: string }; +} + export type ContractValueObject = { - readonly fields: Record; + readonly fields: Record; }; export type ModelStorageBase = Readonly>; diff --git a/packages/1-framework/0-foundation/contract/src/entity-coordinate.ts b/packages/1-framework/0-foundation/contract/src/entity-coordinate.ts new file mode 100644 index 000000000000..a2d403fb1c20 --- /dev/null +++ b/packages/1-framework/0-foundation/contract/src/entity-coordinate.ts @@ -0,0 +1,18 @@ +/** + * Canonical address for a named entity in a contract plane. + * + * `plane` is the top-level contract plane the entity lives on. `entityKind` equals the key of the slot the entity lives under (`'valueSet'` for `entries.valueSet`, `'valueObject'` for `entries.valueObject`, `'enum'` for the domain's `enum` slot), with no translation between the two (ADR 224). `entityName` is the entity's key in that slot. + * + * References between planes obey a directional invariant: domain may refer to storage; storage may never refer to domain. The coordinate carries the plane a validator checks. + * + * The type parameters narrow the plane and the kind for a reference to one kind of entity, such as a field's value object (`EntityCoordinate<'storage', 'valueObject'>`). + */ +export interface EntityCoordinate< + TPlane extends 'domain' | 'storage' = 'domain' | 'storage', + TKind extends string = string, +> { + readonly plane: TPlane; + readonly namespaceId: string; + readonly entityKind: TKind; + readonly entityName: string; +} diff --git a/packages/1-framework/0-foundation/contract/src/exports/types.ts b/packages/1-framework/0-foundation/contract/src/exports/types.ts index 4b3eb44fc734..be3c36d95e14 100644 --- a/packages/1-framework/0-foundation/contract/src/exports/types.ts +++ b/packages/1-framework/0-foundation/contract/src/exports/types.ts @@ -1,8 +1,14 @@ export type { Contract, ContractExecutionSection, + ContractValueObjectAt, ContractValueObjectDefinitions, } from '../contract-types'; +export { + ContractFieldTypeSchema, + ContractValueObjectFieldSchema, + ContractValueObjectSchema, +} from '../contract-value-object-schema'; export type { ControlPolicy } from '../control-policy'; export { effectiveControlPolicy, governingControlPolicy } from '../control-policy'; export type { CrossReference } from '../cross-reference'; @@ -14,10 +20,7 @@ export type { ContractWithDomain, } from '../domain-envelope'; export { UNBOUND_DOMAIN_NAMESPACE_ID } from '../domain-envelope'; -export { - domainModelsAtDefaultNamespace, - domainValueObjectsAtDefaultNamespace, -} from '../domain-namespace-access'; +export { domainModelsAtDefaultNamespace } from '../domain-namespace-access'; export type { ContractDiscriminator, ContractEmbedRelation, @@ -35,19 +38,21 @@ export type { ContractToManyRelation, ContractToOneRelation, ContractValueObject, + ContractValueObjectField, EmbedRelationKeys, ModelStorageBase, ReferenceRelationKeys, ScalarFieldType, UnionFieldType, - ValueObjectFieldType, + ValueObjectReference, } from '../domain-types'; +export type { EntityCoordinate } from '../entity-coordinate'; export { ContractExecutionSectionSchema } from '../execution-section-schema'; export type { EnumMemberValueType, FieldTypesFromDomain, StoredValueType, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, ValueTypeSide, } from '../field-value-types'; export type { NamespaceId } from '../namespace-id'; @@ -58,6 +63,7 @@ export type { ParameterizedOutputTypesFunction, } from '../parameterized-output-types'; export { type ResolvedDomainModel, resolveDomainModel } from '../resolve-domain-model'; +export { valueObjectAt, valueObjectsWithCoordinates } from '../storage-value-objects'; export type { $, Brand, diff --git a/packages/1-framework/0-foundation/contract/src/exports/validate-domain.ts b/packages/1-framework/0-foundation/contract/src/exports/validate-domain.ts index eaf10f0bce0f..d87bf60b6916 100644 --- a/packages/1-framework/0-foundation/contract/src/exports/validate-domain.ts +++ b/packages/1-framework/0-foundation/contract/src/exports/validate-domain.ts @@ -1,5 +1,6 @@ export { type DomainContractShape, type DomainModelShape, + removedDomainKeyProblems, validateContractDomain, } from '../validate-domain'; diff --git a/packages/1-framework/0-foundation/contract/src/exports/validate-storage.ts b/packages/1-framework/0-foundation/contract/src/exports/validate-storage.ts new file mode 100644 index 000000000000..48fcabfa8320 --- /dev/null +++ b/packages/1-framework/0-foundation/contract/src/exports/validate-storage.ts @@ -0,0 +1 @@ +export { validateStorageValueObjects } from '../validate-storage-value-objects'; diff --git a/packages/1-framework/0-foundation/contract/src/field-problems.ts b/packages/1-framework/0-foundation/contract/src/field-problems.ts new file mode 100644 index 000000000000..3e5b267fd7f4 --- /dev/null +++ b/packages/1-framework/0-foundation/contract/src/field-problems.ts @@ -0,0 +1,91 @@ +import { blindCast } from '@internal/utils/casts'; +import { valueObjectAt } from './storage-value-objects'; +import type { StorageBase } from './types'; + +interface FieldTypeLike { + readonly kind?: string; + readonly plane?: string; + readonly namespaceId?: string; + readonly entityKind?: string; + readonly entityName?: string; + readonly name?: string; + readonly members?: readonly FieldTypeLike[]; +} + +export interface FieldLike { + readonly type?: FieldTypeLike; + readonly valueSet?: { readonly namespaceId?: string; readonly entityName?: string }; + readonly many?: unknown; + readonly elementNullable?: unknown; + readonly dict?: boolean; +} + +export function fieldLike(field: unknown): FieldLike | undefined { + return blindCast(field); +} + +function valueObjectReferenceProblem(type: FieldTypeLike): string | undefined { + if (type.plane === undefined && type.entityKind === undefined) { + return `refers to value object "${type.name}" by name; contracts no longer do: a field refers to its value object by the coordinate of a storage entity under storage.namespaces..entries.valueObject, so re-emit the contract from its source, or rewrite a stored contract such as a migration snapshot with value-objects-in-storage.ts, which ships in the value-objects-in-storage scripts of the Prisma 8 upgrade guide`; + } + if (type.plane !== 'storage' || type.entityKind !== 'valueObject') { + return `refers to a value object with plane "${type.plane}" and entityKind "${type.entityKind}"; a value object reference has plane "storage" and entityKind "valueObject"`; + } + return undefined; +} + +/** What is wrong with the value object references of a field's type, a union's members included, each prefixed with `location`. */ +export function valueObjectTypeProblems( + storage: Pick, + type: FieldTypeLike | undefined, + location: string, +): readonly string[] { + if (type === undefined) return []; + if (type.kind === 'union') { + return (type.members ?? []).flatMap((member) => + valueObjectTypeProblems(storage, member, location), + ); + } + if (type.kind !== 'valueObject') return []; + const problem = valueObjectReferenceProblem(type); + if (problem !== undefined) return [`${location} ${problem}`]; + const namespaceId = type.namespaceId ?? ''; + const entityName = type.entityName ?? ''; + const found = valueObjectAt(storage, { + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName, + }); + return found === undefined + ? [ + `${location} refers to value object "${entityName}" in storage namespace "${namespaceId}", which storage.namespaces.${namespaceId}.entries.valueObject does not have`, + ] + : []; +} + +/** What is wrong with a field's `many`, `elementNullable` and `dict`, each prefixed with `location`. */ +export function fieldModifierProblems(location: string, field: unknown): readonly string[] { + const candidate = fieldLike(field); + const many = candidate?.many; + const isList = + typeof many === 'object' && + many !== null && + !Array.isArray(many) && + Object.keys(many).length === 1 && + 'elementNullable' in many && + typeof many.elementNullable === 'boolean'; + const problems: string[] = []; + if (many !== undefined && many !== false && !isList) { + problems.push( + `${location} has invalid "many"; expected false or an elementNullable descriptor`, + ); + } + if (candidate?.elementNullable !== undefined) { + problems.push(`${location} cannot have sibling "elementNullable"; put it inside "many"`); + } + if (isList && candidate?.dict) { + problems.push(`${location} cannot have both "many" and "dict" modifiers`); + } + return problems; +} diff --git a/packages/1-framework/0-foundation/contract/src/field-value-types.ts b/packages/1-framework/0-foundation/contract/src/field-value-types.ts index 0393113a1382..1b8b5959c346 100644 --- a/packages/1-framework/0-foundation/contract/src/field-value-types.ts +++ b/packages/1-framework/0-foundation/contract/src/field-value-types.ts @@ -102,24 +102,27 @@ type InputClosedWhenEmpty = Side extends 'input' ? ClosedWhenEmpty : T; -type NamespaceParts = { - readonly valueObjects: ValueObjects; - readonly overrides: NamespaceOverrides; +type ContractParts = { + readonly storage: StorageNamespaces; + readonly overrides: Overrides; }; +type ValueObjectFields = Lookup< + Lookup, Ns>, 'entries'>, 'valueObject'>, Name>, + 'fields' +>; + type ValueObjectType< + Ns, Name, Parts, CodecTypes, ParameterizedOutputs extends ParameterizedOutputTypesFunction, Side extends ValueTypeSide, > = ClosedWhenEmpty<{ - readonly [F in keyof Lookup< - Lookup, Name>, - 'fields' - >]: FieldValueType< - Lookup, Name>, 'fields'>[F], - Lookup, 'valueObjects'>, Name>, F>, + readonly [F in keyof ValueObjectFields]: FieldValueType< + ValueObjectFields[F], + Lookup, Ns>, 'valueObjects'>, Name>, F>, Parts, CodecTypes, ParameterizedOutputs, @@ -134,8 +137,12 @@ type ElementType< CodecTypes, ParameterizedOutputs extends ParameterizedOutputTypesFunction, Side extends ValueTypeSide, -> = FieldType extends { readonly kind: 'valueObject'; readonly name: infer Name } - ? ValueObjectType +> = FieldType extends { + readonly kind: 'valueObject'; + readonly namespaceId: infer Ns; + readonly entityName: infer Name; +} + ? ValueObjectType : FieldType extends { readonly kind: 'union'; readonly members: infer Members extends readonly unknown[]; @@ -215,16 +222,20 @@ type FieldValueType< ParameterizedOutputs, Side > - : // `ElementType` types value objects too; this branch keys their instantiations by name alone, which costs less. + : // `ElementType` types value objects too; this branch keys their instantiations by namespace and name alone, which costs less. Field extends { - readonly type: { readonly kind: 'valueObject'; readonly name: infer Name }; + readonly type: { + readonly kind: 'valueObject'; + readonly namespaceId: infer Ns; + readonly entityName: infer Name; + }; readonly many?: infer Many; readonly dict?: infer Dictionary; } ? WithModifiers< BaseType< Override, - ValueObjectType, + ValueObjectType, Side >, NullableFlag, @@ -248,19 +259,14 @@ type FieldValueType< > : never; -type NamespacePartsOf = NamespaceParts< - Lookup, 'valueObjects'>, - Lookup ->; - /** - * `FieldOutputTypes` (`Side` `'output'`) or `FieldInputTypes` (`'input'`) of a contract whose domain namespaces are `DomainNamespaces`, computed from the contract's literal shape. Both an emitted `contract.d.ts` and a contract written with `defineContract` type their fields through it. + * `FieldOutputTypes` (`Side` `'output'`) or `FieldInputTypes` (`'input'`) of a contract whose domain namespaces are `DomainNamespaces` and whose storage namespaces are `StorageNamespaces`, computed from the contract's literal shape. It reads both planes: the domain for the fields, and storage for the value objects the fields refer to, so it always takes both. Both an emitted `contract.d.ts` and a contract written with `defineContract` type their fields through it. * * Each value is typed by, in order: * - * 1. its override for that side, `{ output?, input? }`, from `Overrides` at `[namespace]['models'][model][field]` for a model field, or at `[namespace]['valueObjects'][valueObject][field]` for a value object field. An override is the type of a field that the contract's shape and the codec types cannot give: the members of the value set it holds, the type its codec renders as text, or the type a family's storage entry gives it; + * 1. its override for that side, `{ output?, input? }`, from `Overrides` at `[namespace]['models'][model][field]` for a model field, or at `[storage namespace]['valueObjects'][valueObject][field]` for a value object field. An override is the type of a field that the contract's shape and the codec types cannot give: the members of the value set it holds, the type its codec renders as text, or the type a family's storage entry gives it; * 2. for a scalar field, on the output side, the type a pack's type function in `ParameterizedOutputs` gives it for its type parameters (`ParameterizedOutputType`), else its codec's `output` or `input` type, or `unknown` for a codec `CodecTypes` lacks; - * 3. for a value object field, an object of the value object's field types from the same namespace's `valueObjects`, computed by these rules; + * 3. for a value object field, an object of the field types of the value object its reference names in `StorageNamespaces`, at `[namespaceId]['entries']['valueObject'][entityName]`, computed by these rules; * 4. for a union field, the union of its members' types, each a codec's plain type or a value object's type; * * then wrapped in `ReadonlyArray` for a list and in `Readonly>` for a dictionary, with `null` for a nullable element or value. A model without fields has `{}` output types and `Record` input types; a value object without fields has `Record` on both sides. @@ -271,10 +277,12 @@ export type FieldTypesFromDomain< ParameterizedOutputs extends ParameterizedOutputTypesFunction, Overrides, Side extends ValueTypeSide, + StorageNamespaces, > = { readonly [Ns in keyof DomainNamespaces]: NamespaceFieldTypes< Lookup, - NamespacePartsOf, + Lookup, + ContractParts, CodecTypes, ParameterizedOutputs, Side @@ -283,6 +291,7 @@ export type FieldTypesFromDomain< type NamespaceFieldTypes< Models, + NamespaceOverrides, Parts, CodecTypes, ParameterizedOutputs extends ParameterizedOutputTypesFunction, @@ -297,7 +306,7 @@ type NamespaceFieldTypes< { readonly [F in keyof Fields]: FieldValueType< Fields[F], - Lookup, 'models'>, M>, F>, + Lookup, M>, F>, Parts, CodecTypes, ParameterizedOutputs, @@ -309,19 +318,23 @@ type NamespaceFieldTypes< }; /** - * The output (`Side` `'output'`) or input (`'input'`) type of each value object of a contract whose domain namespaces are `DomainNamespaces`, keyed by namespace and value object name, by the rules of {@link FieldTypesFromDomain}. + * The output (`Side` `'output'`) or input (`'input'`) type of each value object of a contract whose storage namespaces are `StorageNamespaces`, keyed by storage namespace and value object name, by the rules of {@link FieldTypesFromDomain}. */ -export type ValueObjectTypesFromDomain< - DomainNamespaces, +export type ValueObjectTypesFromStorage< + StorageNamespaces, CodecTypes, ParameterizedOutputs extends ParameterizedOutputTypesFunction, Overrides, Side extends ValueTypeSide, > = { - readonly [Ns in keyof DomainNamespaces]: { - readonly [V in keyof Lookup]: ValueObjectType< + readonly [Ns in keyof StorageNamespaces]: { + readonly [V in keyof Lookup< + Lookup, + 'valueObject' + >]: ValueObjectType< + Ns, V, - NamespacePartsOf, + ContractParts, CodecTypes, ParameterizedOutputs, Side diff --git a/packages/1-framework/0-foundation/contract/src/storage-value-objects.ts b/packages/1-framework/0-foundation/contract/src/storage-value-objects.ts new file mode 100644 index 000000000000..59431f40ff86 --- /dev/null +++ b/packages/1-framework/0-foundation/contract/src/storage-value-objects.ts @@ -0,0 +1,46 @@ +import { blindCast } from '@internal/utils/casts'; +import type { ContractValueObject } from './domain-types'; +import type { EntityCoordinate } from './entity-coordinate'; +import { isPlainRecord } from './is-plain-record'; +import type { StorageBase } from './types'; + +function valueObjectsOf( + storage: Pick, + namespaceId: string, +): Readonly> { + const slot = Object.hasOwn(storage.namespaces, namespaceId) + ? storage.namespaces[namespaceId]?.entries?.['valueObject'] + : undefined; + return isPlainRecord(slot) + ? blindCast< + Readonly>, + 'the family validators check each storage valueObject entry against the value object schema' + >(slot) + : {}; +} + +/** The storage value object a reference names, or `undefined` when storage does not have it. */ +export function valueObjectAt( + storage: Pick, + reference: EntityCoordinate<'storage', 'valueObject'>, +): ContractValueObject | undefined { + const valueObjects = valueObjectsOf(storage, reference.namespaceId); + return Object.hasOwn(valueObjects, reference.entityName) + ? valueObjects[reference.entityName] + : undefined; +} + +/** Each value object in storage, in every namespace, with its coordinate. */ +export function valueObjectsWithCoordinates( + storage: Pick, +): ReadonlyArray<{ + readonly coordinate: EntityCoordinate<'storage', 'valueObject'>; + readonly valueObject: ContractValueObject; +}> { + return Object.keys(storage.namespaces).flatMap((namespaceId) => + Object.entries(valueObjectsOf(storage, namespaceId)).map(([entityName, valueObject]) => ({ + coordinate: { plane: 'storage', namespaceId, entityKind: 'valueObject', entityName }, + valueObject, + })), + ); +} diff --git a/packages/1-framework/0-foundation/contract/src/validate-domain.ts b/packages/1-framework/0-foundation/contract/src/validate-domain.ts index 704ec7605e75..796be8fb26f8 100644 --- a/packages/1-framework/0-foundation/contract/src/validate-domain.ts +++ b/packages/1-framework/0-foundation/contract/src/validate-domain.ts @@ -1,7 +1,8 @@ -import { blindCast } from '@internal/utils/casts'; import { ContractValidationError } from './contract-validation-error'; import type { CrossReference } from './cross-reference'; import type { ContractWithDomain } from './domain-envelope'; +import { fieldLike, fieldModifierProblems, valueObjectTypeProblems } from './field-problems'; +import { isPlainRecord } from './is-plain-record'; import { asNamespaceId, type NamespaceId } from './namespace-id'; export interface DomainRelationShape { @@ -60,8 +61,24 @@ function* iterateIndexedModels(index: ModelIndex): IterableIterator + isPlainRecord(namespace) && Object.hasOwn(namespace, 'valueObjects') + ? [ + `domain.namespaces.${namespaceId}.valueObjects: contracts no longer store value objects in the domain plane; they are storage entities under storage.namespaces..entries.valueObject, so re-emit the contract from its source, or rewrite a stored contract such as a migration snapshot with value-objects-in-storage.ts, which ships in the value-objects-in-storage scripts of the Prisma 8 upgrade guide`, + ] + : [], + ); +} + export function validateContractDomain(contract: DomainContractShape): void { - const errors: string[] = []; + const errors: string[] = [...removedDomainKeyProblems(contract)]; const modelIndex = indexDomainModels(contract); validateRoots(contract, modelIndex, errors); @@ -71,7 +88,7 @@ export function validateContractDomain(contract: DomainContractShape): void { validateDiscriminators(modelIndex, errors); validateOwnership(contract, modelIndex, errors); validateValueObjectReferences(contract, errors); - validateFieldModifiers(modelIndex, contract, errors); + validateFieldModifiers(modelIndex, errors); if (errors.length > 0) { throw new ContractValidationError( @@ -258,112 +275,29 @@ function validateOwnership( } } -interface FieldTypeLike { - readonly kind?: string; - readonly name?: string; - readonly members?: readonly FieldTypeLike[]; -} - -interface FieldLike { - readonly type?: FieldTypeLike; - readonly many?: unknown; - readonly elementNullable?: unknown; - readonly dict?: boolean; -} - function validateValueObjectReferences(contract: DomainContractShape, errors: string[]): void { - const voNamesByNamespace = new Map>(); - for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) { - const namespaceId = asNamespaceId(namespaceKey); - voNamesByNamespace.set(namespaceId, new Set(Object.keys(namespace.valueObjects ?? {}))); - } - - function checkType( - type: FieldTypeLike | undefined, - location: string, - namespaceId: NamespaceId, - ): void { - if (!type) return; - const voNames = voNamesByNamespace.get(namespaceId) ?? new Set(); - if (type.kind === 'valueObject' && type.name && !voNames.has(type.name)) { - errors.push( - `${location} references value object "${namespaceId}:${type.name}" which does not exist in that namespace's valueObjects`, - ); - return; - } - if (type.kind === 'union') { - for (const member of type.members ?? []) checkType(member, location, namespaceId); - } - } - for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) { const namespaceId = asNamespaceId(namespaceKey); for (const [modelName, model] of Object.entries(namespace.models)) { for (const [fieldName, field] of Object.entries(model.fields)) { - const f = blindCast( - field, - ); - checkType(f?.type, `Model "${namespaceId}:${modelName}" field "${fieldName}"`, namespaceId); - } - } - for (const [voName, vo] of Object.entries(namespace.valueObjects ?? {})) { - for (const [fieldName, field] of Object.entries(vo.fields)) { - const f = blindCast( - field, - ); - checkType( - f?.type, - `Value object "${namespaceId}:${voName}" field "${fieldName}"`, - namespaceId, + errors.push( + ...valueObjectTypeProblems( + contract.storage, + fieldLike(field)?.type, + `Model "${namespaceId}:${modelName}" field "${fieldName}"`, + ), ); } } } } -function validateFieldModifiers( - modelIndex: ModelIndex, - contract: DomainContractShape, - errors: string[], -): void { - function validateField(location: string, field: unknown): void { - const candidate = blindCast< - FieldLike | undefined, - 'domain fields are runtime-validated shapes' - >(field); - const many = candidate?.many; - const isList = - typeof many === 'object' && - many !== null && - !Array.isArray(many) && - Object.keys(many).length === 1 && - 'elementNullable' in many && - typeof many.elementNullable === 'boolean'; - - if (many !== undefined && many !== false && !isList) { - errors.push( - `${location} has invalid "many"; expected false or an elementNullable descriptor`, - ); - } - if (candidate?.elementNullable !== undefined) { - errors.push(`${location} cannot have sibling "elementNullable"; put it inside "many"`); - } - if (isList && candidate?.dict) { - errors.push(`${location} cannot have both "many" and "dict" modifiers`); - } - } - +function validateFieldModifiers(modelIndex: ModelIndex, errors: string[]): void { for (const { namespaceId, name: modelName, model } of iterateIndexedModels(modelIndex)) { for (const [fieldName, field] of Object.entries(model.fields)) { - validateField(`Model "${namespaceId}:${modelName}" field "${fieldName}"`, field); - } - } - for (const [namespaceKey, namespace] of Object.entries(contract.domain.namespaces)) { - const namespaceId = asNamespaceId(namespaceKey); - for (const [voName, vo] of Object.entries(namespace.valueObjects ?? {})) { - for (const [fieldName, field] of Object.entries(vo.fields)) { - validateField(`Value object "${namespaceId}:${voName}" field "${fieldName}"`, field); - } + errors.push( + ...fieldModifierProblems(`Model "${namespaceId}:${modelName}" field "${fieldName}"`, field), + ); } } } diff --git a/packages/1-framework/0-foundation/contract/src/validate-storage-value-objects.ts b/packages/1-framework/0-foundation/contract/src/validate-storage-value-objects.ts new file mode 100644 index 000000000000..d09f9f10e445 --- /dev/null +++ b/packages/1-framework/0-foundation/contract/src/validate-storage-value-objects.ts @@ -0,0 +1,49 @@ +import { ContractValidationError } from './contract-validation-error'; +import { + type FieldLike, + fieldLike, + fieldModifierProblems, + valueObjectTypeProblems, +} from './field-problems'; +import { isPlainRecord } from './is-plain-record'; +import { valueObjectsWithCoordinates } from './storage-value-objects'; +import type { StorageBase } from './types'; + +/** + * Checks the fields of the value objects in storage (ADR 309): each value object a field refers to is in storage, each value set a member names is in storage, and each field's `many` and `dict` are well formed. Both families run it beside `validateContractDomain`, which checks the references from the domain to storage. + */ +export function validateStorageValueObjects(storage: Pick): void { + const errors = valueObjectsWithCoordinates(storage).flatMap( + ({ coordinate: { namespaceId, entityName }, valueObject }) => + Object.entries(valueObject.fields).flatMap(([fieldName, field]) => { + const location = `Value object "${namespaceId}:${entityName}" field "${fieldName}"`; + const valueSetProblem = missingValueSetProblem(storage, fieldLike(field)?.valueSet); + return [ + ...valueObjectTypeProblems(storage, fieldLike(field)?.type, location), + ...(valueSetProblem === undefined ? [] : [`${location} ${valueSetProblem}`]), + ...fieldModifierProblems(location, field), + ]; + }), + ); + if (errors.length > 0) { + throw new ContractValidationError( + `Contract storage validation failed:\n- ${errors.join('\n- ')}`, + 'storage', + ); + } +} + +function missingValueSetProblem( + storage: Pick, + valueSet: FieldLike['valueSet'], +): string | undefined { + if (valueSet === undefined) return undefined; + const namespaceId = valueSet.namespaceId ?? ''; + const entityName = valueSet.entityName ?? ''; + const slot = Object.hasOwn(storage.namespaces, namespaceId) + ? storage.namespaces[namespaceId]?.entries?.['valueSet'] + : undefined; + return isPlainRecord(slot) && Object.hasOwn(slot, entityName) + ? undefined + : `refers to value set "${entityName}" in storage namespace "${namespaceId}", which storage.namespaces.${namespaceId}.entries.valueSet does not have`; +} diff --git a/packages/1-framework/0-foundation/contract/src/value-set-ref.ts b/packages/1-framework/0-foundation/contract/src/value-set-ref.ts index 0ac325600a71..4afb5a21afd7 100644 --- a/packages/1-framework/0-foundation/contract/src/value-set-ref.ts +++ b/packages/1-framework/0-foundation/contract/src/value-set-ref.ts @@ -1,31 +1,14 @@ +import type { EntityCoordinate } from './entity-coordinate'; + /** * Entity coordinate for a domain enum or storage value-set reference. * - * Field-name identity with the framework's `EntityCoordinate` type - * (packages/1-framework/1-core/framework-components/src/ir/storage.ts). - * Foundation-contract cannot import framework-components (dependency points - * the other way), so this is a standalone mirror of that shape plus the - * cross-space discriminator. + * Every `valueSet` reference stays in its plane: a domain field refers to a domain enum, and a storage column or check to a storage value set. * - * One-vocabulary rule (ADR 224): `entityKind` is equal to the entries slot - * key the referenced entity lives under — `'enum'` for domain's `enum` slot, - * `'valueSet'` for storage's `entries.valueSet` slot. No consumer-side - * translation between the kind string and the slot key. + * `namespaceId` admits the `UNBOUND_NAMESPACE_ID` (`__unbound__`) sentinel for single-namespace (unbound) references. * - * Every `valueSet` reference is intra-plane (domain field → domain enum; - * storage column/check → storage value-set). The directional invariant: - * domain may reference storage; storage may never reference domain. - * - * `namespaceId` admits the `UNBOUND_NAMESPACE_ID` (`__unbound__`) sentinel - * for single-namespace (unbound) references. - * - * `spaceId` is the cross-space discriminator: absent ⇒ local (same - * contract-space); present ⇒ cross-space. No separate tag field. + * `spaceId` is the cross-space discriminator: absent ⇒ local (same contract-space); present ⇒ cross-space. No separate tag field. */ -export interface ValueSetRef { - readonly plane: 'domain' | 'storage'; - readonly namespaceId: string; - readonly entityKind: 'enum' | 'valueSet'; - readonly entityName: string; +export interface ValueSetRef extends EntityCoordinate<'domain' | 'storage', 'enum' | 'valueSet'> { readonly spaceId?: string; } diff --git a/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts b/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts index dae0be7f0dde..7ef6e14c8e2d 100644 --- a/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts +++ b/packages/1-framework/0-foundation/contract/test/canonicalization.test.ts @@ -6,8 +6,6 @@ function crossRef(model: string, namespace = 'default') { return { namespace: asNamespaceId(namespace), model }; } -import { blindCast } from '@internal/utils/casts'; -import { ifDefined } from '@internal/utils/defined'; import { type CanonicalizeContractOptions, canonicalizeContract as canonicalizeContractRaw, @@ -17,7 +15,7 @@ import { import { createPreserveEmptyPredicate, type PathPattern } from '../src/canonicalization-path-match'; import { createStorageSort, type NamedArraySortTarget } from '../src/canonicalization-storage-sort'; import type { Contract } from '../src/contract-types'; -import type { ContractModelBase, ContractValueObject } from '../src/domain-types'; +import type { ContractModelBase } from '../src/domain-types'; import { coreHash, profileHash } from '../src/types'; import { applicationDomainOf } from './support/application-domain-of'; @@ -69,6 +67,10 @@ function minimal(overrides?: Record): Contract { domain: _domain, ...rest } = overrides ?? {}; + const storageNamespaces = + valueObjects === undefined + ? {} + : { [UNBOUND]: { id: UNBOUND, entries: { valueObject: valueObjects } } }; return { targetFamily: 'sql', target: 'postgres', @@ -76,18 +78,8 @@ function minimal(overrides?: Record): Contract { domain: domainOverride !== undefined ? (domainOverride as Contract['domain']) - : applicationDomainOf({ - models: models as Record, - ...ifDefined( - 'valueObjects', - valueObjects !== undefined - ? blindCast, 'canonicalization test fixtures'>( - valueObjects, - ) - : undefined, - ), - }), - storage: { storageHash: coreHash('stub'), namespaces: {} }, + : applicationDomainOf({ models: models as Record }), + storage: { storageHash: coreHash('stub'), namespaces: storageNamespaces }, extensions: {}, capabilities: {}, meta: {}, @@ -374,10 +366,11 @@ describe('default omission', () => { expect(nameField).not.toHaveProperty('many'); const cityField = drill( result, - 'domain', + 'storage', 'namespaces', UNBOUND, - 'valueObjects', + 'entries', + 'valueObject', 'Address', 'fields', 'city', @@ -828,8 +821,8 @@ describe('orderTopLevel', () => { }); }); -describe('canonicalize with valueObjects', () => { - it('includes valueObjects under domain.namespaces when present', () => { +describe('canonicalize with value objects', () => { + it('includes value objects under storage.namespaces.*.entries.valueObject when present', () => { const contract = minimal({ valueObjects: { Address: { @@ -841,7 +834,7 @@ describe('canonicalize with valueObjects', () => { }, }); const result = canonicalizeContractToObject(contract); - const vo = drill(result, 'domain', 'namespaces', UNBOUND, 'valueObjects'); + const vo = drill(result, 'storage', 'namespaces', UNBOUND, 'entries', 'valueObject'); expect(vo).toHaveProperty('Address'); }); diff --git a/packages/1-framework/0-foundation/contract/test/contract-types.test-d.ts b/packages/1-framework/0-foundation/contract/test/contract-types.test-d.ts index a348affe6fb2..241e76d1c8f7 100644 --- a/packages/1-framework/0-foundation/contract/test/contract-types.test-d.ts +++ b/packages/1-framework/0-foundation/contract/test/contract-types.test-d.ts @@ -1,5 +1,5 @@ import { expectTypeOf, test } from 'vitest'; -import type { Contract } from '../src/contract-types'; +import type { Contract, ContractValueObjectAt } from '../src/contract-types'; import type { CrossReference } from '../src/cross-reference'; import type { ContractField, ContractModel, ModelStorageBase } from '../src/domain-types'; import type { NamespaceId } from '../src/namespace-id'; @@ -115,3 +115,22 @@ test('old many: true does not satisfy ContractField', () => { test('ContractField has no sibling elementNullable property', () => { expectTypeOf().not.toHaveProperty('elementNullable'); }); + +test('ContractValueObjectAt finds the value object a reference names in its storage namespace', () => { + type Point = { readonly fields: { readonly x: ContractField } }; + type Geo = { readonly fields: { readonly lat: ContractField } }; + type Storage = { + readonly namespaces: { + readonly public: { readonly entries: { readonly valueObject: { readonly Point: Point } } }; + readonly geo: { readonly entries: { readonly valueObject: { readonly Point: Geo } } }; + }; + }; + type Ref = { + readonly namespaceId: Ns; + readonly entityName: Name; + }; + expectTypeOf>>().toEqualTypeOf(); + expectTypeOf>>().toEqualTypeOf(); + expectTypeOf>>().toBeNever(); + expectTypeOf>>().toBeNever(); +}); diff --git a/packages/1-framework/0-foundation/contract/test/contract-value-object-schema.test.ts b/packages/1-framework/0-foundation/contract/test/contract-value-object-schema.test.ts new file mode 100644 index 000000000000..dcce99c82e4c --- /dev/null +++ b/packages/1-framework/0-foundation/contract/test/contract-value-object-schema.test.ts @@ -0,0 +1,85 @@ +import { type } from 'arktype'; +import { describe, expect, it } from 'vitest'; +import { + ContractFieldTypeSchema, + ContractValueObjectSchema, +} from '../src/contract-value-object-schema'; + +const text = { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false }; +const addressReference = { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', +}; + +describe('ContractValueObjectSchema', () => { + it('accepts scalar, value object and union members, lists, dictionaries and value sets', () => { + const valueObject = { + fields: { + street: text, + home: { type: addressReference, nullable: true, many: { elementNullable: false } }, + either: { + type: { kind: 'union', members: [text.type, addressReference] }, + nullable: false, + }, + tags: { ...text, dict: true }, + kind: { + ...text, + valueSet: { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueSet', + entityName: 'Kind', + }, + }, + }, + }; + expect(ContractValueObjectSchema(valueObject)).toEqual(valueObject); + }); + + it.each([ + ['a member without nullable', { street: { type: text.type } }], + ['a member with dict false', { street: { ...text, dict: false } }], + ['a member with an unknown key', { street: { ...text, column: 'street' } }], + [ + 'a member that names its value object the old way', + { home: { ...text, type: { kind: 'valueObject', name: 'Address' } } }, + ], + [ + 'a member value set in the domain plane', + { + kind: { + ...text, + valueSet: { + plane: 'domain', + namespaceId: 'public', + entityKind: 'enum', + entityName: 'Kind', + }, + }, + }, + ], + ])('refuses %s', (_case, fields) => { + expect(ContractValueObjectSchema({ fields })).toBeInstanceOf(type.errors); + }); +}); + +describe('ContractFieldTypeSchema', () => { + it('accepts a scalar, a value object reference and a union of those', () => { + const union = { kind: 'union', members: [text.type, addressReference] }; + expect( + [text.type, addressReference, union].map((fieldType) => ContractFieldTypeSchema(fieldType)), + ).toEqual([text.type, addressReference, union]); + }); + + it('refuses a union inside a union', () => { + expect( + ContractFieldTypeSchema({ + kind: 'union', + members: [text.type, { kind: 'union', members: [text.type] }], + }), + ).toBeInstanceOf(type.errors); + }); +}); diff --git a/packages/1-framework/0-foundation/contract/test/field-value-types.types.test-d.ts b/packages/1-framework/0-foundation/contract/test/field-value-types.types.test-d.ts index 883427945d1b..473028791f7e 100644 --- a/packages/1-framework/0-foundation/contract/test/field-value-types.types.test-d.ts +++ b/packages/1-framework/0-foundation/contract/test/field-value-types.types.test-d.ts @@ -3,7 +3,7 @@ import type { FieldTypesFromDomain, ParameterizedOutputTypesFunction, StoredValueType, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '../src/exports/types'; type CodecTypes = { @@ -32,6 +32,14 @@ interface CharOutputTypes extends ParameterizedOutputTypesFunction { type Scalar = { readonly kind: 'scalar'; readonly codecId: CodecId }; +type ValueObjectRef = { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: Ns; + readonly entityKind: 'valueObject'; + readonly entityName: Name; +}; + type Domain = { readonly app: { readonly models: { @@ -74,59 +82,80 @@ type Domain = { readonly type: Scalar<'test/text@1'>; }; readonly homeWithoutNullable: { - readonly type: { readonly kind: 'valueObject'; readonly name: 'Point' }; + readonly type: ValueObjectRef<'Point'>; }; readonly home: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: ValueObjectRef<'Address'>; }; readonly pastHomes: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: ValueObjectRef<'Address'>; readonly many: { readonly elementNullable: false }; }; + readonly elsewhere: { + readonly nullable: false; + readonly type: ValueObjectRef<'Address', 'other'>; + }; readonly contact: { readonly nullable: false; readonly type: { readonly kind: 'union'; - readonly members: readonly [ - Scalar<'test/text@1'>, - { readonly kind: 'valueObject'; readonly name: 'Address' }, - ]; + readonly members: readonly [Scalar<'test/text@1'>, ValueObjectRef<'Address'>]; }; }; }; }; readonly Empty: { readonly fields: Record }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { readonly nullable: false; readonly type: Scalar<'test/text@1'> }; - readonly code: { - readonly nullable: false; - readonly type: Scalar<'test/char@1'> & { readonly typeParams: { readonly length: 5 } }; + }; +}; + +type Storage = { + readonly shared: { + readonly entries: { + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly street: { readonly nullable: false; readonly type: Scalar<'test/text@1'> }; + readonly code: { + readonly nullable: false; + readonly type: Scalar<'test/char@1'> & { + readonly typeParams: { readonly length: 5 }; + }; + }; + readonly geo: { + readonly nullable: true; + readonly type: ValueObjectRef<'Point'>; + }; + readonly kind: { readonly nullable: true; readonly type: Scalar<'test/text@1'> }; + readonly meta: { readonly nullable: false; readonly type: Scalar<'test/json@1'> }; }; - readonly geo: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Point' }; + }; + readonly Point: { + readonly fields: { + readonly x: { readonly nullable: false; readonly type: Scalar<'test/int@1'> }; }; - readonly kind: { readonly nullable: true; readonly type: Scalar<'test/text@1'> }; - readonly meta: { readonly nullable: false; readonly type: Scalar<'test/json@1'> }; }; - }; - readonly Point: { - readonly fields: { - readonly x: { readonly nullable: false; readonly type: Scalar<'test/int@1'> }; + readonly Nothing: { readonly fields: Record }; + readonly Tree: { + readonly fields: { + readonly children: { + readonly nullable: false; + readonly type: ValueObjectRef<'Tree'>; + readonly many: { readonly elementNullable: false }; + }; + }; }; }; - readonly Nothing: { readonly fields: Record }; - readonly Tree: { - readonly fields: { - readonly children: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Tree' }; - readonly many: { readonly elementNullable: false }; + }; + }; + readonly other: { + readonly entries: { + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly elsewhere: { readonly nullable: false; readonly type: Scalar<'test/int@1'> }; }; }; }; @@ -142,6 +171,8 @@ type Overrides = { readonly settings: { readonly output: { readonly theme: string } }; }; }; + }; + readonly shared: { readonly valueObjects: { readonly Address: { readonly kind: { readonly output: 'home' | 'work'; readonly input: 'home' | 'work' }; @@ -151,8 +182,15 @@ type Overrides = { }; }; -type Output = FieldTypesFromDomain; -type Input = FieldTypesFromDomain; +type Output = FieldTypesFromDomain< + Domain, + CodecTypes, + CharOutputTypes, + Overrides, + 'output', + Storage +>; +type Input = FieldTypesFromDomain; type User = Output['app']['User']; type UserInput = Input['app']['User']; @@ -223,6 +261,15 @@ describe('field types from a contract domain', () => { expectTypeOf().toEqualTypeOf>(); }); + test('a value object field reads the value object of the storage namespace its reference names', () => { + expectTypeOf().toEqualTypeOf<{ readonly elsewhere: bigint }>(); + }); + + test('the storage namespaces are required, so no value object field goes untyped', () => { + // @ts-expect-error FieldTypesFromDomain reads both planes and takes the storage namespaces + expectTypeOf>(); + }); + test('a union field is the union of its members, a scalar member taking its plain codec type', () => { expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); @@ -234,16 +281,16 @@ describe('field types from a contract domain', () => { }); }); -describe('value object types from a contract domain', () => { - type ValueObjects = ValueObjectTypesFromDomain< - Domain, +describe('value object types from contract storage', () => { + type ValueObjects = ValueObjectTypesFromStorage< + Storage, CodecTypes, CharOutputTypes, Overrides, 'output' >; - type ValueObjectInputs = ValueObjectTypesFromDomain< - Domain, + type ValueObjectInputs = ValueObjectTypesFromStorage< + Storage, CodecTypes, CharOutputTypes, Overrides, @@ -251,17 +298,23 @@ describe('value object types from a contract domain', () => { >; test('each value object is keyed by namespace and name', () => { - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); + + test('a value object is keyed by the storage namespace that holds it', () => { + expectTypeOf().toEqualTypeOf<{ + readonly elsewhere: bigint; + }>(); }); test('a value object without fields is closed on both sides', () => { - expectTypeOf().toEqualTypeOf>(); - expectTypeOf().toEqualTypeOf>(); + expectTypeOf().toEqualTypeOf>(); + expectTypeOf().toEqualTypeOf>(); }); test('a value object that holds itself resolves one level at a time', () => { - type Tree = ValueObjects['app']['Tree']; + type Tree = ValueObjects['shared']['Tree']; expectTypeOf().toEqualTypeOf(); }); }); diff --git a/packages/1-framework/0-foundation/contract/test/storage-value-objects.test.ts b/packages/1-framework/0-foundation/contract/test/storage-value-objects.test.ts new file mode 100644 index 000000000000..2c3455898575 --- /dev/null +++ b/packages/1-framework/0-foundation/contract/test/storage-value-objects.test.ts @@ -0,0 +1,100 @@ +import { blindCast } from '@internal/utils/casts'; +import { describe, expect, it } from 'vitest'; +import { valueObjectAt, valueObjectsWithCoordinates } from '../src/storage-value-objects'; +import type { StorageNamespace } from '../src/types'; + +const address = { + fields: { street: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } } }, +} as const; + +const storage = { + namespaces: { + public: { id: 'public', entries: { valueObject: { Address: address } } }, + }, +}; + +describe('valueObjectAt', () => { + it('reads the value object a reference names', () => { + expect( + valueObjectAt(storage, { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }), + ).toEqual(address); + }); + + it('is undefined for a name or a namespace storage does not have', () => { + expect({ + name: valueObjectAt(storage, { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Missing', + }), + namespace: valueObjectAt(storage, { + plane: 'storage', + namespaceId: 'billing', + entityKind: 'valueObject', + entityName: 'Address', + }), + }).toEqual({ name: undefined, namespace: undefined }); + }); +}); + +describe('valueObjectAt with an unusual namespace', () => { + it('is undefined for a namespace id that names an Object.prototype property, or a namespace with no entries', () => { + const reference = (namespaceId: string) => + ({ + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName: 'Address', + }) as const; + expect({ + proto: valueObjectAt(storage, reference('__proto__')), + noEntries: valueObjectAt( + { + namespaces: { + bare: blindCast({ id: 'bare' }), + }, + }, + reference('bare'), + ), + }).toEqual({ proto: undefined, noEntries: undefined }); + }); +}); + +describe('valueObjectsWithCoordinates', () => { + it('lists each value object in every storage namespace with its coordinate', () => { + expect( + valueObjectsWithCoordinates({ + namespaces: { + public: { id: 'public', entries: { valueObject: { Address: address }, table: {} } }, + billing: { id: 'billing', entries: { valueObject: { Address: address } } }, + bare: blindCast({ id: 'bare' }), + }, + }), + ).toEqual([ + { + coordinate: { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, + valueObject: address, + }, + { + coordinate: { + plane: 'storage', + namespaceId: 'billing', + entityKind: 'valueObject', + entityName: 'Address', + }, + valueObject: address, + }, + ]); + }); +}); diff --git a/packages/1-framework/0-foundation/contract/test/support/application-domain-of.ts b/packages/1-framework/0-foundation/contract/test/support/application-domain-of.ts index 225e352f71c1..45a53b308327 100644 --- a/packages/1-framework/0-foundation/contract/test/support/application-domain-of.ts +++ b/packages/1-framework/0-foundation/contract/test/support/application-domain-of.ts @@ -1,6 +1,6 @@ import type { ApplicationDomain } from '../../src/domain-envelope'; import { UNBOUND_DOMAIN_NAMESPACE_ID } from '../../src/domain-envelope'; -import type { ContractModelBase, ContractValueObject } from '../../src/domain-types'; +import type { ContractModelBase } from '../../src/domain-types'; /** * Single-namespace application-domain authoring convenience for this package's @@ -11,17 +11,13 @@ import type { ContractModelBase, ContractValueObject } from '../../src/domain-ty */ export function applicationDomainOf(params: { readonly models?: Record; - readonly valueObjects?: Record; readonly namespaceId?: string; }): ApplicationDomain { const namespaceId = params.namespaceId ?? UNBOUND_DOMAIN_NAMESPACE_ID; const models = params.models ?? {}; return { namespaces: { - [namespaceId]: { - models, - ...(params.valueObjects !== undefined ? { valueObjects: params.valueObjects } : {}), - }, + [namespaceId]: { models }, }, }; } diff --git a/packages/1-framework/0-foundation/contract/test/validate-domain.test.ts b/packages/1-framework/0-foundation/contract/test/validate-domain.test.ts index 2dad94641224..caef2b4e72d3 100644 --- a/packages/1-framework/0-foundation/contract/test/validate-domain.test.ts +++ b/packages/1-framework/0-foundation/contract/test/validate-domain.test.ts @@ -6,10 +6,7 @@ function crossRef(model: string, namespace: string = UNBOUND_DOMAIN_NAMESPACE_ID return { namespace: asNamespaceId(namespace), model }; } -import { blindCast } from '@internal/utils/casts'; -import { ifDefined } from '@internal/utils/defined'; import { ContractValidationError } from '../src/contract-validation-error'; -import type { ContractValueObject } from '../src/domain-types'; import type { DomainContractShape } from '../src/validate-domain'; import { validateContractDomain } from '../src/validate-domain'; import { applicationDomainOf } from './support/application-domain-of'; @@ -47,22 +44,30 @@ function makeValidContract(overrides: Record = {}): DomainContr domain: domainOverride !== undefined ? (domainOverride as DomainContractShape['domain']) - : applicationDomainOf({ - models, - ...ifDefined( - 'valueObjects', - valueObjects !== undefined - ? blindCast< - Record, - 'validate-domain fixtures use structural value-object shapes' - >(valueObjects) - : undefined, - ), - }), + : applicationDomainOf({ models }), + storage: { + namespaces: { + [UNBOUND_DOMAIN_NAMESPACE_ID]: { + id: UNBOUND_DOMAIN_NAMESPACE_ID, + entries: valueObjects !== undefined ? { valueObject: valueObjects } : {}, + }, + }, + }, ...rest, } as DomainContractShape; } +/** A field type that refers to the value object `name` in the storage namespace `namespaceId`. */ +function valueObjectType(name: string, namespaceId: string = UNBOUND_DOMAIN_NAMESPACE_ID) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName: name, + }; +} + describe('validateContractDomain()', () => { describe('namespace coordinate identity', () => { it('treats the same model name in different namespaces as distinct models', () => { @@ -696,128 +701,182 @@ describe('validateContractDomain()', () => { }); describe('value object reference validation', () => { - it('accepts valid value object references from model fields', () => { + const address = { + fields: { + street: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, + city: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, + }, + }; + + it('accepts a model field that refers to a value object in storage', () => { const contract = makeValidContract({ roots: { users: crossRef('User') }, models: { User: makeMinimalModel({ fields: { id: { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, - homeAddress: { nullable: false, type: { kind: 'valueObject', name: 'Address' } }, + homeAddress: { nullable: false, type: valueObjectType('Address') }, }, }), }, - valueObjects: { - Address: { - fields: { - street: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, - city: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, - }, - }, - }, + valueObjects: { Address: address }, }); expect(() => validateContractDomain(contract)).not.toThrow(); }); - it('rejects nonexistent value object reference from model field', () => { + it('refuses a model field that refers to a value object storage does not have, naming the field and the coordinate', () => { const contract = makeValidContract({ roots: { users: crossRef('User') }, models: { User: makeMinimalModel({ fields: { id: { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, - addr: { nullable: false, type: { kind: 'valueObject', name: 'Missing' } }, + addr: { nullable: false, type: valueObjectType('Missing') }, }, }), }, + valueObjects: { Address: address }, }); - expect(() => validateContractDomain(contract)).toThrow(/Missing.*does not exist/); + expect(() => validateContractDomain(contract)).toThrow( + `Model "__unbound__:User" field "addr" refers to value object "Missing" in storage namespace "__unbound__", which storage.namespaces.__unbound__.entries.valueObject does not have`, + ); }); - it('accepts self-referencing value object', () => { + it('refuses a reference into a storage namespace the contract does not have', () => { const contract = makeValidContract({ - roots: {}, - models: {}, - valueObjects: { - TreeNode: { + roots: { users: crossRef('User') }, + models: { + User: makeMinimalModel({ + fields: { addr: { nullable: false, type: valueObjectType('Address', 'billing') } }, + }), + }, + valueObjects: { Address: address }, + }); + expect(() => validateContractDomain(contract)).toThrow( + `Model "__unbound__:User" field "addr" refers to value object "Address" in storage namespace "billing", which storage.namespaces.billing.entries.valueObject does not have`, + ); + }); + + it('refuses a value object reference that does not point at a storage value object', () => { + const contract = makeValidContract({ + roots: { users: crossRef('User') }, + models: { + User: makeMinimalModel({ fields: { - label: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, - child: { nullable: true, type: { kind: 'valueObject', name: 'TreeNode' } }, + addr: { + nullable: false, + type: { ...valueObjectType('Address'), plane: 'domain', entityKind: 'enum' }, + }, }, - }, + }), + }, + valueObjects: { Address: address }, + }); + expect(() => validateContractDomain(contract)).toThrow( + `Model "__unbound__:User" field "addr" refers to a value object with plane "domain" and entityKind "enum"; a value object reference has plane "storage" and entityKind "valueObject"`, + ); + }); + + it('refuses a reference by name, the old shape, and says to re-emit', () => { + const contract = makeValidContract({ + roots: { users: crossRef('User') }, + models: { + User: makeMinimalModel({ + fields: { addr: { nullable: false, type: { kind: 'valueObject', name: 'Address' } } }, + }), + }, + valueObjects: { Address: address }, + }); + expect(() => validateContractDomain(contract)).toThrow( + 'Model "__unbound__:User" field "addr" refers to value object "Address" by name; contracts no longer do: a field refers to its value object by the coordinate of a storage entity under storage.namespaces..entries.valueObject, so re-emit the contract from its source, or rewrite a stored contract such as a migration snapshot with value-objects-in-storage.ts, which ships in the value-objects-in-storage scripts of the Prisma 8 upgrade guide', + ); + }); + + it('accepts a value object whose field refers to another value object', () => { + const contract = makeValidContract({ + roots: { users: crossRef('User') }, + models: { + User: makeMinimalModel({ + fields: { profile: { nullable: false, type: valueObjectType('Profile') } }, + }), + }, + valueObjects: { + Address: address, + Profile: { fields: { home: { nullable: true, type: valueObjectType('Address') } } }, }, }); expect(() => validateContractDomain(contract)).not.toThrow(); }); - it('rejects nonexistent value object reference from another value object', () => { + it('accepts a value object that refers to itself', () => { const contract = makeValidContract({ roots: {}, models: {}, valueObjects: { - Wrapper: { + TreeNode: { fields: { - inner: { nullable: false, type: { kind: 'valueObject', name: 'Nonexistent' } }, + label: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, + child: { nullable: true, type: valueObjectType('TreeNode') }, }, }, }, }); - expect(() => validateContractDomain(contract)).toThrow(/Nonexistent.*does not exist/); + expect(() => validateContractDomain(contract)).not.toThrow(); }); - it('rejects nonexistent value object reference inside union members', () => { + it('refuses a union member that refers to a value object storage does not have', () => { const contract = makeValidContract({ roots: { users: crossRef('User') }, models: { User: makeMinimalModel({ fields: { - id: { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, data: { nullable: false, type: { kind: 'union', - members: [ - { kind: 'scalar', codecId: 'pg/text@1' }, - { kind: 'valueObject', name: 'Ghost' }, - ], + members: [{ kind: 'scalar', codecId: 'pg/text@1' }, valueObjectType('Ghost')], }, }, }, }), }, }); - expect(() => validateContractDomain(contract)).toThrow(/Ghost.*does not exist/); + expect(() => validateContractDomain(contract)).toThrow(/value object "Ghost"/); }); - it('accepts valid value object reference inside union members', () => { + it('accepts a union member that refers to a value object in storage', () => { const contract = makeValidContract({ roots: { users: crossRef('User') }, models: { User: makeMinimalModel({ fields: { - id: { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, data: { nullable: false, type: { kind: 'union', - members: [ - { kind: 'scalar', codecId: 'pg/text@1' }, - { kind: 'valueObject', name: 'Address' }, - ], + members: [{ kind: 'scalar', codecId: 'pg/text@1' }, valueObjectType('Address')], }, }, }, }), }, - valueObjects: { - Address: { - fields: { - street: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, - }, + valueObjects: { Address: address }, + }); + expect(() => validateContractDomain(contract)).not.toThrow(); + }); + + it('refuses value objects in the domain plane, and says where they live now', () => { + const contract = makeValidContract({ + domain: { + namespaces: { + public: { models: {}, valueObjects: { Address: address } }, }, }, + roots: {}, }); - expect(() => validateContractDomain(contract)).not.toThrow(); + expect(() => validateContractDomain(contract)).toThrow( + 'domain.namespaces.public.valueObjects: contracts no longer store value objects in the domain plane; they are storage entities under storage.namespaces..entries.valueObject, so re-emit the contract from its source, or rewrite a stored contract such as a migration snapshot with value-objects-in-storage.ts, which ships in the value-objects-in-storage scripts of the Prisma 8 upgrade guide', + ); }); }); @@ -877,24 +936,5 @@ describe('validateContractDomain()', () => { }); expect(() => validateContractDomain(contract)).toThrow(/sibling "elementNullable"/); }); - - it('rejects old many: true when type checking is bypassed', () => { - const contract = makeValidContract({ - roots: {}, - models: {}, - valueObjects: { - Address: { - fields: { - tags: { - nullable: false, - type: { kind: 'scalar', codecId: 'pg/text@1' }, - many: true, - }, - }, - }, - }, - }); - expect(() => validateContractDomain(contract)).toThrow(/Value object.*invalid "many"/); - }); }); }); diff --git a/packages/1-framework/0-foundation/contract/test/validate-storage-value-objects.test.ts b/packages/1-framework/0-foundation/contract/test/validate-storage-value-objects.test.ts new file mode 100644 index 000000000000..d5c80ee1b99a --- /dev/null +++ b/packages/1-framework/0-foundation/contract/test/validate-storage-value-objects.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, it } from 'vitest'; +import { ContractValidationError } from '../src/contract-validation-error'; +import type { StorageNamespace } from '../src/types'; +import { validateContractDomain } from '../src/validate-domain'; +import { validateStorageValueObjects } from '../src/validate-storage-value-objects'; + +const NAMESPACE = '__unbound__'; +const text = { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }; + +function valueObjectType(name: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: NAMESPACE, + entityKind: 'valueObject', + entityName: name, + }; +} + +function storageWith(entries: StorageNamespace['entries']) { + return { namespaces: { [NAMESPACE]: { id: NAMESPACE, entries } } }; +} + +function validating(entries: StorageNamespace['entries']) { + return () => validateStorageValueObjects(storageWith(entries)); +} + +describe('validateStorageValueObjects', () => { + it('accepts a value object whose field refers to another value object, and one that refers to itself', () => { + expect( + validating({ + valueObject: { + Address: { fields: { street: text } }, + Profile: { fields: { home: { nullable: true, type: valueObjectType('Address') } } }, + TreeNode: { + fields: { label: text, child: { nullable: true, type: valueObjectType('TreeNode') } }, + }, + }, + }), + ).not.toThrow(); + }); + + it('refuses a value object field that refers to a value object storage does not have', () => { + expect( + validating({ + valueObject: { + Wrapper: { fields: { inner: { nullable: false, type: valueObjectType('Nonexistent') } } }, + }, + }), + ).toThrow( + 'Contract storage validation failed:\n- Value object "__unbound__:Wrapper" field "inner" refers to value object "Nonexistent" in storage namespace "__unbound__", which storage.namespaces.__unbound__.entries.valueObject does not have', + ); + }); + + it('refuses a value object member whose storage value set storage does not have', () => { + const member = (entityName: string) => ({ + ...text, + valueSet: { plane: 'storage', entityKind: 'valueSet', namespaceId: NAMESPACE, entityName }, + }); + expect( + validating({ + valueObject: { Tagged: { fields: { role: member('Role'), kind: member('Kind') } } }, + valueSet: { Role: { kind: 'valueSet', values: ['a'] } }, + }), + ).toThrow( + 'Contract storage validation failed:\n- Value object "__unbound__:Tagged" field "kind" refers to value set "Kind" in storage namespace "__unbound__", which storage.namespaces.__unbound__.entries.valueSet does not have', + ); + }); + + it('refuses the old many: true on a value object field', () => { + expect( + validating({ valueObject: { Address: { fields: { tags: { ...text, many: true } } } } }), + ).toThrow(/Value object "__unbound__:Address" field "tags" has invalid "many"/); + }); + + it('throws a storage validation error', () => { + expect( + validating({ valueObject: { Address: { fields: { tags: { ...text, many: true } } } } }), + ).toThrow(ContractValidationError); + }); +}); + +describe('validateContractDomain', () => { + it('leaves the fields of storage value objects to the storage validator', () => { + expect(() => + validateContractDomain({ + roots: {}, + domain: { namespaces: { [NAMESPACE]: { models: {} } } }, + storage: storageWith({ + valueObject: { + Wrapper: { fields: { inner: { nullable: false, type: valueObjectType('Missing') } } }, + }, + }), + }), + ).not.toThrow(); + }); +}); diff --git a/packages/1-framework/0-foundation/contract/test/value-object-field.test-d.ts b/packages/1-framework/0-foundation/contract/test/value-object-field.test-d.ts new file mode 100644 index 000000000000..7db8801e11fb --- /dev/null +++ b/packages/1-framework/0-foundation/contract/test/value-object-field.test-d.ts @@ -0,0 +1,24 @@ +import { expectTypeOf, test } from 'vitest'; +import type { ContractValueObject } from '../src/domain-types'; + +const role = { namespaceId: 'public', entityName: 'Role' } as const; +const storageValueSet = { plane: 'storage', entityKind: 'valueSet', ...role } as const; +const domainEnum = { plane: 'domain', entityKind: 'enum', ...role } as const; +const text = { kind: 'scalar', codecId: 'pg/text@1' } as const; + +test('a value object member refers to a storage value set, never a domain enum', () => { + const storageMember: ContractValueObject = { + fields: { role: { nullable: false, type: text, valueSet: storageValueSet } }, + }; + const domainMember: ContractValueObject = { + fields: { + role: { + nullable: false, + type: text, + // @ts-expect-error a storage entity's field may not refer to the domain plane + valueSet: domainEnum, + }, + }, + }; + expectTypeOf(storageMember).toEqualTypeOf(domainMember); +}); diff --git a/packages/1-framework/0-foundation/contract/tsdown.config.ts b/packages/1-framework/0-foundation/contract/tsdown.config.ts index fa4eb5c8e9eb..dca3c077e11a 100644 --- a/packages/1-framework/0-foundation/contract/tsdown.config.ts +++ b/packages/1-framework/0-foundation/contract/tsdown.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ 'src/exports/resolve-domain-model.ts', 'src/exports/types.ts', 'src/exports/validate-domain.ts', + 'src/exports/validate-storage.ts', 'src/exports/contract-validation-error.ts', 'src/exports/hashing.ts', 'src/exports/hashing-utils.ts', diff --git a/packages/1-framework/1-core/framework-components/src/control/control-migration-types.ts b/packages/1-framework/1-core/framework-components/src/control/control-migration-types.ts index b1e96d05abc0..f30fb480203c 100644 --- a/packages/1-framework/1-core/framework-components/src/control/control-migration-types.ts +++ b/packages/1-framework/1-core/framework-components/src/control/control-migration-types.ts @@ -422,6 +422,8 @@ export interface MigrationRunnerFailureMeta { readonly rowsWithoutValue?: readonly RowsWithoutValue[]; /** The data that still holds a stored enum value whose removal its storage cannot take, when that made a check fail. */ readonly rowsHoldingValue?: RowsHoldingValue; + /** The existing rows that a check the failed operation adds refuses, when that made the operation fail. */ + readonly rowsFailingCheck?: RowsFailingCheck; /** The objects in the database that kept the failed operation from running, such as a view on a table to drop. */ readonly blockingObjects?: readonly BlockingObject[]; /** The differences the schema check after apply found, when that check failed. */ @@ -508,6 +510,16 @@ export interface RowsHoldingValue { readonly keepDataEdit?: KeepDataEdit; } +/** The existing rows that a check the failed operation adds refuses. The failure's message names where the check is and how to find the rows. */ +export interface RowsFailingCheck { + /** The check, as the database names it. */ + readonly check: string; + /** The check the plan drops before it adds this one, when the plan replaces a check. While it holds, the rows cannot be changed to fit the new check. */ + readonly replacedCheck?: string; + /** The position of the operation in the operations of the failing space's plan. */ + readonly operationIndex: number; +} + /** A field that is to become required while existing rows have no value for it. */ export interface RowsWithoutValue { /** The field, when the destination contract names the storage that lacks the value. */ diff --git a/packages/1-framework/1-core/framework-components/src/exports/control.ts b/packages/1-framework/1-core/framework-components/src/exports/control.ts index e41a8f36d311..d9f867fb58fe 100644 --- a/packages/1-framework/1-core/framework-components/src/exports/control.ts +++ b/packages/1-framework/1-core/framework-components/src/exports/control.ts @@ -71,6 +71,7 @@ export type { MigrationScaffoldContext, OpFactoryCall, PlanOrigin, + RowsFailingCheck, RowsHoldingValue, RowsWithoutValue, SchemaEntityCoordinate, diff --git a/packages/1-framework/1-core/framework-components/src/exports/ir.ts b/packages/1-framework/1-core/framework-components/src/exports/ir.ts index ff35c61b9a51..ed2d56146920 100644 --- a/packages/1-framework/1-core/framework-components/src/exports/ir.ts +++ b/packages/1-framework/1-core/framework-components/src/exports/ir.ts @@ -15,6 +15,12 @@ export type { IRNode } from '../ir/ir-node'; export { freezeNode, IRNodeBase } from '../ir/ir-node'; export type { Namespace } from '../ir/namespace'; export { NamespaceBase, UNBOUND_NAMESPACE_ID } from '../ir/namespace'; -export type { EntityCoordinate, Storage } from '../ir/storage'; -export { coordinateKey, elementCoordinates, entityAt, isPlainRecord } from '../ir/storage'; +export type { Storage } from '../ir/storage'; +export { + changedElementCoordinates, + coordinateKey, + elementCoordinates, + entityAt, + isPlainRecord, +} from '../ir/storage'; export type { StorageType } from '../ir/storage-type'; diff --git a/packages/1-framework/1-core/framework-components/src/ir/domain.ts b/packages/1-framework/1-core/framework-components/src/ir/domain.ts index cced223a8a32..424224cff792 100644 --- a/packages/1-framework/1-core/framework-components/src/ir/domain.ts +++ b/packages/1-framework/1-core/framework-components/src/ir/domain.ts @@ -1,5 +1,4 @@ -import type { ApplicationDomain } from '@internal/contract/types'; -import type { EntityCoordinate } from './storage'; +import type { ApplicationDomain, EntityCoordinate } from '@internal/contract/types'; /** * Lazy walk over every named domain entity in a {@link ApplicationDomain}, diff --git a/packages/1-framework/1-core/framework-components/src/ir/storage.ts b/packages/1-framework/1-core/framework-components/src/ir/storage.ts index 0a5f1a43cad8..1d17f8fb4e80 100644 --- a/packages/1-framework/1-core/framework-components/src/ir/storage.ts +++ b/packages/1-framework/1-core/framework-components/src/ir/storage.ts @@ -1,34 +1,12 @@ import { isPlainRecord } from '@internal/contract/is-plain-record'; -import type { StorageBase } from '@internal/contract/types'; +import type { EntityCoordinate, StorageBase } from '@internal/contract/types'; +import { canonicalStringify } from '@internal/utils/canonical-stringify'; import { blindCast } from '@internal/utils/casts'; import type { IRNode } from './ir-node'; import type { Namespace } from './namespace'; export { isPlainRecord }; -/** - * Canonical address for a named entity in Contract IR / Schema IR. - * - * `plane` is `'domain' | 'storage'`: which top-level contract plane the - * entity lives on. Domain-side walks yield `plane: 'domain'` via - * {@link domainElementCoordinates}; {@link elementCoordinates} over storage - * yields `plane: 'storage'`. - * - * Cross-plane references obey a directional invariant: domain → storage is - * allowed; storage → domain is forbidden. That rule is enforced by a - * separate validator, not by constraining this coordinate shape — the - * coordinate carries the axis the validator checks. - * - * Iteration order over namespace properties follows `Object.entries` order; - * consumers that depend on ordering must sort. - */ -export interface EntityCoordinate { - readonly plane: 'domain' | 'storage'; - readonly namespaceId: string; - readonly entityKind: string; - readonly entityName: string; -} - /** * Lazy walk over every named storage entity in a `Storage`-shaped * value, yielded as {@link EntityCoordinate} tuples with @@ -76,6 +54,42 @@ export function coordinateKey( .join('-'); } +/** + * The storage entities that differ between two `Storage`-shaped values: added, removed, or with + * different content. Content is compared as JSON, ignoring key order. Sorted by namespace id, + * entity kind and entity name. With no earlier storage, every entity counts as added. + */ +export function changedElementCoordinates( + from: Pick | null, + to: Pick, +): EntityCoordinate[] { + const contentAt = ( + storage: Pick | null, + coordinate: EntityCoordinate, + ): string | undefined => { + if (storage === null) return undefined; + const entity = entityAt(storage, coordinate); + return entity === undefined + ? undefined + : canonicalStringify(JSON.parse(JSON.stringify(entity))); + }; + const coordinates = new Map(); + for (const coordinate of [ + ...(from === null ? [] : elementCoordinates(from)), + ...elementCoordinates(to), + ]) { + coordinates.set(coordinateKey(coordinate), coordinate); + } + return [...coordinates.values()] + .filter((coordinate) => contentAt(from, coordinate) !== contentAt(to, coordinate)) + .sort( + (a, b) => + a.namespaceId.localeCompare(b.namespaceId, 'en') || + a.entityKind.localeCompare(b.entityKind, 'en') || + a.entityName.localeCompare(b.entityName, 'en'), + ); +} + /** * Looks up a single entity in a `Storage`-shaped value by its full coordinate. * Returns `undefined` if the namespace, entity kind, or entity name is absent. diff --git a/packages/1-framework/1-core/framework-components/test/domain-element-coordinates.test.ts b/packages/1-framework/1-core/framework-components/test/domain-element-coordinates.test.ts index 0c1537aa0743..9d5bd914dcef 100644 --- a/packages/1-framework/1-core/framework-components/test/domain-element-coordinates.test.ts +++ b/packages/1-framework/1-core/framework-components/test/domain-element-coordinates.test.ts @@ -3,12 +3,12 @@ import { describe, expect, it } from 'vitest'; import { domainElementCoordinates } from '../src/ir/domain'; describe('domainElementCoordinates', () => { - it('yields domain-plane coordinates for models and value objects', () => { + it('yields domain-plane coordinates for models and enums', () => { const domain: Pick = { namespaces: { auth: { models: { User: { fields: {}, relations: {}, storage: {} } }, - valueObjects: { Email: { fields: {} } }, + enum: { Role: { codecId: 'pg/text@1', members: [{ name: 'Admin', value: 'admin' }] } }, }, public: { models: { Post: { fields: {}, relations: {}, storage: {} } }, @@ -21,7 +21,7 @@ describe('domainElementCoordinates', () => { expect(coordinates).toEqual( expect.arrayContaining([ { plane: 'domain', namespaceId: 'auth', entityKind: 'models', entityName: 'User' }, - { plane: 'domain', namespaceId: 'auth', entityKind: 'valueObjects', entityName: 'Email' }, + { plane: 'domain', namespaceId: 'auth', entityKind: 'enum', entityName: 'Role' }, { plane: 'domain', namespaceId: 'public', entityKind: 'models', entityName: 'Post' }, ]), ); diff --git a/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts b/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts index 66e4748d4092..540537b259e6 100644 --- a/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts +++ b/packages/1-framework/1-core/framework-components/test/element-coordinates.test.ts @@ -1,9 +1,9 @@ -import type { StorageBase } from '@internal/contract/types'; +import type { EntityCoordinate, StorageBase } from '@internal/contract/types'; import { blindCast } from '@internal/utils/casts'; import { describe, expect, it } from 'vitest'; import { + changedElementCoordinates, coordinateKey, - type EntityCoordinate, elementCoordinates, entityAt, } from '../src/ir/storage'; @@ -151,3 +151,56 @@ describe('entityAt', () => { ).toBeUndefined(); }); }); + +describe('changedElementCoordinates', () => { + function storageOf(namespaces: Record): Pick { + return blindCast, 'synthetic storage diff fixture'>({ + namespaces, + }); + } + + it('lists added, removed and changed entities, sorted, and leaves out equal ones', () => { + const from = storageOf({ + public: { + entries: { + table: { users: { columns: { id: 'int', name: 'text' } }, posts: { id: 1 } }, + valueSet: { Status: { values: ['open'] } }, + }, + }, + }); + const to = storageOf({ + public: { + entries: { + table: { + users: { columns: { name: 'text', id: 'int', note: 'text' } }, + posts: { id: 1 }, + }, + valueSet: { Role: { values: ['admin'] } }, + }, + }, + audit: { entries: { table: { events: {} } } }, + }); + + expect(changedElementCoordinates(from, to)).toEqual([ + { plane: 'storage', namespaceId: 'audit', entityKind: 'table', entityName: 'events' }, + { plane: 'storage', namespaceId: 'public', entityKind: 'table', entityName: 'users' }, + { plane: 'storage', namespaceId: 'public', entityKind: 'valueSet', entityName: 'Role' }, + { plane: 'storage', namespaceId: 'public', entityKind: 'valueSet', entityName: 'Status' }, + ]); + }); + + it('ignores key order inside an entity', () => { + const from = storageOf({ public: { entries: { table: { users: { a: 1, b: 2 } } } } }); + const to = storageOf({ public: { entries: { table: { users: { b: 2, a: 1 } } } } }); + + expect(changedElementCoordinates(from, to)).toEqual([]); + }); + + it('treats every entity as added when there is no earlier storage', () => { + const to = storageOf({ public: { entries: { table: { users: {} } } } }); + + expect(changedElementCoordinates(null, to)).toEqual([ + { plane: 'storage', namespaceId: 'public', entityKind: 'table', entityName: 'users' }, + ]); + }); +}); diff --git a/packages/1-framework/1-core/framework-components/test/migration-statements.test.ts b/packages/1-framework/1-core/framework-components/test/migration-statements.test.ts index 54915a6d308f..c20a0d09f0c6 100644 --- a/packages/1-framework/1-core/framework-components/test/migration-statements.test.ts +++ b/packages/1-framework/1-core/framework-components/test/migration-statements.test.ts @@ -30,6 +30,7 @@ function contractWith(models: Readonly>): Cont ]), ), }, + storage: { namespaces: {} }, }; } diff --git a/packages/1-framework/3-tooling/cli/README.md b/packages/1-framework/3-tooling/cli/README.md index 2d1ce4b9f670..53d4d65bfa3b 100644 --- a/packages/1-framework/3-tooling/cli/README.md +++ b/packages/1-framework/3-tooling/cli/README.md @@ -1095,7 +1095,7 @@ prisma migration plan [--config ] [--name ] [--from ] [--t - `migrations//ops.json` — planned operations (empty list `[]` if placeholders blocked the planner) - `migrations/snapshots//contract.{json,d.ts}` — bookend contracts, written write-if-absent, keyed by each contract's storage hash (one entry for `from` when applicable, one for `to`) -**Rename statements:** each `--rename` names the old and the new name in contract vocabulary, never a table or a column. Each side is one of `namespace`, `Model`, `namespace.Model`, `Model.field`, `namespace.Model.field`, `Enum.value` or `namespace.Enum.value`, and both sides name the same kind of thing: a namespace, a model, a field of the same model, or a value of the same enum. Names match exactly, including case. A name without a namespace resolves when exactly one namespace declares the model. A field's model is named as the destination contract names it, so a model rename followed by a field rename on it is `--rename Profile:User --rename User.name:User.fullName`. The old name must exist in the starting contract and not in the destination; the new name must exist in the destination and not in the starting contract. Statements resolve before anything is written; a statement that is malformed fails with `MIGRATION.STATEMENT_INVALID`, and one that does not resolve fails with `MIGRATION.STATEMENT_UNRESOLVED`, naming what it searched and what it found. Each error's next step is the statement to type instead: the accepted forms, the statement written the right way round, the statements a model-and-field mix may have meant, or the order that works. A statement that has already been applied (the origin already has the new name and not the old one) says to leave it out. Statements cannot swap two names in one plan, and the error gives the three plans that do, through a temporary name. A plan from an empty database (`--from @empty`, or greenfield) has no models to rename, so every statement is unresolved. A statement whose namespace changes, such as `--rename public.Invoice:billing.Invoice`, moves the table to the new schema on Postgres, creating the schema first when the database lacks it; a move into the unbound namespace is refused. One name on each side of a namespace of the origin contract, `--rename billing:finance`, renames the namespace on Postgres; a statement that reads both as a namespace and as a model rename is refused, and `public` and a schema another contract space declares are not renamed. Namespace renames run before the other statements, whatever order they are given in. When a name is both a namespace and a model, name the model with its namespace, or give both statements. An enum value is named through its enum by its member name, `--rename Status.ARCHIVED:Status.RETIRED`, never by its stored value; on Postgres and SQLite the plan replaces the old stored value with the new member's in every column the enum types, with `this.replaceStoredValue(...)` calls in `migration.ts`, between the drop of the old check and the creation of the new one on Postgres. A member renamed with its stored value kept plans nothing, and its statement is listed with no operations. A rename into a stored value another member of the origin enum holds is refused, since it would merge the data of the two members. Without a snapshot of the contract the database is at, no enum value is known to have changed: nothing is asked about it, `--delete Status.ARCHIVED` answers nothing and is refused as unused, and on Postgres the apply fails at the new check while data holds the old value. MongoDB refuses enum value statements. Renaming value objects is not supported in this release. The planner renames the table or column, and the constraints and indexes named after it, ahead of the rest of the plan. On Postgres, a check, a row-level-security policy, or an expression or partial index whose SQL names a renamed column is dropped and created again, and adding a check back reads every row of the table under an exclusive lock, so on a large table such a rename takes time; `migration.ts` contains the same `...this.renameTable(...)` and `...this.renameColumn(...)` calls you could have written by hand. On MongoDB a field is named by its name in the contract, as on SQL (`id @map("_id")` is `User.id`), and a rename that keeps the stored name plans nothing. A model rename renames the collection, which keeps its documents, indexes and validator (`renameCollection`); a field rename renames the field in every document that holds it (`renameField`), and only in that variant's documents for a variant's field; `migration.ts` calls these factories. The statements are listed under `Statements applied` after the operations, each with its number of operations, and as `appliedStatements` in `--json` output, each with its `statement`, `description` and `operationIndexes`, the positions in `operations` of the operations it accounts for. A coordinate in a contract with no namespaces has no `namespaceId` in the JSON. A statement whose storage does not change, such as a model whose table name is kept with `@@map`, is listed with no operations. When the storage did not change at all and no statement needs an operation, no migration package is written and the output says so; when the storage changed but the planner planned nothing, planning fails with `MIGRATION.PLANNING_FAILED`, statements or not. A statement the planner cannot carry out, for example on a table whose control policy is not `managed`, fails planning with `MIGRATION.PLANNING_FAILED`, and the refused statement is carried in the error's conflicts. +**Rename statements:** each `--rename` names the old and the new name in contract vocabulary, never a table or a column. Each side is one of `namespace`, `Model`, `namespace.Model`, `Model.field`, `namespace.Model.field`, `Enum.value` or `namespace.Enum.value`, and both sides name the same kind of thing: a namespace, a model, a field of the same model, or a value of the same enum. Names match exactly, including case. A name without a namespace resolves when exactly one namespace declares the model. A field's model is named as the destination contract names it, so a model rename followed by a field rename on it is `--rename Profile:User --rename User.name:User.fullName`. The old name must exist in the starting contract and not in the destination; the new name must exist in the destination and not in the starting contract. Statements resolve before anything is written; a statement that is malformed fails with `MIGRATION.STATEMENT_INVALID`, and one that does not resolve fails with `MIGRATION.STATEMENT_UNRESOLVED`, naming what it searched and what it found. Each error's next step is the statement to type instead: the accepted forms, the statement written the right way round, the statements a model-and-field mix may have meant, or the order that works. A statement that has already been applied (the origin already has the new name and not the old one) says to leave it out. Statements cannot swap two names in one plan, and the error gives the three plans that do, through a temporary name. A plan from an empty database (`--from @empty`, or greenfield) has no models to rename, so every statement is unresolved. A statement whose namespace changes, such as `--rename public.Invoice:billing.Invoice`, moves the table to the new schema on Postgres, creating the schema first when the database lacks it; a move into the unbound namespace is refused. One name on each side of a namespace of the origin contract, `--rename billing:finance`, renames the namespace on Postgres; a statement that reads both as a namespace and as a model rename is refused, and `public` and a schema another contract space declares are not renamed. Namespace renames run before the other statements, whatever order they are given in. When a name is both a namespace and a model, name the model with its namespace, or give both statements. An enum value is named through its enum by its member name, `--rename Status.ARCHIVED:Status.RETIRED`, never by its stored value; on Postgres and SQLite the plan replaces the old stored value with the new member's in every column the enum types, with `this.replaceStoredValue(...)` calls in `migration.ts`, between the drop of the old check and the creation of the new one on Postgres. A member renamed with its stored value kept plans nothing, and its statement is listed with no operations. A rename into a stored value another member of the origin enum holds is refused, since it would merge the data of the two members. Without a snapshot of the contract the database is at, no enum value is known to have changed: nothing is asked about it, `--delete Status.ARCHIVED` answers nothing and is refused as unused, and on Postgres the apply fails at the new check while data holds the old value. MongoDB refuses enum value statements. Renaming value objects is not supported in this release. The planner renames the table or column, and the constraints and indexes named after it, ahead of the rest of the plan. On Postgres, a check, a row-level-security policy, or an expression or partial index whose SQL names a renamed column is dropped and created again, and adding a check back reads every row of the table under an exclusive lock, so on a large table such a rename takes time; `migration.ts` contains the same `...this.renameTable(...)` and `...this.renameColumn(...)` calls you could have written by hand. On MongoDB a field is named by its name in the contract, as on SQL (`id @map("_id")` is `User.id`), and a rename that keeps the stored name plans nothing. A model rename renames the collection, which keeps its documents, indexes and validator (`renameCollection`); a field rename renames the field in every document that holds it (`renameField`), and only in that variant's documents for a variant's field; `migration.ts` calls these factories. The statements are listed under `Statements applied` after the operations, each with its number of operations, and as `appliedStatements` in `--json` output, each with its `statement`, `description` and `operationIndexes`, the positions in `operations` of the operations it accounts for. A coordinate in a contract with no namespaces has no `namespaceId` in the JSON. A statement whose storage does not change, such as a model whose table name is kept with `@@map`, is listed with no operations. When the storage did not change at all and no statement needs an operation, no migration package is written and the output says so; when the storage changed but the planner planned nothing, the migration is written with no operations, statements or not, and the output names the storage entities that changed. In `--json` output, `changedStorageEntities` lists the storage entities that differ between the origin and the destination for every written migration, with operations or without; a migration written with no operations has an empty `operations` and `noOp: false`. `noOp: true` means no migration was written, because the storage hash did not change, and then `changedStorageEntities` is empty. A statement the planner cannot carry out, for example on a table whose control policy is not `managed`, fails planning with `MIGRATION.PLANNING_FAILED`, and the refused statement is carried in the error's conflicts. **Data loss:** a plan that would lose data is written only once you say what each such operation means. The command asks one question per model, field or storage name whose data an operation would lose (dropping a table or a column, or a type change that can change values), before it writes anything, including the baseline package of an auto-baseline. Each question is answered with a statement: diff --git a/packages/1-framework/3-tooling/cli/src/control-api/operations/changed-storage-entities.ts b/packages/1-framework/3-tooling/cli/src/control-api/operations/changed-storage-entities.ts new file mode 100644 index 000000000000..4cf323f100f5 --- /dev/null +++ b/packages/1-framework/3-tooling/cli/src/control-api/operations/changed-storage-entities.ts @@ -0,0 +1,59 @@ +import type { Contract, EntityCoordinate } from '@internal/contract/types'; +import { changedElementCoordinates, UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; +import { canonicalStringify } from '@internal/utils/canonical-stringify'; + +/** The coordinate of a storage entity that changed, without the internal unbound namespace id. */ +export type ChangedStorageEntity = Omit & { + readonly namespaceId?: string; +}; + +/** The storage entities that differ between two contracts, as a result reports them. */ +export function changedStorageEntities( + fromContract: Contract | null, + toContract: Contract, +): readonly ChangedStorageEntity[] { + return changedElementCoordinates(fromContract?.storage ?? null, toContract.storage).map( + withoutUnboundNamespaceId, + ); +} + +/** The sentence that names what changed in storage, such as `What changed: value object "public.Home".` */ +export function whatChangedSentence(fromContract: Contract | null, toContract: Contract): string { + const changed = changedElementCoordinates(fromContract?.storage ?? null, toContract.storage); + return changed.length === 0 + ? `No storage entity changed; what changed is ${changedStorageKeys(fromContract, toContract).join(', ') || 'outside the storage entities'}.` + : `What changed: ${changed.map(storageEntityText).join(', ')}.`; +} + +function withoutUnboundNamespaceId({ + namespaceId, + ...rest +}: EntityCoordinate): ChangedStorageEntity { + return namespaceId === UNBOUND_NAMESPACE_ID ? rest : { namespaceId, ...rest }; +} + +/** The top-level storage keys whose content differs between two contracts, such as `storage.types`, in key order. */ +function changedStorageKeys(fromContract: Contract | null, toContract: Contract): string[] { + const plain = (storage: unknown): Record => + typeof storage === 'object' && storage !== null ? JSON.parse(JSON.stringify(storage)) : {}; + const from = plain(fromContract?.storage); + const to = plain(toContract.storage); + return [...new Set([...Object.keys(from), ...Object.keys(to)])] + .filter((key) => key !== 'storageHash') + .filter((key) => canonicalStringify(from[key]) !== canonicalStringify(to[key])) + .sort() + .map((key) => `storage.${key}`); +} + +function storageEntityText(coordinate: EntityCoordinate): string { + const name = + coordinate.namespaceId === UNBOUND_NAMESPACE_ID + ? coordinate.entityName + : `${coordinate.namespaceId}.${coordinate.entityName}`; + return `${kindInWords(coordinate.entityKind)} "${name}"`; +} + +/** An entity kind as words, such as `value object` for `valueObject`. */ +function kindInWords(entityKind: string): string { + return entityKind.replace(/[A-Z]/g, (letter) => ` ${letter.toLowerCase()}`); +} diff --git a/packages/1-framework/3-tooling/cli/src/control-api/operations/db-run.ts b/packages/1-framework/3-tooling/cli/src/control-api/operations/db-run.ts index 8274d1c68aa1..5e6d247ba9e6 100644 --- a/packages/1-framework/3-tooling/cli/src/control-api/operations/db-run.ts +++ b/packages/1-framework/3-tooling/cli/src/control-api/operations/db-run.ts @@ -84,6 +84,11 @@ import type { PerSpaceExecutionEntry, PlannerWarningsListener, } from '../types'; +import { + type ChangedStorageEntity, + changedStorageEntities, + whatChangedSentence, +} from './changed-storage-entities'; import { type BuildAggregateInputs, buildContractSpaceAggregate, @@ -521,12 +526,18 @@ export async function executeRun r.entry.displayOps); + const signedWithoutOperations = + action === 'dbUpdate' && + applied.value.totalOpsExecuted === 0 && + markerRows.get(aggregate.app.spaceId)?.storageHash !== appPlan.destination.storageHash; const summary = action === 'dbInit' ? `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), database signed` - : applied.value.totalOpsExecuted === 0 - ? `Database already matches contract across ${applied.value.orderedResolutions.length} space(s), signature updated` - : `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), signature updated`; + : signedWithoutOperations + ? `The storage the contract describes changed, but the database needs no operation for it, so none ran and the database was signed with the new contract. ${whatChangedSentence(fromContract, contract)}` + : applied.value.totalOpsExecuted === 0 + ? `Database already matches contract across ${applied.value.orderedResolutions.length} space(s), signature updated` + : `Applied ${applied.value.totalOpsExecuted} operation(s) across ${applied.value.orderedResolutions.length} space(s), signature updated`; return wrapApplyResult({ operations: aggregateOps, @@ -538,11 +549,14 @@ export async function executeRun>; -/** A plan origin whose earlier contract the planner diffs against. */ -type PlanLegOriginWithContract = - | Exclude - | (Extract & { readonly contract: Contract }); - /** - * Why a planner leg failed: the planner refused (a conflict, such as a refused statement), or it - * produced no operations for a changed contract. + * Where a `migration plan` leg plans from: an empty database, an empty database toward the `db` + * ref's contract (the automatic baseline), or an earlier contract the planner diffs against. */ -interface PlannerLegFailure { - readonly reason: 'refused' | 'noOperations'; - readonly error: CliStructuredError; -} +type PlanLegOrigin = + | { readonly kind: 'empty' } + | { readonly kind: 'baseline' } + | { readonly kind: 'contract'; readonly contract: Contract }; async function runPlannerLeg( planner: ReturnType, migrations: TargetMigrationsApi, frameworkComponents: ReturnType, contract: Contract, - origin: PlanLegOriginWithContract, + origin: PlanLegOrigin, destination: PlanDestination, statements: readonly ResolvedMigrationStatement[], - /** - * True when the storage did not change and statements were given: a plan of - * no operations then means the statements need none, not a planning failure. - */ - noOperationsExpected: boolean, spaceId: string, ownership: SchemaOwnership, snapshotsImportPath: string, resolveImportSpecifier: ImportSpecifierResolver, refused: (conflicts: readonly MigrationPlannerConflict[]) => CliStructuredError = (conflicts) => errorMigrationPlanningFailed({ conflicts: castAs(conflicts) }), -): Promise> { +): Promise> { const fromContract = origin.kind === 'contract' ? origin.contract : null; const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents); const plannerResult = planner.plan({ @@ -204,10 +201,7 @@ async function runPlannerLeg( snapshotsImportPath, }); if (plannerResult.kind === 'failure') { - return notOk({ - reason: 'refused', - error: refused(plannerResult.conflicts), - }); + return notOk(refused(plannerResult.conflicts)); } // The operation at each position of the plan, or `undefined` where it is an unfilled placeholder. @@ -215,11 +209,8 @@ async function runPlannerLeg( let hasPlaceholders = false; try { resolved = await Promise.all(plannerResult.plan.operations); - if (resolved.length === 0 && !noOperationsExpected) { - return notOk({ - reason: 'noOperations', - error: errorPlanProducedNoOperations(origin, destination), - }); + if (resolved.length === 0 && origin.kind === 'empty') { + return notOk(errorPlanProducedNoOperations(destination)); } } catch (e) { if (CliStructuredError.is(e) && e.code === 'MIGRATION.UNFILLED_PLACEHOLDER') { @@ -456,6 +447,10 @@ export interface MigrationPlanResult { readonly appliedStatements: readonly AppliedStatementReport[]; /** The new required fields whose existing rows the plan gives a temporary value. */ readonly temporaryValues: readonly TemporaryValueReport[]; + /** + * The storage entities that differ between the origin and the destination. Empty when nothing was written (`noOp`), since the storage hashes are equal. A written migration whose `operations` is empty still lists them. A coordinate in the unbound namespace has no `namespaceId`, since that id is internal. + */ + readonly changedStorageEntities: readonly ChangedStorageEntity[]; readonly timings: { readonly total: number; }; @@ -626,14 +621,10 @@ async function executeMigrationPlanCommandInner( break; } const resolvedFrom = resolutionResult.value; - const fromOrigin: PlanLegOriginWithContract = + const fromOrigin: PlanLegOrigin = resolvedFrom.kind === 'greenfield' ? { kind: 'empty' } - : { - kind: 'contract', - hash: resolvedFrom.fromHash, - contract: resolvedFrom.fromContract, - }; + : { kind: 'contract', contract: resolvedFrom.fromContract }; // `--to ` swaps the planner destination to an arbitrary resolved // contract (e.g. an ancestor / rollback target). The from-side resolution @@ -731,6 +722,7 @@ async function executeMigrationPlanCommandInner( ...(warnings.length > 0 ? { warnings } : {}), appliedStatements: [], temporaryValues: [], + changedStorageEntities: [], summary: 'No changes detected between contracts', timings: { total: Date.now() - startTime }, }; @@ -795,7 +787,7 @@ async function executeMigrationPlanCommandInner( planDelta: ( texts: readonly StatementText[], resolved: readonly ResolvedMigrationStatement[], - ) => Promise>, + ) => Promise>, ) => answerPlannedDataLoss({ baseline, @@ -814,8 +806,7 @@ async function executeMigrationPlanCommandInner( destination: toContract, }); if (!resolved.ok) return resolved; - const leg = await planDelta([...consentTexts, ...planned], resolved.value); - return leg.ok ? leg : notOk(leg.failure.error); + return planDelta([...consentTexts, ...planned], resolved.value); }, }); @@ -832,17 +823,16 @@ async function executeMigrationPlanCommandInner( migrations, frameworkComponents, fromContract, - { kind: 'baseline', hash: fromHash }, + { kind: 'baseline' }, { hash: fromHash, isEmitted: false }, [], - false, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(baselinePackageDir, migrationsDir), resolveImportSpecifier, ); if (!baselineLeg.ok) { - return notOk(baselineLeg.failure.error); + return baselineLeg; } const planBaselineDelta = ( @@ -857,7 +847,6 @@ async function executeMigrationPlanCommandInner( fromOrigin, planDestination, resolved, - false, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(deltaPackageDir, migrationsDir), @@ -868,14 +857,8 @@ async function executeMigrationPlanCommandInner( fromHash === toStorageHash ? undefined : await planBaselineDelta(statementTexts, statements); - // A refused delta writes nothing. A delta with no operations still writes the baseline, so - // the `migration new --from` its error advises has the history to start from. - if ( - plannedDelta !== undefined && - !plannedDelta.ok && - plannedDelta.failure.reason === 'refused' - ) { - return notOk(plannedDelta.failure.error); + if (plannedDelta !== undefined && !plannedDelta.ok) { + return plannedDelta; } const answered = await answerDataLoss( @@ -887,9 +870,7 @@ async function executeMigrationPlanCommandInner( return notOk(answered.failure); } const deltaLeg = - plannedDelta === undefined || !plannedDelta.ok || answered.value.delta === undefined - ? plannedDelta - : ok(answered.value.delta); + plannedDelta === undefined ? undefined : (answered.value.delta ?? plannedDelta.value); const deletes = answered.value.deletes; await writePlannedMigrationPackage( @@ -900,10 +881,6 @@ async function executeMigrationPlanCommandInner( baselineLeg.value, ); - if (deltaLeg !== undefined && !deltaLeg.ok) { - return notOk(deltaLeg.failure.error); - } - if (deltaLeg === undefined) { const statementsWithoutOperations = reportAppliedStatements( statements.map((statement) => ({ statement, operationIndexes: [] })), @@ -928,6 +905,7 @@ async function executeMigrationPlanCommandInner( placeholderDirs: [baselineDir], appliedStatements: [...statementsWithoutOperations, ...deletes], temporaryValues: baselineLeg.value.temporaryValues, + changedStorageEntities: changedStorageEntities(fromContract, toContract), summary: placeholderSummary('baseline', [baselinePackageDir], invocationOf(options)), timings: { total: Date.now() - startTime }, }; @@ -953,6 +931,7 @@ async function executeMigrationPlanCommandInner( ...(warnings.length > 0 ? { warnings } : {}), appliedStatements: [...statementsWithoutOperations, ...deletes], temporaryValues: baselineLeg.value.temporaryValues, + changedStorageEntities: changedStorageEntities(fromContract, toContract), summary: buildAutoBaselinePlanSummary(baselineOps.length, 0, emittedExtensionDirs.length), timings: { total: Date.now() - startTime }, }; @@ -964,13 +943,13 @@ async function executeMigrationPlanCommandInner( fromHash, toStorageHash, deltaTimestamp, - deltaLeg.value, + deltaLeg, ); await writeDestinationSnapshot(toStorageHash); const baselineOps = baselineLeg.value.hasPlaceholders ? [] : baselineLeg.value.plannedOps; - const deltaOps = deltaLeg.value.hasPlaceholders ? [] : deltaLeg.value.plannedOps; - if (baselineLeg.value.hasPlaceholders || deltaLeg.value.hasPlaceholders) { + const deltaOps = deltaLeg.hasPlaceholders ? [] : deltaLeg.plannedOps; + if (baselineLeg.value.hasPlaceholders || deltaLeg.hasPlaceholders) { const baselineDir = relative(cwd, baselinePackageDir); const deltaDir = relative(cwd, deltaPackageDir); const result: MigrationPlanResult = { @@ -980,17 +959,18 @@ async function executeMigrationPlanCommandInner( to: toStorageHash, dir: deltaDir, baselineDir, - operations: operationSummaries(deltaLeg.value.plannedOps), + operations: operationSummaries(deltaLeg.plannedOps), baselineOperations: operationSummaries(baselineLeg.value.plannedOps), emittedExtensionDirs, ...(warnings.length > 0 ? { warnings } : {}), pendingPlaceholders: true, placeholderDirs: [ ...(baselineLeg.value.hasPlaceholders ? [relative(cwd, baselinePackageDir)] : []), - ...(deltaLeg.value.hasPlaceholders ? [relative(cwd, deltaPackageDir)] : []), + ...(deltaLeg.hasPlaceholders ? [relative(cwd, deltaPackageDir)] : []), ], - appliedStatements: [...deltaLeg.value.appliedStatements, ...deletes], - temporaryValues: deltaLeg.value.temporaryValues, + appliedStatements: [...deltaLeg.appliedStatements, ...deletes], + temporaryValues: deltaLeg.temporaryValues, + changedStorageEntities: changedStorageEntities(fromContract, toContract), summary: placeholderSummary( 'baseline + migration', [baselinePackageDir, deltaPackageDir], @@ -1026,12 +1006,17 @@ async function executeMigrationPlanCommandInner( emittedExtensionDirs, ...(preview !== undefined ? { preview } : {}), ...(warnings.length > 0 ? { warnings } : {}), - appliedStatements: [...deltaLeg.value.appliedStatements, ...deletes], - temporaryValues: deltaLeg.value.temporaryValues, - summary: buildAutoBaselinePlanSummary( - baselineOps.length, - deltaOps.length, - emittedExtensionDirs.length, + appliedStatements: [...deltaLeg.appliedStatements, ...deletes], + temporaryValues: deltaLeg.temporaryValues, + ...withNoOperationsReport( + buildAutoBaselinePlanSummary( + baselineOps.length, + deltaOps.length, + emittedExtensionDirs.length, + ), + deltaOps.length === 0, + fromContract, + toContract, ), timings: { total: Date.now() - startTime }, }; @@ -1055,7 +1040,6 @@ async function executeMigrationPlanCommandInner( fromOrigin, planDestination, resolved, - resolved.length > 0 && fromHash === toStorageHash, aggregate.app.spaceId, aggregate, snapshotsImportPathFrom(packageDir, migrationsDir), @@ -1064,16 +1048,20 @@ async function executeMigrationPlanCommandInner( ); const plannedDelta = await planDelta(statementTexts, statements); if (!plannedDelta.ok) { - return notOk(plannedDelta.failure.error); + return plannedDelta; } const answered = await answerDataLoss(undefined, plannedDelta.value, planDelta); if (!answered.ok) { return notOk(answered.failure); } - const deltaLeg = ok(answered.value.delta ?? plannedDelta.value); + const deltaLeg = answered.value.delta ?? plannedDelta.value; const deletes = answered.value.deletes; - if (!deltaLeg.value.hasPlaceholders && deltaLeg.value.plannedOps.length === 0) { + if ( + !deltaLeg.hasPlaceholders && + deltaLeg.plannedOps.length === 0 && + fromHash === toStorageHash + ) { const result: MigrationPlanResult = { ok: true, noOp: true, @@ -1082,24 +1070,19 @@ async function executeMigrationPlanCommandInner( operations: [], emittedExtensionDirs, ...(warnings.length > 0 ? { warnings } : {}), - appliedStatements: [...deltaLeg.value.appliedStatements, ...deletes], - temporaryValues: deltaLeg.value.temporaryValues, + appliedStatements: [...deltaLeg.appliedStatements, ...deletes], + temporaryValues: deltaLeg.temporaryValues, + changedStorageEntities: [], summary: 'No changes to plan: the statements need no operations', timings: { total: Date.now() - startTime }, }; return ok(result); } - await writePlannedMigrationPackage( - packageDir, - fromHash, - toStorageHash, - timestamp, - deltaLeg.value, - ); + await writePlannedMigrationPackage(packageDir, fromHash, toStorageHash, timestamp, deltaLeg); await writeDestinationSnapshot(toStorageHash); - if (deltaLeg.value.hasPlaceholders) { + if (deltaLeg.hasPlaceholders) { const dir = relative(cwd, packageDir); const result: MigrationPlanResult = { ok: true, @@ -1107,21 +1090,22 @@ async function executeMigrationPlanCommandInner( from: fromHash, to: toStorageHash, dir, - operations: operationSummaries(deltaLeg.value.plannedOps), + operations: operationSummaries(deltaLeg.plannedOps), emittedExtensionDirs, ...(warnings.length > 0 ? { warnings } : {}), pendingPlaceholders: true, placeholderDirs: [relative(cwd, packageDir)], ...(fromDefaulted ? { fromDefaulted } : {}), - appliedStatements: [...deltaLeg.value.appliedStatements, ...deletes], - temporaryValues: deltaLeg.value.temporaryValues, + appliedStatements: [...deltaLeg.appliedStatements, ...deletes], + temporaryValues: deltaLeg.temporaryValues, + changedStorageEntities: changedStorageEntities(fromContract, toContract), summary: placeholderSummary('migration', [packageDir], invocationOf(options)), timings: { total: Date.now() - startTime }, }; return ok(result); } - const plannedOps = deltaLeg.value.plannedOps; + const plannedOps = deltaLeg.plannedOps; const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(plannedOps) : undefined; @@ -1140,9 +1124,14 @@ async function executeMigrationPlanCommandInner( ...(preview !== undefined ? { preview } : {}), ...(fromDefaulted ? { fromDefaulted } : {}), ...(warnings.length > 0 ? { warnings } : {}), - appliedStatements: [...deltaLeg.value.appliedStatements, ...deletes], - temporaryValues: deltaLeg.value.temporaryValues, - summary: buildPlanSummary(plannedOps.length, emittedExtensionDirs.length), + appliedStatements: [...deltaLeg.appliedStatements, ...deletes], + temporaryValues: deltaLeg.temporaryValues, + ...withNoOperationsReport( + buildPlanSummary(plannedOps.length, emittedExtensionDirs.length), + plannedOps.length === 0, + fromContract, + toContract, + ), timings: { total: Date.now() - startTime }, }; return ok(result); @@ -1153,6 +1142,24 @@ async function executeMigrationPlanCommandInner( } } +/** + * The summary of a written migration and, when it has no operations, the storage entities that + * changed, with a sentence added to the summary that says the database needs no operation for them. + */ +function withNoOperationsReport( + summary: string, + hasNoOperations: boolean, + fromContract: Contract | null, + toContract: Contract, +): Pick { + const reported = { changedStorageEntities: changedStorageEntities(fromContract, toContract) }; + if (!hasNoOperations) return { ...reported, summary }; + return { + ...reported, + summary: `${summary}. The storage the contract describes changed, but the database needs no operation for it, so the migration has none. ${whatChangedSentence(fromContract, toContract)}`, + }; +} + /** * Compose the success-line summary so the cross-space side effect * (extension-space migration packages materialised on disk during diff --git a/packages/1-framework/3-tooling/cli/src/control-api/operations/run-migration.ts b/packages/1-framework/3-tooling/cli/src/control-api/operations/run-migration.ts index 31e2d8794811..2b280c8d7c8b 100644 --- a/packages/1-framework/3-tooling/cli/src/control-api/operations/run-migration.ts +++ b/packages/1-framework/3-tooling/cli/src/control-api/operations/run-migration.ts @@ -11,6 +11,7 @@ import type { MigrationOperationPolicy, MigrationRunnerFailure, MigrationSubject, + RowsFailingCheck, RowsHoldingValue, TargetMigrationsCapability, } from '@internal/framework-components/control'; @@ -34,6 +35,7 @@ import type { FailedTypeChangeReport, OnControlProgress, PerSpaceExecutionEntry, + RowsFailingCheckReport, RowsHoldingValueReport, RunnerFailureFacts, } from '../types'; @@ -85,23 +87,36 @@ function rowsHoldingValueReport( }; } +function rowsFailingCheckReport( + fact: RowsFailingCheck | undefined, +): RowsFailingCheckReport | undefined { + if (fact === undefined) return undefined; + const { operationIndex: _operationIndex, ...report } = fact; + return report; +} + /** What the runner said blocked the failed operation, named as the advice names it. */ function failureReports( failure: MigrationRunnerFailure, resolutions: readonly OrderedResolution[], ): Pick< RunnerFailureFacts, - 'failedTypeChange' | 'rowsWithoutValue' | 'rowsHoldingValue' | 'failedMigration' + | 'failedTypeChange' + | 'rowsWithoutValue' + | 'rowsHoldingValue' + | 'rowsFailingCheck' + | 'failedMigration' > { const resolution = resolutions.find(({ spaceId }) => spaceId === failure.meta.failingSpace); if (resolution === undefined) return {}; const fieldText = (field: MigrationSubject) => subjectText(field, { origin: resolution.entry.destinationContract, statements: [] }); - const { failedTypeChange, rowsWithoutValue, rowsHoldingValue } = failure.meta; + const { failedTypeChange, rowsWithoutValue, rowsHoldingValue, rowsFailingCheck } = failure.meta; const operationIndex = failedTypeChange?.operationIndex ?? rowsWithoutValue?.[0]?.operationIndex ?? - rowsHoldingValue?.operationIndex; + rowsHoldingValue?.operationIndex ?? + rowsFailingCheck?.operationIndex; const edge = operationIndex !== undefined ? edgeOfOperation(resolution.entry.migrationEdges, operationIndex) @@ -129,6 +144,7 @@ function failureReports( ? undefined : rowsHoldingValueReport(rowsHoldingValue, resolution.entry.destinationContract), ), + ...ifDefined('rowsFailingCheck', rowsFailingCheckReport(rowsFailingCheck)), ...ifDefined( 'failedMigration', edge === undefined ? undefined : { spaceId: resolution.spaceId, edge }, diff --git a/packages/1-framework/3-tooling/cli/src/control-api/statements/resolve-statements.ts b/packages/1-framework/3-tooling/cli/src/control-api/statements/resolve-statements.ts index aac72d3d10e1..d5a8e500587e 100644 --- a/packages/1-framework/3-tooling/cli/src/control-api/statements/resolve-statements.ts +++ b/packages/1-framework/3-tooling/cli/src/control-api/statements/resolve-statements.ts @@ -7,6 +7,8 @@ import { type ContractFieldType, type ContractModelBase, type ContractWithDomain, + type StorageBase, + valueObjectAt, } from '@internal/contract/types'; import type { CliStructuredError } from '@internal/errors/control'; import { @@ -190,8 +192,19 @@ function modelIn( return Object.hasOwn(namespace.models, name) ? namespace.models[name] : undefined; } -function hasValueObject(namespace: ApplicationDomainNamespace, name: string): boolean { - return namespace.valueObjects !== undefined && Object.hasOwn(namespace.valueObjects, name); +function hasValueObject( + storage: Pick, + namespaceId: string, + name: string, +): boolean { + return ( + valueObjectAt(storage, { + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName: name, + }) !== undefined + ); } function modelAt( @@ -239,7 +252,7 @@ function lookupModelInNamespace( model, }; } - if (hasValueObject(namespace, name)) { + if (hasValueObject(contract.contract.storage, namespaceId, name)) { return { kind: 'refused', ...valueObjectRefusal(displayName(namespaceId, name)) }; } return { @@ -293,7 +306,12 @@ function lookupModel( fix: `Name the model with its namespace, for example ${candidates[0]}.`, }; } - if (Object.values(namespaces).some((namespace) => hasValueObject(namespace, name))) { + const storageNamespaceIds = Object.keys(contract.contract.storage.namespaces); + if ( + storageNamespaceIds.some((namespaceId) => + hasValueObject(contract.contract.storage, namespaceId, name), + ) + ) { return { kind: 'refused', ...valueObjectRefusal(name) }; } return { @@ -1217,7 +1235,7 @@ function typeText(type: ContractFieldType): string { ? type.codecId : `${type.codecId}(${JSON.stringify(type.typeParams)})`; case 'valueObject': - return type.name; + return type.entityName; case 'union': return type.members.map(typeText).join(' | '); } diff --git a/packages/1-framework/3-tooling/cli/src/control-api/types.ts b/packages/1-framework/3-tooling/cli/src/control-api/types.ts index f6ee1eab347e..237c68a512bf 100644 --- a/packages/1-framework/3-tooling/cli/src/control-api/types.ts +++ b/packages/1-framework/3-tooling/cli/src/control-api/types.ts @@ -26,6 +26,7 @@ import type { MigrationPlanOperation, MigrationPlanSubjects, OperationPreview, + RowsFailingCheck, VerifyDatabaseResult, VerifyDatabaseSchemaResult, } from '@internal/framework-components/control'; @@ -34,6 +35,7 @@ import type { AggregateMigrationEdgeRef } from '@internal/migration-tools/aggreg import type { Result } from '@internal/utils/result'; import type { CliStructuredError } from '../utils/cli-errors'; import type { CliInvocation } from '../utils/printed-command'; +import type { ChangedStorageEntity } from './operations/changed-storage-entities'; import type { ExecuteDbSignResult } from './operations/db-sign'; import type { ExecuteDbVerifyResult } from './operations/db-verify'; import type { RenderContractDtsOptions, RenderContractDtsResult } from './render-contract-dts'; @@ -497,6 +499,8 @@ export interface DbUpdateSuccess * graph ends at it: the origin a `migration plan` for this database starts from. */ readonly databaseStorageHash?: string; + /** The storage entities that changed, when the run signed the database with a changed contract and ran no operation. */ + readonly changedStorageEntities?: readonly ChangedStorageEntity[]; readonly summary: string; } @@ -529,10 +533,15 @@ export interface RunnerFailureFacts { readonly failedPrecheck?: FailedPrecheckReport; /** The data that still holds a stored enum value whose removal its storage cannot take. */ readonly rowsHoldingValue?: RowsHoldingValueReport; + /** The existing rows that a check the failed operation adds refuses. */ + readonly rowsFailingCheck?: RowsFailingCheckReport; /** The written migration of the failed operation, when the runner named the operation, or the last one applied when the schema check after apply failed. */ readonly failedMigration?: { readonly spaceId: string; readonly edge: AggregateMigrationEdgeRef }; } +/** The existing rows that a check the failed operation adds refuses, as the advice names them. */ +export type RowsFailingCheckReport = Omit; + /** The data that still holds the stored value of an enum value the plan removes from storage that cannot do without it. */ export interface RowsHoldingValueReport { /** The enum value, as a statement names it, such as `Status.ARCHIVED`. */ diff --git a/packages/1-framework/3-tooling/cli/src/orm/db/migration-blocks.ts b/packages/1-framework/3-tooling/cli/src/orm/db/migration-blocks.ts index 6a5e97049d9a..8ce64431681c 100644 --- a/packages/1-framework/3-tooling/cli/src/orm/db/migration-blocks.ts +++ b/packages/1-framework/3-tooling/cli/src/orm/db/migration-blocks.ts @@ -163,6 +163,7 @@ function applySummaryText(result: MigrationCommandResult): string { const executed = result.execution?.operationsExecuted ?? 0; const spaces = result.perSpace?.length ?? 0; const across = spaces > 0 ? ` across ${spaces} contract space${spaces === 1 ? '' : 's'}` : ''; + if (result.changedStorageEntities !== undefined) return result.summary; if (executed === 0) { return `Database already matches contract${across}`; } diff --git a/packages/1-framework/3-tooling/cli/src/orm/db/update.ts b/packages/1-framework/3-tooling/cli/src/orm/db/update.ts index cb8b6e4404f4..6e4a5238b331 100644 --- a/packages/1-framework/3-tooling/cli/src/orm/db/update.ts +++ b/packages/1-framework/3-tooling/cli/src/orm/db/update.ts @@ -223,6 +223,7 @@ function updateDocument(inputs: { }, }), ...ifDefined('perSpace', value.perSpace), + ...ifDefined('changedStorageEntities', value.changedStorageEntities), appliedStatements: value.appliedStatements, temporaryValues: value.temporaryValues, ...(value.mode === 'plan' diff --git a/packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts b/packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts index 7db428a84e5d..53daeb46d00e 100644 --- a/packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts +++ b/packages/1-framework/3-tooling/cli/src/orm/migration/plan.ts @@ -200,7 +200,11 @@ function planBlocks(result: MigrationPlanResult, migrationsRelative: string): re } return [ ...warningBlocks(result), - { kind: 'summary', status: 'ok', text: result.summary }, + { + kind: 'summary', + status: result.operations.length === 0 ? 'warn' : 'ok', + text: result.summary, + }, ...originNoticeBlocks(result), ...operationBlocks(result), ...temporaryValueBlocks(result), diff --git a/packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts b/packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts index 33e3768e698e..81a9d63b2758 100644 --- a/packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts +++ b/packages/1-framework/3-tooling/cli/src/utils/cli-errors.ts @@ -6,7 +6,6 @@ export type { CliErrorConflict, CliErrorEnvelope } from '@internal/errors/control'; import { - type CliErrorConflict, CliStructuredError, errorConfigFileNotFound, errorConfigValidation, @@ -858,59 +857,29 @@ export function errorStatementOriginUnknown(origin: { ); } -/** Where a `migration plan` leg plans from: an empty database, an empty database toward the `db` ref's contract (the automatic baseline), or an earlier contract. */ -export type PlanLegOrigin = - | { readonly kind: 'empty' } - | { readonly kind: 'baseline'; readonly hash: string } - | { readonly kind: 'contract'; readonly hash: string }; - /** Where a `migration plan` leg plans to; only the emitted contract can be the destination of `migration new`. */ export interface PlanDestination { readonly hash: string; readonly isEmitted: boolean; } -const REPORT_MISSED_CHANGE = - 'If you expected the database to change, migration plan missed it: report it with the output of `prisma migration plan --json`.'; - -function noOperationsConflict( - origin: PlanLegOrigin, - destination: PlanDestination, -): CliErrorConflict { - switch (origin.kind) { - case 'empty': - return { +/** + * `migration plan` refuses a first migration whose planner produced no operations: the contract + * describes nothing to create. + */ +export function errorPlanProducedNoOperations(destination: PlanDestination): CliStructuredError { + return errorMigrationPlanningFailed({ + conflicts: [ + { kind: 'nothingToCreate', summary: 'This contract describes nothing migration plan can create, so there is no first migration to plan.', why: destination.isEmitted ? 'If the database needs something the contract does not describe, run `prisma migration new`, add its operations to the new `migration.ts`, then run that `migration.ts` with `node` to write `ops.json`.' : '`prisma migration new` writes a migration only to the emitted contract. If the database needs something this contract does not describe, emit it, run `prisma migration new`, add its operations to the new `migration.ts`, then run that `migration.ts` with `node` to write `ops.json`.', - }; - case 'baseline': - return { - kind: 'nothingToBaseline', - summary: `The migrations directory is empty, so migration plan starts the migration history with a baseline migration to the contract the db ref points at, ${origin.hash}. That contract describes nothing migration plan can create, so there is no baseline to plan.`, - why: 'No command writes a baseline migration with no operations. If you manage the database with `prisma db init` or `prisma db update`, keep using `prisma db update`. No command can start a migration history from this database yet; report it with the output of `prisma migration plan --json`.', - }; - case 'contract': - return { - kind: 'noDatabaseChange', - summary: - 'The contract changed, but migration plan found nothing to change in the database. That is expected after a Prisma upgrade that changes how contract.json records values, after you switch a field to another codec of the same type, or after you change a control policy.', - why: destination.isEmitted - ? `If you deploy with migrations, run \`prisma migration new --from ${origin.hash}\` to write a migration with no operations, then \`prisma db migrate\`. If you manage the database with \`prisma db init\` or \`prisma db update\`, run \`prisma db sign\` on each database instead. ${REPORT_MISSED_CHANGE}` - : `\`prisma migration new\` writes a migration only to the emitted contract; to write one to this target, emit it first. If you manage the database with \`prisma db init\` or \`prisma db update\`, run \`prisma db sign ${destination.hash}\` on each database. ${REPORT_MISSED_CHANGE}`, - }; - } -} - -/** `migration plan` refuses a leg whose planner produced no operations, and says how to proceed from where it planned. */ -export function errorPlanProducedNoOperations( - origin: PlanLegOrigin, - destination: PlanDestination, -): CliStructuredError { - return errorMigrationPlanningFailed({ conflicts: [noOperationsConflict(origin, destination)] }); + }, + ], + }); } const ROLLBACK_IS_DESTRUCTIVE = diff --git a/packages/1-framework/3-tooling/cli/src/utils/db-update-failure.ts b/packages/1-framework/3-tooling/cli/src/utils/db-update-failure.ts index f7ac8f750c65..6e538c7905a7 100644 --- a/packages/1-framework/3-tooling/cli/src/utils/db-update-failure.ts +++ b/packages/1-framework/3-tooling/cli/src/utils/db-update-failure.ts @@ -6,6 +6,7 @@ import { type CliInvocation, cliCommand } from './printed-command'; import { blockingObjectsAdvice, dbUpdateFailedTypeChangeAdvice, + dbUpdateRowsFailingCheckAdvice, dbUpdateRowsHoldingValueAdvice, dbUpdateRowsWithoutValueAdvice, } from './runner-failure-advice'; @@ -44,16 +45,22 @@ export function dbUpdateRunnerFailureError( databaseStorageHash, invocation, ) - : schemaCheckIssues(failure) !== undefined - ? [ - editFileAction( - `Change the contract source to resolve the issues listed, re-run \`${cliCommand(invocation, 'contract emit')}\`, then re-run \`${update}\``, - ), - ] - : [ - chooseAction( - `Inspect the reported conflict, reconcile schema drift if needed, then re-run \`${update}\``, - ), - ]; + : facts.rowsFailingCheck !== undefined + ? dbUpdateRowsFailingCheckAdvice( + facts.rowsFailingCheck, + databaseStorageHash, + invocation, + ) + : schemaCheckIssues(failure) !== undefined + ? [ + editFileAction( + `Change the contract source to resolve the issues listed, re-run \`${cliCommand(invocation, 'contract emit')}\`, then re-run \`${update}\``, + ), + ] + : [ + chooseAction( + `Inspect the reported conflict, reconcile schema drift if needed, then re-run \`${update}\``, + ), + ]; return runnerFailureError(failure, advice); } diff --git a/packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts b/packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts index c0b8ecf52c82..e6ebe7466dcd 100644 --- a/packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts +++ b/packages/1-framework/3-tooling/cli/src/utils/formatters/migrations.ts @@ -3,6 +3,7 @@ import type { MigrationSubjectJson, OperationPreview, } from '@internal/framework-components/control'; +import type { ChangedStorageEntity } from '../../control-api/operations/changed-storage-entities'; import type { AppliedStatementReport } from '../../control-api/statements/report-applied-statements'; import type { TemporaryValueReport } from '../../control-api/statements/temporary-values'; import type { PerSpaceExecutionEntry } from '../../control-api/types'; @@ -104,6 +105,8 @@ export interface MigrationCommandResult { * into a single ambiguous list. See {@link PerSpaceExecutionEntry}. */ readonly perSpace?: ReadonlyArray; + /** `db update` only: the storage entities that changed, when it signed the database with a changed contract and ran no operation. */ + readonly changedStorageEntities?: readonly ChangedStorageEntity[]; /** `db update` only: the statements the plan applied, in order; empty when none were given. */ readonly appliedStatements?: readonly AppliedStatementReport[]; /** `db update` only: the new required fields whose existing rows get a temporary value. */ diff --git a/packages/1-framework/3-tooling/cli/src/utils/runner-failure-advice.ts b/packages/1-framework/3-tooling/cli/src/utils/runner-failure-advice.ts index 2e06e41e58a2..db7a3dc3143c 100644 --- a/packages/1-framework/3-tooling/cli/src/utils/runner-failure-advice.ts +++ b/packages/1-framework/3-tooling/cli/src/utils/runner-failure-advice.ts @@ -9,6 +9,7 @@ import { basename, join } from 'pathe'; import { statementFlag } from '../control-api/statements/statement-flag'; import type { FailedTypeChangeReport, + RowsFailingCheckReport, RowsHoldingValueReport, RowsWithoutValueReport, RunnerFailureFacts, @@ -204,6 +205,63 @@ function migrateRowsHoldingValueAdvice( ]; } +/** Where the operation that updates the rows goes among the operations of a migration.ts that replaces a check. */ +function betweenTheChecks(report: RowsFailingCheckReport, replacedCheck: string): string { + return `after the operation that drops check "${replacedCheck}" and before the operation that adds check "${report.check}"`; +} + +function updateOperation(report: RowsFailingCheckReport): string { + return `an operation that updates those rows to fit check "${report.check}"`; +} + +function fixRowsFirst(report: RowsFailingCheckReport, rerun: string): NextAction { + return chooseAction( + `Update or delete those rows so they fit check "${report.check}", then re-run ${quoted(rerun)}`, + ); +} + +/** The way out when existing rows fail a check `db update` adds: it cannot keep the data while the check it replaces holds. */ +export function dbUpdateRowsFailingCheckAdvice( + report: RowsFailingCheckReport, + databaseStorageHash: string | undefined, + invocation: CliInvocation, +): RunnerAdvice { + const update = cliCommand(invocation, 'db update', invocation.rerunArgs ?? []); + const { replacedCheck } = report; + if (replacedCheck === undefined) return [fixRowsFirst(report, update)]; + const cannotKeep = `${quoted(cliCommand(invocation, 'db update'))} cannot keep this data`; + if (databaseStorageHash === undefined) { + return [ + chooseAction( + `${cannotKeep}, and no migration ends at the contract the database is at, so ${quoted(cliCommand(invocation, 'migration plan'))} cannot plan from it. Drop check "${replacedCheck}" in the database yourself, update those rows to fit check "${report.check}", then re-run ${quoted(update)}`, + ), + ]; + } + const plan = printedPlanCommand(invocation, databaseStorageHash, invocation.statementArgs ?? []); + return [ + chooseAction( + `${cannotKeep}. Plan the change with ${quoted(plan)}, add ${updateOperation(report)} to the migration.ts it writes, ${betweenTheChecks(report, replacedCheck)}, ${runWrittenFile(invocation)}`, + ), + ]; +} + +function migrateRowsFailingCheckAdvice( + report: RowsFailingCheckReport, + failedMigration: FailedMigrationText | undefined, + invocation: CliInvocation, +): RunnerAdvice { + const { replacedCheck } = report; + if (replacedCheck === undefined) { + return [fixRowsFirst(report, cliCommand(invocation, 'db migrate', invocation.rerunArgs ?? []))]; + } + const { file, run } = failedMigrationFile(failedMigration); + return [ + editFileAction( + `To keep the data, add ${updateOperation(report)} to ${file}, ${betweenTheChecks(report, replacedCheck)}, ${run}, and re-run ${dbMigrateCommand(invocation)}`, + ), + ]; +} + /** * The way out of rows without a value for `db init`: a migration plan needs an origin contract, * which a database that `db init` never initialized does not have. @@ -477,6 +535,9 @@ export function migrateAdvice( if (facts.rowsHoldingValue !== undefined) { return migrateRowsHoldingValueAdvice(facts.rowsHoldingValue, failedMigration, invocation); } + if (facts.rowsFailingCheck !== undefined) { + return migrateRowsFailingCheckAdvice(facts.rowsFailingCheck, failedMigration, invocation); + } if (schemaCheckIssues(failure) !== undefined) { return migrateSchemaCheckAdvice(failedMigration, invocation); } diff --git a/packages/1-framework/3-tooling/cli/test/control-api/statements/resolve-convert-backfill-statements.test.ts b/packages/1-framework/3-tooling/cli/test/control-api/statements/resolve-convert-backfill-statements.test.ts index 257aa81dda79..bbfdeeda55f1 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/statements/resolve-convert-backfill-statements.test.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/statements/resolve-convert-backfill-statements.test.ts @@ -119,6 +119,26 @@ describe('resolveStatements, convert statements', () => { ); }); + it('refuses a value-object field whose type does not change, naming the value object', () => { + const withAddress = contractOf({ + app: { + models: { + User: { fields: ['id', 'home'], fieldTypes: { home: { valueObject: 'Address' } } }, + }, + valueObjects: { Address: ['city'] }, + }, + }); + expectFailure( + resolveStatements({ + statements: [convert('User.home')], + origin: { kind: 'contract', contract: withAddress }, + destination: withAddress, + }), + UNRESOLVED, + 'has the same type, Address, in the origin and destination contracts', + ); + }); + it('refuses a field the origin does not have and no rename gives it', () => { expectFailure( resolve([convert('User.handle')]), diff --git a/packages/1-framework/3-tooling/cli/test/control-api/statements/statement-fixtures.ts b/packages/1-framework/3-tooling/cli/test/control-api/statements/statement-fixtures.ts index fc98031d5bb7..7a421c267f5d 100644 --- a/packages/1-framework/3-tooling/cli/test/control-api/statements/statement-fixtures.ts +++ b/packages/1-framework/3-tooling/cli/test/control-api/statements/statement-fixtures.ts @@ -14,9 +14,12 @@ interface ModelSpec { readonly fields?: readonly string[]; readonly relations?: readonly string[]; readonly base?: string; - /** The field's codec and whether it is optional, for a field `fields` lists; text and required otherwise. */ + /** The field's codec or the value object of its namespace it holds, and whether it is optional, for a field `fields` lists; text and required otherwise. */ readonly fieldTypes?: Readonly< - Record + Record< + string, + { readonly codecId?: string; readonly valueObject?: string; readonly nullable?: boolean } + > >; } @@ -33,6 +36,7 @@ const textField: ContractField = { }; function fieldsOf( + namespaceId: string, names: readonly string[], types: ModelSpec['fieldTypes'] = {}, ): Record { @@ -45,21 +49,30 @@ function fieldsOf( ? textField : { nullable: type.nullable ?? false, - type: { kind: 'scalar', codecId: type.codecId ?? 'test/text@1' }, + type: + type.valueObject === undefined + ? { kind: 'scalar', codecId: type.codecId ?? 'test/text@1' } + : { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName: type.valueObject, + }, }, ]; }), ); } -function modelOf(spec: ModelSpec): ContractModelBase { +function modelOf(namespaceId: string, spec: ModelSpec): ContractModelBase { const relation: ContractRelation = { to: crossRef('Other'), cardinality: '1:N', on: { localFields: ['id'], targetFields: ['ownerId'] }, }; return { - fields: fieldsOf(spec.fields ?? [], spec.fieldTypes), + fields: fieldsOf(namespaceId, spec.fields ?? [], spec.fieldTypes), relations: Object.fromEntries((spec.relations ?? []).map((name) => [name, relation])), storage: {}, ...(spec.base !== undefined ? { base: crossRef(spec.base) } : {}), @@ -67,24 +80,26 @@ function modelOf(spec: ModelSpec): ContractModelBase { } function valueObjectOf(fields: readonly string[]): ContractValueObject { - return { fields: fieldsOf(fields) }; + return { + fields: Object.fromEntries( + fields.map((name) => [ + name, + { nullable: false, type: { kind: 'scalar', codecId: 'test/text@1' } }, + ]), + ), + }; } export function contractOf(namespaces: Record): ContractWithDomain { + const entries = Object.entries(namespaces); return { domain: { namespaces: Object.fromEntries( - Object.entries(namespaces).map(([id, ns]) => [ + entries.map(([id, ns]) => [ id, { models: Object.fromEntries( - Object.entries(ns.models ?? {}).map(([name, spec]) => [name, modelOf(spec)]), - ), - valueObjects: Object.fromEntries( - Object.entries(ns.valueObjects ?? {}).map(([name, fields]) => [ - name, - valueObjectOf(fields), - ]), + Object.entries(ns.models ?? {}).map(([name, spec]) => [name, modelOf(id, spec)]), ), ...(ns.enums === undefined ? {} @@ -106,6 +121,24 @@ export function contractOf(namespaces: Record): ContractW ]), ), }, + storage: { + namespaces: Object.fromEntries( + entries.map(([id, ns]) => [ + id, + { + id, + entries: { + valueObject: Object.fromEntries( + Object.entries(ns.valueObjects ?? {}).map(([name, fields]) => [ + name, + valueObjectOf(fields), + ]), + ), + }, + }, + ]), + ), + }, }; } diff --git a/packages/1-framework/3-tooling/cli/test/orm/db-update-consent.test.ts b/packages/1-framework/3-tooling/cli/test/orm/db-update-consent.test.ts index fb3e12ba5a08..72854ad0d496 100644 --- a/packages/1-framework/3-tooling/cli/test/orm/db-update-consent.test.ts +++ b/packages/1-framework/3-tooling/cli/test/orm/db-update-consent.test.ts @@ -63,9 +63,11 @@ const origin = { }, }, }, + storage: { namespaces: {} }, }; const destination = { domain: { namespaces: { app: { models: { User: { fields: {}, relations: {}, storage: {} } } } } }, + storage: { namespaces: {} }, }; const contracts = { origin, diff --git a/packages/1-framework/3-tooling/cli/test/orm/fixtures/offline-project.ts b/packages/1-framework/3-tooling/cli/test/orm/fixtures/offline-project.ts index 243ba755b002..a2ebe6acfd9a 100644 --- a/packages/1-framework/3-tooling/cli/test/orm/fixtures/offline-project.ts +++ b/packages/1-framework/3-tooling/cli/test/orm/fixtures/offline-project.ts @@ -71,12 +71,21 @@ function modelEntry(model: FixtureModel): readonly [string, Record>; + export function contractJson( storageHash: string, models?: readonly FixtureModel[], + storageNamespaces: FixtureStorageNamespaces = {}, + storageTypes?: Readonly>, ): Record { return { - storage: { storageHash, namespaces: {} }, + storage: { + storageHash, + namespaces: storageNamespaces, + ...(storageTypes === undefined ? {} : { types: storageTypes }), + }, schemaVersion: '1.0.0', target: 'postgres', targetFamily: 'sql', @@ -98,6 +107,8 @@ export function contractJson( export async function createOfflineProject(options: { readonly storageHash: string; readonly models?: readonly FixtureModel[]; + readonly storageNamespaces?: FixtureStorageNamespaces; + readonly storageTypes?: Readonly>; }): Promise { const dir = createTestProjectDir('orm-offline'); created.push(dir); @@ -105,7 +116,14 @@ export async function createOfflineProject(options: { await mkdir(join(dir, 'output'), { recursive: true }); await writeFile( contractPath, - JSON.stringify(contractJson(options.storageHash, options.models)), + JSON.stringify( + contractJson( + options.storageHash, + options.models, + options.storageNamespaces, + options.storageTypes, + ), + ), 'utf-8', ); await writeFile( @@ -173,9 +191,10 @@ export async function seedContractSnapshot(options: { readonly migrationsDir: string; readonly storageHash: string; readonly models?: readonly FixtureModel[]; + readonly storageNamespaces?: FixtureStorageNamespaces; }): Promise { await writeContractSnapshot(options.migrationsDir, options.storageHash, { - contractJson: contractJson(options.storageHash, options.models), + contractJson: contractJson(options.storageHash, options.models, options.storageNamespaces), contractDts: 'export type Contract = never;\n', }); } @@ -320,10 +339,27 @@ function fakePlanner(script: FakePlannerScript): Record { const SQL_POSTGRES = { familyId: 'sql', targetId: 'postgres', version: '1.0.0' }; +/** Storage hydrated into a class instance, as a real family's `deserializeContract` returns it. */ +class HydratedFixtureStorage { + constructor(json: object) { + Object.assign(this, json); + } +} + +function deserializeIntoStorageInstance(json: unknown): unknown { + if (json === null || typeof json !== 'object' || !('storage' in json)) return json; + const { storage } = json; + return storage === null || typeof storage !== 'object' + ? json + : { ...json, storage: new HydratedFixtureStorage(storage) }; +} + export function offlineConfig(options: { readonly project: OfflineProject; readonly script?: FakePlannerScript; readonly targetSupportsMigrations?: boolean; + /** Hydrate storage into a class instance, as real families do, instead of returning the JSON. */ + readonly storageAsClassInstance?: boolean; }): Record { const migrations = { contractToSchema: () => ({}), @@ -335,7 +371,11 @@ export function offlineConfig(options: { kind: 'family', id: 'sql', emission: {}, - create: () => ({ deserializeContract: (json: unknown) => json }), + create: () => ({ + deserializeContract: options.storageAsClassInstance + ? deserializeIntoStorageInstance + : (json: unknown) => json, + }), }, target: { ...SQL_POSTGRES, diff --git a/packages/1-framework/3-tooling/cli/test/orm/migration-plan-statements.test.ts b/packages/1-framework/3-tooling/cli/test/orm/migration-plan-statements.test.ts index 466189f279c9..e503f11d7854 100644 --- a/packages/1-framework/3-tooling/cli/test/orm/migration-plan-statements.test.ts +++ b/packages/1-framework/3-tooling/cli/test/orm/migration-plan-statements.test.ts @@ -211,18 +211,22 @@ describe('migration plan --rename', () => { expect(await plannedDirs(project)).toEqual(['20260101T0000_initial']); }); - it('fails planning when the storage changed but the planner planned nothing, statements or not', async () => { + it('writes a migration with no operations when the storage changed but the planner planned nothing', async () => { const project = await renamingProject(); const run = await harness(project, { operations: [] }).run( - ['migration', 'plan', '--rename', 'Profile:User', '--json'], + ['migration', 'plan', '--name', 'delta', '--rename', 'Profile:User', '--json'], { cwd: project.dir }, ); - expect(run.json.at(-1)).toMatchObject({ - kind: 'result', - envelope: { ok: false, error: { code: 'MIGRATION.PLANNING_FAILED' } }, + expect(run.presented?.data).toMatchObject({ + noOp: false, + operations: [], + appliedStatements: [{ statement: profileToUser, operationIndexes: [] }], }); - expect(await plannedDirs(project)).toEqual(['20260101T0000_initial']); + expect((await plannedDirs(project)).map((dir) => dir.replace(/^\d{8}T\d{4}_/, ''))).toEqual([ + 'initial', + 'delta', + ]); }); it('passes the statements to the delta of an auto-baseline plan only', async () => { @@ -331,7 +335,7 @@ describe('migration plan --rename', () => { }); }); - it('writes nothing when the auto-baseline delta is refused, and only the baseline when it has no operations', async () => { + it('writes nothing when the auto-baseline delta is refused, and the baseline and the delta when it has no operations', async () => { const refusedProject = await renamingProject({ history: false }); const refused = await harness(refusedProject, { refuseStatements: true }).run( ['migration', 'plan', '--name', 'delta', '--rename', 'Profile:User', '--json'], @@ -360,7 +364,7 @@ describe('migration plan --rename', () => { }, }).toEqual({ refused: { kind: 'statementRefused', dirs: [] }, - empty: { kind: 'noDatabaseChange', dirs: ['baseline'] }, + empty: { kind: undefined, dirs: ['baseline', 'delta'] }, }); }); }); diff --git a/packages/1-framework/3-tooling/cli/test/orm/migration-plan.no-operations.test.ts b/packages/1-framework/3-tooling/cli/test/orm/migration-plan.no-operations.test.ts index 05eee6b097cf..bcebaa5c879d 100644 --- a/packages/1-framework/3-tooling/cli/test/orm/migration-plan.no-operations.test.ts +++ b/packages/1-framework/3-tooling/cli/test/orm/migration-plan.no-operations.test.ts @@ -7,6 +7,7 @@ import { createOrmTestCli } from '../helpers/orm-test-cli'; import { ADDITIVE_OP, createOfflineProject, + type FixtureStorageNamespaces, OFFLINE_COMMANDS, type OfflineProject, offlineConfig, @@ -21,28 +22,28 @@ const HASH_TO = `c0ffee${'0'.repeat(58)}`; const HASH_FROM = `beef${'1'.repeat(60)}`; const HASH_OTHER = `dead${'2'.repeat(60)}`; -const NO_DATABASE_CHANGE = - 'The contract changed, but migration plan found nothing to change in the database. That is expected after a Prisma upgrade that changes how contract.json records values, after you switch a field to another codec of the same type, or after you change a control policy.'; -const REPORT_IT = - 'If you expected the database to change, migration plan missed it: report it with the output of `prisma migration plan --json`.'; - beforeEach(resetRenderContractDtsMock); afterEach(removeOfflineProjects); -function cli(project: OfflineProject) { +function cli(project: OfflineProject, options: { readonly storageAsClassInstance?: boolean } = {}) { return createOrmTestCli({ commands: OFFLINE_COMMANDS, groups: BIN_GROUPS, - orm: offlineConfig({ project, script: { operations: [] } }), + orm: offlineConfig({ project, script: { operations: [] }, ...options }), }); } -async function run(project: OfflineProject, args: readonly string[]) { - const result = await cli(project).run([...args, '--json'], { cwd: project.dir }); +async function run( + project: OfflineProject, + args: readonly string[], + options: { readonly storageAsClassInstance?: boolean } = {}, +) { + const result = await cli(project, options).run([...args, '--json'], { cwd: project.dir }); const terminal = result.json.at(-1); return { exitCode: result.exitCode, envelope: terminal !== undefined && terminal.kind === 'result' ? terminal.envelope : undefined, + data: result.presented?.data, }; } @@ -66,39 +67,73 @@ async function migrationsOf(project: OfflineProject) { const dirs = (await readdir(project.appMigrationsDir)).filter((entry) => entry !== 'refs'); return Promise.all( dirs.map(async (dir) => { - const metadata = JSON.parse( - await readFile(join(project.appMigrationsDir, dir, 'migration.json'), 'utf-8'), - ); - return { from: metadata.from, to: metadata.to }; + const packageDir = join(project.appMigrationsDir, dir); + const metadata = JSON.parse(await readFile(join(packageDir, 'migration.json'), 'utf-8')); + const ops = JSON.parse(await readFile(join(packageDir, 'ops.json'), 'utf-8')); + return { from: metadata.from, to: metadata.to, ops }; }), ); } +const STORAGE_FROM: FixtureStorageNamespaces = { + public: { + entries: { + table: { posts: { columns: { id: 'int' } }, users: { columns: { id: 'int' } } }, + valueSet: { Status: { values: ['open'] } }, + }, + }, +}; + +const STORAGE_TO: FixtureStorageNamespaces = { + public: { + entries: { + table: { posts: { columns: { id: 'int' } }, users: { columns: { id: 'int', note: 'text' } } }, + valueSet: { Role: { values: ['admin'] } }, + }, + }, +}; + +const CHANGED_ELEMENT_COORDINATES = [ + { plane: 'storage', namespaceId: 'public', entityKind: 'table', entityName: 'users' }, + { plane: 'storage', namespaceId: 'public', entityKind: 'valueSet', entityName: 'Role' }, + { plane: 'storage', namespaceId: 'public', entityKind: 'valueSet', entityName: 'Status' }, +]; + +const NO_OPERATIONS_TEXT = + 'The storage the contract describes changed, but the database needs no operation for it, so the migration has none. What changed: table "public.users", value set "public.Role", value set "public.Status".'; +const NO_OPERATIONS_SUMMARY = `Planned 0 operation(s). ${NO_OPERATIONS_TEXT}`; + /** The database sits at HASH_FROM, which a migration reaches; the emitted contract is HASH_TO. */ async function projectWithHistory(): Promise { - const project = await createOfflineProject({ storageHash: HASH_TO }); + const project = await createOfflineProject({ + storageHash: HASH_TO, + storageNamespaces: STORAGE_TO, + }); await seedMigrationPackage({ appMigrationsDir: project.appMigrationsDir, dirName: '20260101T0000_initial', from: null, to: HASH_FROM, }); - await seedContractSnapshot({ migrationsDir: project.migrationsDir, storageHash: HASH_FROM }); + await seedContractSnapshot({ + migrationsDir: project.migrationsDir, + storageHash: HASH_FROM, + storageNamespaces: STORAGE_FROM, + }); await seedDbRef({ appMigrationsDir: project.appMigrationsDir, storageHash: HASH_FROM }); return project; } const NOTHING_TO_CREATE = 'This contract describes nothing migration plan can create, so there is no first migration to plan.'; -const NOTHING_TO_BASELINE = { - kind: 'nothingToBaseline', - summary: `The migrations directory is empty, so migration plan starts the migration history with a baseline migration to the contract the db ref points at, ${HASH_FROM}. That contract describes nothing migration plan can create, so there is no baseline to plan.`, - why: 'No command writes a baseline migration with no operations. If you manage the database with `prisma db init` or `prisma db update`, keep using `prisma db update`. No command can start a migration history from this database yet; report it with the output of `prisma migration plan --json`.', -}; /** Records HASH_OTHER as the contract of a `target` ref, so `--to target` names it. */ async function seedOtherContract(project: OfflineProject): Promise { - await seedContractSnapshot({ migrationsDir: project.migrationsDir, storageHash: HASH_OTHER }); + await seedContractSnapshot({ + migrationsDir: project.migrationsDir, + storageHash: HASH_OTHER, + storageNamespaces: STORAGE_TO, + }); await writeRef(join(project.appMigrationsDir, 'refs'), 'target', { hash: HASH_OTHER, invariants: [], @@ -107,32 +142,127 @@ async function seedOtherContract(project: OfflineProject): Promise { /** An empty migrations directory whose db ref points at HASH_FROM. */ async function projectWithDbRefOnly(): Promise { - const project = await createOfflineProject({ storageHash: HASH_TO }); - await seedContractSnapshot({ migrationsDir: project.migrationsDir, storageHash: HASH_FROM }); + const project = await createOfflineProject({ + storageHash: HASH_TO, + storageNamespaces: STORAGE_TO, + }); + await seedContractSnapshot({ + migrationsDir: project.migrationsDir, + storageHash: HASH_FROM, + storageNamespaces: STORAGE_FROM, + }); await seedDbRef({ appMigrationsDir: project.appMigrationsDir, storageHash: HASH_FROM }); return project; } +function writtenWithNoOperations(to: string) { + return { + exitCode: 0, + data: { + ok: true, + noOp: false, + from: HASH_FROM, + to, + operations: [], + changedStorageEntities: CHANGED_ELEMENT_COORDINATES, + summary: NO_OPERATIONS_SUMMARY, + }, + }; +} + describe('migration plan when the planner produces no operations', () => { describe('from an earlier contract', () => { - const fix = `If you deploy with migrations, run \`prisma migration new --from ${HASH_FROM}\` to write a migration with no operations, then \`prisma db migrate\`. If you manage the database with \`prisma db init\` or \`prisma db update\`, run \`prisma db sign\` on each database instead. ${REPORT_IT}`; - - it('names the earlier contract, and migration new --from accepts it', async () => { + it('writes a migration with no operations and names what changed in storage', async () => { const project = await projectWithHistory(); const plan = await run(project, ['migration', 'plan']); - const scaffold = await run(project, ['migration', 'new', '--from', HASH_FROM]); - expect(plan).toMatchObject( - planningFailed({ kind: 'noDatabaseChange', summary: NO_DATABASE_CHANGE, why: fix }), + expect(plan).toMatchObject(writtenWithNoOperations(HASH_TO)); + expect(await migrationsOf(project)).toEqual( + expect.arrayContaining([{ from: HASH_FROM, to: HASH_TO, ops: [] }]), ); - expect({ exitCode: scaffold.exitCode, migrations: await migrationsOf(project) }).toEqual({ - exitCode: 0, - migrations: expect.arrayContaining([{ from: HASH_FROM, to: HASH_TO }]), + }); + + it.each([ + { storage: 'plain JSON', storageAsClassInstance: false }, + { storage: 'a class instance, as real families hydrate it', storageAsClassInstance: true }, + ])( + 'names the storage keys that changed when no storage entity did, with storage as $storage', + async ({ storageAsClassInstance }) => { + const project = await createOfflineProject({ + storageHash: HASH_TO, + storageNamespaces: STORAGE_FROM, + storageTypes: { Short: { codecId: 'sql/varchar@1' } }, + }); + await seedMigrationPackage({ + appMigrationsDir: project.appMigrationsDir, + dirName: '20260101T0000_initial', + from: null, + to: HASH_FROM, + }); + await seedContractSnapshot({ + migrationsDir: project.migrationsDir, + storageHash: HASH_FROM, + storageNamespaces: STORAGE_FROM, + }); + await seedDbRef({ appMigrationsDir: project.appMigrationsDir, storageHash: HASH_FROM }); + + expect(await run(project, ['migration', 'plan'], { storageAsClassInstance })).toMatchObject( + { + exitCode: 0, + data: { + changedStorageEntities: [], + summary: + 'Planned 0 operation(s). The storage the contract describes changed, but the database needs no operation for it, so the migration has none. No storage entity changed; what changed is storage.types.', + }, + }, + ); + }, + ); + + it('names what changed in storage for a migration with operations too, and does not warn', async () => { + const project = await projectWithHistory(); + const harness = createOrmTestCli({ + commands: OFFLINE_COMMANDS, + groups: BIN_GROUPS, + orm: offlineConfig({ project, script: { operations: [ADDITIVE_OP] } }), + }); + + const plan = await harness.run(['migration', 'plan'], { + cwd: project.dir, + isTty: { stdout: true }, + }); + + expect({ + data: plan.presented?.data, + summary: plan.presented?.presentation.human.find((block) => block.kind === 'summary'), + }).toMatchObject({ + data: { + noOp: false, + operations: [expect.objectContaining({ id: ADDITIVE_OP.id })], + changedStorageEntities: CHANGED_ELEMENT_COORDINATES, + summary: 'Planned 1 operation(s)', + }, + summary: { status: 'ok' }, + }); + }); + + it('says so in a warning, above the contract edge', async () => { + const project = await projectWithHistory(); + + const plan = await cli(project).run(['migration', 'plan'], { + cwd: project.dir, + isTty: { stdout: true }, + }); + + expect(plan.presented?.presentation.human).toContainEqual({ + kind: 'summary', + status: 'warn', + text: NO_OPERATIONS_SUMMARY, }); }); - it('gives the same advice when the delta after an automatic baseline has no operations', async () => { + it('writes the delta after an automatic baseline with no operations', async () => { const project = await projectWithDbRefOnly(); const harness = createOrmTestCli({ commands: OFFLINE_COMMANDS, @@ -141,26 +271,26 @@ describe('migration plan when the planner produces no operations', () => { }); const plan = await harness.run(['migration', 'plan', '--json'], { cwd: project.dir }); - const terminal = plan.json.at(-1); - const scaffold = await run(project, ['migration', 'new', '--from', HASH_FROM]); - expect({ - exitCode: plan.exitCode, - envelope: - terminal !== undefined && terminal.kind === 'result' ? terminal.envelope : undefined, - }).toMatchObject( - planningFailed({ kind: 'noDatabaseChange', summary: NO_DATABASE_CHANGE, why: fix }), - ); - expect({ exitCode: scaffold.exitCode, migrations: await migrationsOf(project) }).toEqual({ + expect({ exitCode: plan.exitCode, data: plan.presented?.data }).toMatchObject({ exitCode: 0, - migrations: expect.arrayContaining([ - { from: null, to: HASH_FROM }, - { from: HASH_FROM, to: HASH_TO }, - ]), + data: { + from: HASH_FROM, + to: HASH_TO, + operations: [], + changedStorageEntities: CHANGED_ELEMENT_COORDINATES, + summary: `Planned baseline (1 operation(s)) + 0 operation(s). ${NO_OPERATIONS_TEXT}`, + }, }); + expect(await migrationsOf(project)).toEqual( + expect.arrayContaining([ + { from: null, to: HASH_FROM, ops: [expect.objectContaining({ id: ADDITIVE_OP.id })] }, + { from: HASH_FROM, to: HASH_TO, ops: [] }, + ]), + ); }); - it('names db sign with the --to target and leaves out migration new', async () => { + it('writes a migration with no operations to the --to target', async () => { const project = await projectWithHistory(); await seedMigrationPackage({ appMigrationsDir: project.appMigrationsDir, @@ -171,11 +301,7 @@ describe('migration plan when the planner produces no operations', () => { await seedOtherContract(project); expect(await run(project, ['migration', 'plan', '--to', 'target'])).toMatchObject( - planningFailed({ - kind: 'noDatabaseChange', - summary: NO_DATABASE_CHANGE, - why: `\`prisma migration new\` writes a migration only to the emitted contract; to write one to this target, emit it first. If you manage the database with \`prisma db init\` or \`prisma db update\`, run \`prisma db sign ${HASH_OTHER}\` on each database. ${REPORT_IT}`, - }), + writtenWithNoOperations(HASH_OTHER), ); }); }); @@ -196,7 +322,7 @@ describe('migration plan when the planner produces no operations', () => { ); expect({ exitCode: scaffold.exitCode, migrations: await migrationsOf(project) }).toEqual({ exitCode: 0, - migrations: [{ from: null, to: HASH_TO }], + migrations: [{ from: null, to: HASH_TO, ops: [] }], }); }); @@ -215,25 +341,20 @@ describe('migration plan when the planner produces no operations', () => { }); describe('toward the db ref contract on an empty migrations directory', () => { - it('says there is no baseline to plan, without sending the user to migration new', async () => { + it('writes a baseline with no operations, then the delta', async () => { const project = await projectWithDbRefOnly(); const plan = await run(project, ['migration', 'plan']); - const scaffold = await run(project, ['migration', 'new']); - expect(plan).toMatchObject(planningFailed(NOTHING_TO_BASELINE)); - expect(scaffold).toMatchObject({ - exitCode: 2, - envelope: { ok: false, error: { code: 'MIGRATION.HASH_NOT_IN_GRAPH' } }, + expect(plan).toMatchObject({ + exitCode: 0, + data: { from: HASH_FROM, to: HASH_TO, baselineOperations: [] }, }); - }); - - it('gives the same advice when --to names the destination', async () => { - const project = await projectWithDbRefOnly(); - await seedOtherContract(project); - - expect(await run(project, ['migration', 'plan', '--to', 'target'])).toMatchObject( - planningFailed(NOTHING_TO_BASELINE), + expect(await migrationsOf(project)).toEqual( + expect.arrayContaining([ + { from: null, to: HASH_FROM, ops: [] }, + { from: HASH_FROM, to: HASH_TO, ops: [] }, + ]), ); }); }); diff --git a/packages/1-framework/3-tooling/cli/test/utils/rows-failing-check-advice.test.ts b/packages/1-framework/3-tooling/cli/test/utils/rows-failing-check-advice.test.ts new file mode 100644 index 000000000000..3164cf8ca935 --- /dev/null +++ b/packages/1-framework/3-tooling/cli/test/utils/rows-failing-check-advice.test.ts @@ -0,0 +1,91 @@ +import { migrationRunnerFailure } from '@internal/framework-components/control'; +import { describe, expect, it } from 'vitest'; +import { dbUpdateRunnerFailureError } from '../../src/utils/db-update-failure'; +import { migrateRunnerFailureError } from '../../src/utils/migrate-failure'; + +const PROJECT = { cwd: '/work/app', configPath: undefined, dbGiven: false }; +const FROM = 'sha256:0123'; +const QUERY = `SELECT * FROM "public"."user" WHERE NOT ("address" IS NULL)`; + +const failure = migrationRunnerFailure( + 'MIGRATION.EXECUTION_FAILED', + 'Rows of table "user" fail check "user_address_shape_5c02601b" on column "address", so the check cannot be added.', + { why: `Find them with \`${QUERY}\`.`, meta: { databaseUnchanged: true } }, +); + +const replacing = { + check: 'user_address_shape_5c02601b', + replacedCheck: 'user_address_shape_9a7e984c', +} as const; + +const { replacedCheck: _replaced, ...adding } = replacing; + +function labels(error: { readonly nextActions?: readonly { readonly label: string }[] }) { + return (error.nextActions ?? []).map(({ label }) => label); +} + +describe('the advice when existing rows fail a check the apply adds', () => { + it('on db migrate, says to update the rows in the failed migration between the drop of the old check and the add of the new one', () => { + const error = migrateRunnerFailureError( + failure, + { rowsFailingCheck: replacing }, + { directory: '/work/app/migrations/app/20260101T0000_country', from: FROM }, + PROJECT, + ); + + expect(labels(error)).toEqual([ + 'To keep the data, add an operation that updates those rows to fit check "user_address_shape_5c02601b" to `migrations/app/20260101T0000_country/migration.ts`, after the operation that drops check "user_address_shape_9a7e984c" and before the operation that adds check "user_address_shape_5c02601b", run `node migrations/app/20260101T0000_country/migration.ts`, and re-run `{bin} db migrate`', + ]); + expect(JSON.stringify(error.nextActions)).not.toContain('--to'); + }); + + it('on db migrate, says to fix the rows first when the check replaces none, and to re-run with the same flags', () => { + const error = migrateRunnerFailureError( + failure, + { rowsFailingCheck: adding }, + { directory: '/work/app/migrations/app/20260101T0000_country', from: FROM }, + { ...PROJECT, rerunArgs: ['--advance-ref', 'db'] }, + ); + + expect(labels(error)).toEqual([ + 'Update or delete those rows so they fit check "user_address_shape_5c02601b", then re-run `{bin} db migrate --advance-ref db`', + ]); + }); + + it('on db update, says it cannot keep the data, and to plan the change with migration plan instead', () => { + const error = dbUpdateRunnerFailureError( + failure, + { rowsFailingCheck: replacing }, + { databaseStorageHash: FROM, invocation: PROJECT }, + ); + + expect(labels(error)).toEqual([ + '`{bin} db update` cannot keep this data. Plan the change with `{bin} migration plan --from sha256:0123`, add an operation that updates those rows to fit check "user_address_shape_5c02601b" to the migration.ts it writes, after the operation that drops check "user_address_shape_9a7e984c" and before the operation that adds check "user_address_shape_5c02601b", run `node /migration.ts` for the directory it writes, and apply it with `{bin} db migrate`', + ]); + expect(JSON.stringify(error.nextActions)).not.toContain('drift'); + }); + + it('on db update with no migration at the database contract, says to drop the old check and fix the rows in the database', () => { + const error = dbUpdateRunnerFailureError( + failure, + { rowsFailingCheck: replacing }, + { databaseStorageHash: undefined, invocation: PROJECT }, + ); + + expect(labels(error)).toEqual([ + '`{bin} db update` cannot keep this data, and no migration ends at the contract the database is at, so `{bin} migration plan` cannot plan from it. Drop check "user_address_shape_9a7e984c" in the database yourself, update those rows to fit check "user_address_shape_5c02601b", then re-run `{bin} db update`', + ]); + }); + + it('on db update, says to fix the rows first when the check replaces none', () => { + const error = dbUpdateRunnerFailureError( + failure, + { rowsFailingCheck: adding }, + { databaseStorageHash: FROM, invocation: PROJECT }, + ); + + expect(labels(error)).toEqual([ + 'Update or delete those rows so they fit check "user_address_shape_5c02601b", then re-run `{bin} db update`', + ]); + }); +}); diff --git a/packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts b/packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts index 1abfc678f18d..fa46c107324a 100644 --- a/packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts +++ b/packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts @@ -4,15 +4,21 @@ import type { ContractManyToManyRelation, ContractModelBase, ContractValueObject, + ContractValueObjectField, + ContractWithDomain, CrossReference, JsonValue, + StorageBase, + ValueObjectReference, } from '@internal/contract/types'; +import { valueObjectsWithCoordinates } from '@internal/contract/types'; import type { CodecLookup } from '@internal/framework-components/codec'; import { type ImportSpecifierResolver, keepInternalSpecifiers, type TypesImportSpec, } from '@internal/framework-components/emission'; +import { entityAt } from '@internal/framework-components/ir'; import { type ImportRequirement, renderImports, tsStringLiteral } from '@internal/ts-render'; import { blindCast } from '@internal/utils/casts'; import { emitterError } from './emitter-errors'; @@ -78,6 +84,10 @@ export function generateRootsType(roots: Record | undefi return `{ ${entries} }`; } +function valueObjectFieldTypeText(type: ValueObjectReference): string { + return `{ readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: ${serializeValue(type.namespaceId)}; readonly entityKind: "valueObject"; readonly entityName: ${serializeValue(type.entityName)} }`; +} + function contractFieldModifierSuffix(field: ContractField): string { const many = field.many ? `; readonly many: { readonly elementNullable: ${field.many.elementNullable} }` @@ -97,7 +107,7 @@ export function generateModelFieldEntry(fieldName: string, field: ContractField) return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${nullable}; readonly type: { readonly kind: "scalar"; readonly codecId: ${serializeValue(type.codecId)}${typeParamsSpec} }${mods} }`; } if (type.kind === 'valueObject') { - return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${nullable}; readonly type: { readonly kind: "valueObject"; readonly name: ${serializeValue(type.name)} }${mods} }`; + return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${nullable}; readonly type: ${valueObjectFieldTypeText(type)}${mods} }`; } return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${nullable}; readonly type: ${serializeValue(type)}${mods} }`; } @@ -369,9 +379,9 @@ export function parameterizedOutputTypeFunctionsAlias( return `type ${PARAMETERIZED_OUTPUT_TYPE_FUNCTIONS} = ${aliases.length > 0 ? aliases.join(' | ') : 'never'};`; } -/** `FieldOutputTypes` (`side` `'output'`) or `FieldInputTypes` (`'input'`): the shared generic applied to the emitted contract's domain, with `FieldTypeOverrides`. */ +/** `FieldOutputTypes` (`side` `'output'`) or `FieldInputTypes` (`'input'`): the shared generic applied to the emitted contract's domain, with `FieldTypeOverrides`, and to its storage namespaces. */ export function fieldTypesMapExpression(side: 'output' | 'input'): string { - return `FieldTypesFromDomain`; + return `FieldTypesFromDomain`; } export function generateContractFieldDescriptor(fieldName: string, field: ContractField): string { @@ -385,7 +395,7 @@ export function generateContractFieldDescriptor(fieldName: string, field: Contra return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${field.nullable}; readonly type: { readonly kind: "scalar"; readonly codecId: ${serializeValue(type.codecId)}${typeParamsSpec} }${modStr} }`; } if (type.kind === 'valueObject') { - return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${field.nullable}; readonly type: { readonly kind: "valueObject"; readonly name: ${serializeValue(type.name)} }${modStr} }`; + return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${field.nullable}; readonly type: ${valueObjectFieldTypeText(type)}${modStr} }`; } return `readonly ${serializeObjectKey(fieldName)}: { readonly nullable: ${field.nullable}; readonly type: ${serializeValue(type)}${modStr} }`; } @@ -412,7 +422,7 @@ export function generateValueObjectsDescriptorType( } /** - * `Output` and `Input` for each value object, read from the shared generic's value object types at the namespace that declares it. + * `Output` and `Input` for each value object, read from the shared generic's value object types at the storage namespace that holds it. */ export function generateValueObjectTypeAliases( valueObjectNamespaces: ReadonlyArray, @@ -421,7 +431,7 @@ export function generateValueObjectTypeAliases( return ''; } const generic = (side: 'output' | 'input') => - `ValueObjectTypesFromDomain`; + `ValueObjectTypesFromStorage`; const aliases = [ `type ValueObjectOutputTypes = ${generic('output')};`, `type ValueObjectInputTypes = ${generic('input')};`, @@ -441,7 +451,7 @@ export type TypeOverride = { readonly output?: string; readonly input?: string } * The type of a scalar field that its codec does not give, which the shared field type generic takes as an override: the literal union of the value set it holds, or the text its codec renders for its type parameters. `undefined` when the codec gives both sides, and for a value object or union field, which the generic types itself. */ export function resolveFieldTypeOverride( - field: ContractField, + field: Pick, codecLookup: CodecLookup | undefined, resolvedValueSet: ResolvedFieldValueSet | undefined, ): TypeOverride | undefined { @@ -532,7 +542,7 @@ function renderOverrideTree(tree: OverrideTree): string { } /** - * The values a field's value set holds, with the field's codec. A reference to a domain enum is read from the contract's domain, for model and value object fields alike. Any other model field goes to the family's `resolveStorageValueSet`, which reads its storage. A value object field has no storage of its own. + * The values a model field's value set holds, with the field's codec. A reference to a domain enum is read from the contract's domain. Any other model field goes to the family's `resolveStorageValueSet`, which reads its storage. */ function resolveFieldValueSet( field: ContractField, @@ -559,48 +569,53 @@ function resolveDomainValueSet( }; } -/** `FieldTypeOverrides`: per namespace, `models` or `valueObjects`, owner and field, the type of each field its codec does not give. */ +/** The values of the storage value set a value object field names, with the field's codec. */ +function resolveValueObjectFieldValueSet( + field: ContractValueObjectField, + storage: Pick, +): ResolvedFieldValueSet | undefined { + if (field.valueSet === undefined || field.type.kind !== 'scalar') return undefined; + const valueSet = entityAt<{ readonly values: readonly JsonValue[] }>(storage, field.valueSet); + if (valueSet === undefined) return undefined; + return { encodedValues: valueSet.values, codecId: field.type.codecId }; +} + +/** `FieldTypeOverrides`: per namespace, `models` (domain namespace) or `valueObjects` (storage namespace), owner and field, the type of each field its codec does not give. */ export function generateFieldTypeOverridesMap( - contract: Pick, + contract: ContractWithDomain, codecLookup: CodecLookup | undefined, resolveStorageValueSet: StorageValueSetResolver | undefined, ): string { - return renderTypeOverridesMap( - Object.entries(contract.domain.namespaces).flatMap(([nsId, ns]) => { - const modelFields = Object.entries(ns.models).flatMap(([modelName, model]) => - Object.entries(model.fields).map( - ([fieldName, field]) => - [ - [nsId, 'models', modelName, fieldName], - resolveFieldTypeOverride( - field, - codecLookup, - resolveFieldValueSet(field, contract, () => - resolveStorageValueSet?.(modelName, fieldName, model), - ), + const modelFields = Object.entries(contract.domain.namespaces).flatMap(([nsId, ns]) => + Object.entries(ns.models).flatMap(([modelName, model]) => + Object.entries(model.fields).map( + ([fieldName, field]) => + [ + [nsId, 'models', modelName, fieldName], + resolveFieldTypeOverride( + field, + codecLookup, + resolveFieldValueSet(field, contract, () => + resolveStorageValueSet?.(modelName, fieldName, model), ), - ] as const, - ), - ); - const valueObjects = blindCast< - Record, - 'ns.valueObjects is a ContractValueObject record in the emitted IR (default to {} when absent)' - >(ns.valueObjects ?? {}); - const valueObjectFields = Object.entries(valueObjects).flatMap( - ([valueObjectName, valueObject]) => - Object.entries(valueObject.fields).map( - ([fieldName, field]) => - [ - [nsId, 'valueObjects', valueObjectName, fieldName], - resolveFieldTypeOverride( - field, - codecLookup, - resolveFieldValueSet(field, contract, () => undefined), - ), - ] as const, - ), - ); - return [...modelFields, ...valueObjectFields]; - }), + ), + ] as const, + ), + ), + ); + const valueObjectFields = valueObjectsWithCoordinates(contract.storage).flatMap( + ({ coordinate: { namespaceId: nsId, entityName: valueObjectName }, valueObject }) => + Object.entries(valueObject.fields).map( + ([fieldName, field]) => + [ + [nsId, 'valueObjects', valueObjectName, fieldName], + resolveFieldTypeOverride( + field, + codecLookup, + resolveValueObjectFieldValueSet(field, contract.storage), + ), + ] as const, + ), ); + return renderTypeOverridesMap([...modelFields, ...valueObjectFields]); } diff --git a/packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts b/packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts index 3bbfb2ccc3c4..f17a2ff04395 100644 --- a/packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts +++ b/packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts @@ -1,8 +1,8 @@ -import type { - Contract, - ContractEnum, - ContractModelBase, - ContractValueObject, +import { + type Contract, + type ContractEnum, + type ContractModelBase, + valueObjectsWithCoordinates, } from '@internal/contract/types'; import type { CodecLookup } from '@internal/framework-components/codec'; import { @@ -23,7 +23,6 @@ import { generateImportLines, generateModelsType, generateRootsType, - generateValueObjectsDescriptorType, generateValueObjectTypeAliases, parameterizedOutputTypeFunctionsAlias, renderValueLiteralType, @@ -115,42 +114,24 @@ export function generateContractDts( const rootsType = generateRootsType(contract.roots); - // Flatten value objects across all namespaces — first-name-wins on collision. - const flatValueObjects: Record = {}; - const valueObjectNamespaces: Array = []; - for (const [nsId, ns] of namespaceEntries) { - for (const [voName, vo] of Object.entries( - blindCast< - Record, - 'ns.valueObjects is a ContractValueObject record in the emitted IR (default to {} when absent)' - >(ns.valueObjects ?? {}), - )) { - if (!(voName in flatValueObjects)) { - flatValueObjects[voName] = vo; - valueObjectNamespaces.push([voName, nsId]); - } - } + const firstNamespaceOfValueObject = new Map(); + for (const { + coordinate: { namespaceId, entityName: name }, + } of valueObjectsWithCoordinates(contract.storage)) { + if (!firstNamespaceOfValueObject.has(name)) firstNamespaceOfValueObject.set(name, namespaceId); } - const valueObjects = Object.keys(flatValueObjects).length > 0 ? flatValueObjects : undefined; + const valueObjectNamespaces = [...firstNamespaceOfValueObject]; + const hasValueObjects = valueObjectNamespaces.length > 0; const valueObjectTypeAliases = generateValueObjectTypeAliases(valueObjectNamespaces); - const valueObjectsDescriptor = generateValueObjectsDescriptorType(valueObjects); - // Per-namespace models, value objects, and enum types for the domain.namespaces section. + // Per-namespace models and enum types for the domain.namespaces section. const perNamespaceTypes: Array< - readonly [string, string, string | undefined, string | undefined, string | undefined] + readonly [string, string, string | undefined, string | undefined] > = namespaceEntries.map(([nsId, ns]) => { const nsModels = ns.models; const nsModelsType = generateModelsType(nsModels, (name, model) => emitter.generateModelStorageType(name, model), ); - const nsValueObjects = blindCast< - Record | undefined, - 'ns.valueObjects is an optional ContractValueObject record in the emitted IR' - >(ns.valueObjects); - const nsValueObjectsDescriptor = - nsValueObjects !== undefined && Object.keys(nsValueObjects).length > 0 - ? generateValueObjectsDescriptorType(nsValueObjects) - : undefined; const nsEnums = blindCast< Record | undefined, 'ns.enum is an optional ContractEnum record in the emitted IR' @@ -160,27 +141,17 @@ export function generateContractDts( const nsEnumMemberTypesBlock = hasEnums ? generateEnumMemberTypesBlock(nsEnums, codecLookup) : undefined; - return [ - nsId, - nsModelsType, - nsValueObjectsDescriptor, - nsEnumBlock, - nsEnumMemberTypesBlock, - ] as const; + return [nsId, nsModelsType, nsEnumBlock, nsEnumMemberTypesBlock] as const; }); const domainNamespacesType = perNamespaceTypes - .map(([nsId, nsModelsType, nsValueObjectsDescriptor, nsEnumBlock, nsEnumMemberTypesBlock]) => { - const voLine = - nsValueObjectsDescriptor !== undefined - ? `\n readonly valueObjects: ${nsValueObjectsDescriptor};` - : ''; + .map(([nsId, nsModelsType, nsEnumBlock, nsEnumMemberTypesBlock]) => { const enumLine = nsEnumBlock !== undefined ? `\n readonly enum: ${nsEnumBlock};` : ''; const enumMemberTypesLine = nsEnumMemberTypesBlock !== undefined ? `\n readonly enumMemberTypes?: ${nsEnumMemberTypesBlock};` : ''; - return ` readonly ${serializeObjectKey(nsId)}: {\n readonly models: ${nsModelsType};${voLine}${enumLine}${enumMemberTypesLine}\n }`; + return ` readonly ${serializeObjectKey(nsId)}: {\n readonly models: ${nsModelsType};${enumLine}${enumMemberTypesLine}\n }`; }) .join(';\n'); @@ -239,7 +210,6 @@ ${domainNamespacesType}; readonly capabilities: ${serializeValue(contract.capabilities)}; readonly extensions: ${serializeValue(contract.extensions)};${executionClause} readonly meta: ${serializeValue(contract.meta)}; - ${valueObjects ? `readonly valueObjects: ${valueObjectsDescriptor};` : ''} readonly profileHash: ProfileHash; }; @@ -263,7 +233,7 @@ import type { FieldTypesFromDomain, NamespaceId, ProfileHashBase, - StorageHashBase,${valueObjects ? '\n ValueObjectTypesFromDomain,' : ''} + StorageHashBase,${hasValueObjects ? '\n ValueObjectTypesFromStorage,' : ''} } from '${resolveImportSpecifier('@internal/contract/types')}'; ${body}`; diff --git a/packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts b/packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts index 70dd46fdd3aa..6883ee078750 100644 --- a/packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts @@ -653,22 +653,34 @@ describe('generateContractFieldDescriptor', () => { ); }); - it('generates value object field descriptor', () => { + it('renders a value object field type as the coordinate of its storage value object', () => { const field: ContractField = { nullable: true, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, many: false, }; const result = generateContractFieldDescriptor('homeAddress', field); expect(result).toBe( - 'readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly name: "Address" } }', + 'readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "public"; readonly entityKind: "valueObject"; readonly entityName: "Address" } }', ); }); it('includes the non-nullable list descriptor', () => { const field: ContractField = { nullable: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, many: { elementNullable: false }, }; const result = generateContractFieldDescriptor('addresses', field); @@ -678,11 +690,17 @@ describe('generateContractFieldDescriptor', () => { it('includes element nullability inside the list descriptor', () => { const field: ContractField = { nullable: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, many: { elementNullable: true }, }; expect(generateContractFieldDescriptor('addresses', field)).toBe( - 'readonly addresses: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly name: "Address" }; readonly many: { readonly elementNullable: true } }', + 'readonly addresses: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "public"; readonly entityKind: "valueObject"; readonly entityName: "Address" }; readonly many: { readonly elementNullable: true } }', ); }); @@ -741,15 +759,15 @@ describe('generateValueObjectTypeAliases', () => { expect(generateValueObjectTypeAliases([])).toBe(''); }); - it('reads each value object from the shared generic at the namespace that declares it', () => { + it('reads each value object from the shared generic at the storage namespace that holds it', () => { expect( generateValueObjectTypeAliases([ ['Address', 'public'], ['Point', '__unbound__'], ]).split('\n'), ).toEqual([ - "type ValueObjectOutputTypes = ValueObjectTypesFromDomain;", - "type ValueObjectInputTypes = ValueObjectTypesFromDomain;", + "type ValueObjectOutputTypes = ValueObjectTypesFromStorage;", + "type ValueObjectInputTypes = ValueObjectTypesFromStorage;", 'export type AddressOutput = ValueObjectOutputTypes["public"]["Address"];', 'export type AddressInput = ValueObjectInputTypes["public"]["Address"];', 'export type PointOutput = ValueObjectOutputTypes["__unbound__"]["Point"];', diff --git a/packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts b/packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts index 2f02a75334cd..af7ec956d776 100644 --- a/packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts @@ -133,15 +133,22 @@ describe('emitter integration', () => { }, }, storage: { - tables: { - user: { - columns: { - id: { codecId: 'pg/int4@1', dataType: 'pg/int4', nullable: false }, + namespaces: { + __unbound__: { + id: '__unbound__', + entries: { + table: { + user: { + columns: { + id: { codecId: 'pg/int4@1', dataType: 'pg/int4', nullable: false }, + }, + primaryKey: { columns: ['id'] }, + uniques: [], + indexes: [], + foreignKeys: [], + }, + }, }, - primaryKey: { columns: ['id'] }, - uniques: [], - indexes: [], - foreignKeys: [], }, }, }, diff --git a/packages/1-framework/3-tooling/emitter/test/emitter.test.ts b/packages/1-framework/3-tooling/emitter/test/emitter.test.ts index 204296b91322..fa5db03c4d71 100644 --- a/packages/1-framework/3-tooling/emitter/test/emitter.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/emitter.test.ts @@ -298,7 +298,7 @@ describe('emitter', () => { expect(result.contractDts).toContain("from '@ext/query'"); }); - it('emits value object clauses when the domain namespace declares value objects', async () => { + it('emits value object clauses when storage declares value objects', async () => { const ir = createTestContract({ storage: emptySqlStorage, valueObjects: { @@ -320,45 +320,10 @@ describe('emitter', () => { }; const result = await emit(ir, options, mockSqlHook); - expect(result.contractDts).toContain('readonly valueObjects:'); - expect(result.contractDts).toContain('export type AddressOutput'); - }); - - it('emits per-namespace valueObjects block when a single namespace declares value objects', () => { - const addressModel = { - fields: { - street: { - type: { kind: 'scalar' as const, codecId: 'pg/text@1' }, - nullable: false, - many: false as const, - }, - }, - }; - const contract = { - ...createTestContract(), - domain: { - namespaces: { - public: { - models: {}, - valueObjects: { Address: addressModel }, - }, - }, - }, - }; - const dts = generateContractDts(contract, mockSqlHook, [], { - storageHash: '0000000000000000000000000000000000000000000000000000000000000001', - profileHash: '0000000000000000000000000000000000000000000000000000000000000002', - }); - // The per-namespace valueObjects block must appear inside the namespace block. - // A positive match on the nested structure proves it is inside domain.namespaces.public. - expect(dts).toContain('readonly public:'); - const publicNsIndex = dts.indexOf('readonly public:'); - const afterPublic = dts.slice(publicNsIndex); - // valueObjects must appear before the closing of the namespace block - expect(afterPublic.indexOf('readonly valueObjects:')).toBeGreaterThan(0); - expect(afterPublic.indexOf('readonly valueObjects:')).toBeLessThan( - afterPublic.indexOf('readonly capabilities:'), + expect(result.contractDts).toContain( + "export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address'];", ); + expect(result.contractDts).not.toContain('readonly valueObjects:'); }); it('emits successfully when domain has more than one namespace', () => { diff --git a/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts b/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts index 1314e34533a7..11774a5e7d56 100644 --- a/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts @@ -60,6 +60,13 @@ function makeEnumContract(opts: { return createTestContract({ namespaces: { public: publicNs } }); } +describe('generateContractDts contract type', () => { + it('writes profileHash on the line after meta, with no blank line between them', () => { + const dts = generateContractDts(createTestContract(), createMockSpi(), [], HASHES); + expect(dts).toMatch(/readonly meta: [^\n]*;\n {2}readonly profileHash: ProfileHash;/); + }); +}); + describe('generateContractDts SPI hook plumbing', () => { it('omits extra storage exports when the SPI has no getStorageTypeExports hook', () => { const spi: EmissionSpi = createMockSpi(); diff --git a/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.type-imports.test.ts b/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.type-imports.test.ts index 832c9feb81cd..cc6bf1377229 100644 --- a/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.type-imports.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/generate-contract-dts.type-imports.test.ts @@ -88,7 +88,13 @@ describe('generateContractDts field type maps', () => { }, address: { nullable: true, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'shared', + entityKind: 'valueObject', + entityName: 'Address', + }, many: false, }, }, @@ -96,10 +102,19 @@ describe('generateContractDts field type maps', () => { storage: {}, }, }, - valueObjects: { - Address: { - fields: { - street: { nullable: false, type: { kind: 'scalar', codecId: 'demo/text@1' } }, + }, + }, + storage: { + namespaces: { + shared: { + id: 'shared', + entries: { + valueObject: { + Address: { + fields: { + street: { nullable: false, type: { kind: 'scalar', codecId: 'demo/text@1' } }, + }, + }, }, }, }, @@ -107,27 +122,74 @@ describe('generateContractDts field type maps', () => { }, }); - it('applies the shared generic to the contract domain, with no override for fields the generic types itself', () => { + it('applies the shared generic to the contract domain and storage, with no override for fields the generic types itself', () => { const dts = generateContractDts(contract, createMockSpi(), [CODEC_TYPES], HASHES); expect(dts).toContain('type FieldTypeOverrides = { };'); expect(dts).not.toContain('ValueObjectFieldTypeOverrides'); expect(dts).toContain( - "export type FieldOutputTypes = FieldTypesFromDomain;", + "export type FieldOutputTypes = FieldTypesFromDomain;", ); expect(dts).toContain( - "export type FieldInputTypes = FieldTypesFromDomain;", + "export type FieldInputTypes = FieldTypesFromDomain;", ); expect(dts).toMatch(/\n {2}FieldTypesFromDomain,\n/); }); - it('types each value object alias through the shared generic', () => { + it('passes the storage namespaces to the shared generic for a contract without value objects too', () => { + const dts = generateContractDts(createTestContract(), createMockSpi(), [CODEC_TYPES], HASHES); + + expect(dts).toContain( + "export type FieldOutputTypes = FieldTypesFromDomain;", + ); + expect(dts).not.toContain('ValueObjectTypesFromStorage'); + }); + + it('types each value object alias through the shared generic, at the storage namespace that holds it', () => { const dts = generateContractDts(contract, createMockSpi(), [CODEC_TYPES], HASHES); expect(dts).toContain( - 'export type AddressOutput = ValueObjectOutputTypes["public"]["Address"];', + 'export type AddressOutput = ValueObjectOutputTypes["shared"]["Address"];', ); - expect(dts).toMatch(/\n {2}ValueObjectTypesFromDomain,\n/); + expect(dts).toMatch(/\n {2}ValueObjectTypesFromStorage,\n/); + }); + + it('writes one alias for a value object name that two storage namespaces hold, from the first', () => { + const address = { + fields: { street: { nullable: false, type: { kind: 'scalar', codecId: 'demo/text@1' } } }, + }; + const dts = generateContractDts( + createTestContract({ + storage: { + namespaces: { + first: { id: 'first', entries: { valueObject: { Address: address } } }, + second: { id: 'second', entries: { valueObject: { Address: address } } }, + }, + }, + }), + createMockSpi(), + [CODEC_TYPES], + HASHES, + ); + + expect(dts.match(/export type AddressOutput =/g)).toHaveLength(1); + expect(dts).toContain( + 'export type AddressOutput = ValueObjectOutputTypes["first"]["Address"];', + ); + }); + + it('renders a value object field as the coordinate of its storage value object', () => { + const dts = generateContractDts(contract, createMockSpi(), [CODEC_TYPES], HASHES); + + expect(dts).toContain( + 'readonly address: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "shared"; readonly entityKind: "valueObject"; readonly entityName: "Address" } }', + ); + }); + + it('describes value objects only in storage, not in the domain or at the top level', () => { + const dts = generateContractDts(contract, createMockSpi(), [CODEC_TYPES], HASHES); + + expect(dts).not.toContain('readonly valueObjects:'); }); it('types the fields of a Models member by indexing FieldOutputTypes', () => { diff --git a/packages/1-framework/3-tooling/emitter/test/type-overrides.test.ts b/packages/1-framework/3-tooling/emitter/test/type-overrides.test.ts index d9729f9414ad..e0cc893e083a 100644 --- a/packages/1-framework/3-tooling/emitter/test/type-overrides.test.ts +++ b/packages/1-framework/3-tooling/emitter/test/type-overrides.test.ts @@ -1,4 +1,9 @@ -import type { Contract, ContractField, ContractModelBase } from '@internal/contract/types'; +import type { + Contract, + ContractField, + ContractModelBase, + StorageBase, +} from '@internal/contract/types'; import type { CodecLookup } from '@internal/framework-components/codec'; import { describe, expect, it } from 'vitest'; import { @@ -63,6 +68,14 @@ describe('renderCodecTypeOverride', () => { }); }); +const addressRef = { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'shared', + entityKind: 'valueObject', + entityName: 'Address', +} as const; + describe('resolveFieldTypeOverride', () => { const scalar = (typeParams?: Record): ContractField => ({ nullable: true, @@ -103,16 +116,13 @@ describe('resolveFieldTypeOverride', () => { const valueObject: ContractField = { nullable: true, many: false, - type: { kind: 'valueObject', name: 'Address' }, + type: addressRef, }; const union: ContractField = { nullable: false, type: { kind: 'union', - members: [ - { kind: 'scalar', codecId: 'demo/text@1' }, - { kind: 'valueObject', name: 'Address' }, - ], + members: [{ kind: 'scalar', codecId: 'demo/text@1' }, addressRef], }, }; expect([ @@ -212,7 +222,8 @@ describe('generateFieldTypeOverridesMap', () => { }, }, }, - } satisfies Pick; + storage: { namespaces: {} }, + } satisfies Pick & { storage: Pick }; const resolveValueSet = (_model: string, field: string) => field === 'mood' ? { encodedValues: ['up', 'down'], codecId: 'demo/text@1' } : undefined; @@ -240,7 +251,8 @@ describe('generateFieldTypeOverridesMap', () => { }, }, }, - } satisfies Pick; + storage: { namespaces: {} }, + } satisfies Pick & { storage: Pick }; const resolveValueSet = () => ({ encodedValues: ['storage'], codecId: 'demo/text@1' }); expect(generateFieldTypeOverridesMap(contract, literalLookup, resolveValueSet)).toBe( @@ -248,25 +260,33 @@ describe('generateFieldTypeOverridesMap', () => { ); }); - it('collects the override of each value object field, from the domain enum its value set names or its codec text', () => { + it('collects the override of each value object field, keyed by its storage namespace, from the storage value set its value set names or its codec text', () => { const contract = { - domain: { + domain: { namespaces: { public: { models: {} } } }, + storage: { namespaces: { - public: { - models: {}, - enum: { Kind: kindEnum }, - valueObjects: { - Address: { - fields: { - street: { nullable: false, type: { kind: 'scalar', codecId: 'demo/text@1' } }, - kind: { - nullable: true, - type: { kind: 'scalar', codecId: 'demo/text@1' }, - valueSet: kindRef, - }, - embedding: { - nullable: false, - type: { kind: 'scalar', codecId: 'demo/vector@1', typeParams: { length: 2 } }, + shared: { + id: 'shared', + entries: { + valueSet: { Kind: { values: ['home', 'work'] } }, + valueObject: { + Address: { + fields: { + street: { nullable: false, type: { kind: 'scalar', codecId: 'demo/text@1' } }, + kind: { + nullable: true, + type: { kind: 'scalar', codecId: 'demo/text@1' }, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'shared', + entityName: 'Kind', + }, + }, + embedding: { + nullable: false, + type: { kind: 'scalar', codecId: 'demo/vector@1', typeParams: { length: 2 } }, + }, }, }, }, @@ -274,30 +294,33 @@ describe('generateFieldTypeOverridesMap', () => { }, }, }, - } as const satisfies Pick; + } as const satisfies Pick & { storage: Pick }; expect(generateFieldTypeOverridesMap(contract, literalLookup, undefined)).toBe( - '{ readonly public: { readonly valueObjects: { readonly Address: { readonly embedding: { readonly output: Vector<2>; readonly input: VectorInput<2> }; readonly kind: { readonly output: "home" | "work"; readonly input: "home" | "work" } } } } }', + '{ readonly shared: { readonly valueObjects: { readonly Address: { readonly embedding: { readonly output: Vector<2>; readonly input: VectorInput<2> }; readonly kind: { readonly output: "home" | "work"; readonly input: "home" | "work" } } } } }', ); }); - it('gives no override for a value object field whose value set is held in storage, which it cannot name', () => { + it('gives no override for a value object field whose value set storage does not have', () => { const contract = { - domain: { + domain: { namespaces: { public: { models: {} } } }, + storage: { namespaces: { - public: { - models: {}, - valueObjects: { - Address: { - fields: { - kind: { - nullable: false, - type: { kind: 'scalar', codecId: 'demo/text@1' }, - valueSet: { - plane: 'storage', - entityKind: 'valueSet', - namespaceId: 'public', - entityName: 'Kind', + shared: { + id: 'shared', + entries: { + valueObject: { + Address: { + fields: { + kind: { + nullable: false, + type: { kind: 'scalar', codecId: 'demo/text@1' }, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'shared', + entityName: 'Kind', + }, }, }, }, @@ -306,7 +329,7 @@ describe('generateFieldTypeOverridesMap', () => { }, }, }, - } as const satisfies Pick; + } as const satisfies Pick & { storage: Pick }; const resolveValueSet = () => ({ encodedValues: ['storage'], codecId: 'demo/text@1' }); expect(generateFieldTypeOverridesMap(contract, literalLookup, resolveValueSet)).toBe('{ }'); diff --git a/packages/1-framework/3-tooling/emitter/test/utils.ts b/packages/1-framework/3-tooling/emitter/test/utils.ts index 02ed97e7f9df..cc7a06fdc93a 100644 --- a/packages/1-framework/3-tooling/emitter/test/utils.ts +++ b/packages/1-framework/3-tooling/emitter/test/utils.ts @@ -19,10 +19,7 @@ const identitySerialize = (c: Contract): JsonObject => c as unknown as JsonObjec const identityDeserialize = (json: Record): Contract => { const contract = structuredClone(json) as unknown as Contract; for (const namespace of Object.values(contract.domain.namespaces)) { - for (const entity of [ - ...Object.values(namespace.models), - ...Object.values(namespace.valueObjects ?? {}), - ]) { + for (const entity of Object.values(namespace.models)) { for (const [name, field] of Object.entries(entity.fields)) { entity.fields[name] = { ...field, many: field.many ?? false }; } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/build-mongo-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/build-mongo-storage.ts index 1815aec6036e..06f1933ffd28 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/build-mongo-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/build-mongo-storage.ts @@ -3,22 +3,26 @@ import { mongoContractCanonicalizationHooks } from './canonicalization-hooks'; import { buildMongoNamespace } from './ir/build-mongo-namespace'; import type { MongoCollectionInput } from './ir/mongo-collection'; import { MongoStorage } from './ir/mongo-storage'; +import type { MongoValueObjectInput } from './ir/mongo-value-object'; import type { MongoValueSetInput } from './ir/mongo-value-set'; /** - * Builds a Mongo contract's storage: the namespace `namespaceId` holding the collections and value sets, and the storage hash that `db sign` and `db verify` compare. Every Mongo contract source calls this, so they hash storage the same way. + * Builds a Mongo contract's storage: the namespace `namespaceId` holding the collections, value sets and value objects, and the storage hash that `db sign` and `db verify` compare. Every Mongo contract source calls this, so they hash storage the same way. */ export function buildMongoStorage(input: { readonly namespaceId: string; readonly collections: Readonly>; readonly valueSets: Readonly>; + readonly valueObjects?: Readonly>; }): MongoStorage { const hasValueSets = Object.keys(input.valueSets).length > 0; + const valueObjects = input.valueObjects ?? {}; const namespace = buildMongoNamespace({ id: input.namespaceId, entries: { collection: input.collections, ...(hasValueSets ? { valueSet: input.valueSets } : {}), + ...(Object.keys(valueObjects).length > 0 ? { valueObject: valueObjects } : {}), }, }); const storageHash = computeStorageHash({ @@ -33,6 +37,9 @@ export function buildMongoStorage(input: { ...(namespace.entries.valueSet !== undefined ? { valueSet: namespace.entries.valueSet } : {}), + ...(namespace.entries.valueObject !== undefined + ? { valueObject: namespace.entries.valueObject } + : {}), }, }, }, diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-refusals.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-refusals.ts index 6be2a17995ab..00c3c5e634b6 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-refusals.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-refusals.ts @@ -106,7 +106,7 @@ export function lookupFreeRefusalsOf( } /** - * The enums a collection validator lists: those stored by a model in a managed collection, by one of its variants or embedded models, or by a value object any of them holds. + * The enums a collection validator lists: those stored by a model in a managed collection, by one of its variants or embedded models, or by a value object any of them holds. A model field refers to its domain enum and a value object member to the enum's storage value set; both carry the enum's name. */ function validatedEnumNames(input: MongoContractRefusalsInput): ReadonlySet { const { models, valueObjects, controlPolicies } = input; @@ -119,10 +119,10 @@ function validatedEnumNames(input: MongoContractRefusalsInput): ReadonlySet(); const visitFields = (fields: Record) => { for (const field of Object.values(fields)) { - if (field.valueSet?.entityKind === 'enum') enums.add(field.valueSet.entityName); - if (field.type.kind === 'valueObject' && !visitedValueObjects.has(field.type.name)) { - visitedValueObjects.add(field.type.name); - visitFields(valueObjects[field.type.name]?.fields ?? {}); + if (field.valueSet !== undefined) enums.add(field.valueSet.entityName); + if (field.type.kind === 'valueObject' && !visitedValueObjects.has(field.type.entityName)) { + visitedValueObjects.add(field.type.entityName); + visitFields(valueObjects[field.type.entityName]?.fields ?? {}); } } }; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts index 476393962250..cfae8ad555e2 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts @@ -1,32 +1,14 @@ -import { ContractExecutionSectionSchema, CrossReferenceSchema } from '@internal/contract/types'; +import { + ContractExecutionSectionSchema, + ContractFieldTypeSchema, + ContractValueObjectSchema, + CrossReferenceSchema, +} from '@internal/contract/types'; import { type Type, type } from 'arktype'; import type { MongoJsonObject, MongoJsonPrimitive, MongoJsonValue } from './contract-types'; const ControlPolicySchema = type("'managed' | 'tolerated' | 'external' | 'observed'"); -const ScalarFieldTypeSchema = type({ - '+': 'reject', - kind: "'scalar'", - codecId: 'string', - 'typeParams?': 'Record', -}); - -const ValueObjectFieldTypeSchema = type({ - '+': 'reject', - kind: "'valueObject'", - name: 'string', -}); - -const UnionFieldTypeSchema = type({ - '+': 'reject', - kind: "'union'", - members: ScalarFieldTypeSchema.or(ValueObjectFieldTypeSchema).array(), -}); - -const FieldTypeSchema = ScalarFieldTypeSchema.or(ValueObjectFieldTypeSchema).or( - UnionFieldTypeSchema, -); - const DomainEnumRefSchema = type({ plane: "'domain'", namespaceId: 'string', @@ -49,7 +31,7 @@ const ContractEnumSchema = type({ const RawFieldSchema = type({ '+': 'reject', - type: FieldTypeSchema, + type: ContractFieldTypeSchema, 'nullable?': 'boolean', 'many?': type('false').or({ elementNullable: 'boolean' }), 'dict?': 'boolean', @@ -418,6 +400,7 @@ export function createMongoNamespaceEnvelopeSchema( entries: type({ 'collection?': type({ '[string]': collectionEntrySchema(fragments) }), 'valueSet?': type({ '[string]': StorageValueSetSchema }), + 'valueObject?': type({ '[string]': ContractValueObjectSchema }), }), }).narrow((ns, ctx) => { if (typeof ns !== 'object' || ns === null || Array.isArray(ns)) { @@ -460,10 +443,8 @@ export function createMongoContractSchema( domain: type({ namespaces: type({ '[string]': type({ + '+': 'reject', models: type({ '[string]': ModelDefinitionSchema }), - 'valueObjects?': type({ - '[string]': type({ '+': 'reject', fields: type({ '[string]': FieldSchema }) }), - }), 'enum?': type({ '[string]': ContractEnumSchema }), }), }), diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/derive-json-schema.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/derive-json-schema.ts index 62c7b20deab8..da52f404d9ce 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/derive-json-schema.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/derive-json-schema.ts @@ -5,13 +5,15 @@ import type { ContractRelation, ContractValueObject, JsonValue, + StorageBase, } from '@internal/contract/types'; +import { valueObjectAt } from '@internal/contract/types'; import type { TypeLookups } from '@internal/framework-components/codec'; import { assertDefined } from '@internal/utils/assertions'; import type { MongoModelStorage } from './contract-types'; import { MongoValidator } from './ir/mongo-validator'; import { bsonTypesOfCodec } from './mongo-data-type'; -import { recursiveGroupsOf, typeKey } from './recursive-groups'; +import { modelKey, recursiveGroupsOf, valueObjectKey } from './recursive-groups'; import { isEmbedRelation } from './relation-fields'; import { DOCUMENT_ID, @@ -46,7 +48,7 @@ interface SchemaSources extends ModelValidatorSources { const OPEN_OBJECT: Record = { bsonType: 'object' }; function sourcesFor(input: ModelValidatorSources, rootKey: string): SchemaSources { - const groups = recursiveGroupsOf(input.valueObjects, input.models); + const groups = recursiveGroupsOf(input.storage, input.models); const rootGroup = groups.get(rootKey); return { ...input, @@ -56,12 +58,10 @@ function sourcesFor(input: ModelValidatorSources, rootKey: string): SchemaSource } function objectSchemaOf( - kind: 'valueObject' | 'model', - name: string, + key: string, shape: StoredShape, sources: SchemaSources, ): Record { - const key = typeKey(kind, name); const group = sources.groups.get(key); if (group !== undefined && sources.enteredGroups.has(group)) return OPEN_OBJECT; return deriveObjectSchema(shape, entering(key, sources)); @@ -131,7 +131,7 @@ function fieldToBsonSchema( field: ContractField, sources: SchemaSources, ): Record | undefined { - const { lookups, valueObjects, valueSets } = sources; + const { lookups, storage, valueSets } = sources; if (field.type.kind === 'scalar') { const bsonTypes = bsonTypesOfCodec(field.type.codecId, lookups); assertDefined( @@ -166,9 +166,9 @@ function fieldToBsonSchema( } if (field.type.kind === 'valueObject') { - const vo = valueObjects[field.type.name]; + const vo = valueObjectAt(storage, field.type); if (!vo) return undefined; - const voSchema = objectSchemaOf('valueObject', field.type.name, valueObjectShape(vo), sources); + const voSchema = objectSchemaOf(valueObjectKey(field.type), valueObjectShape(vo), sources); if (field.many) { return { bsonType: field.nullable ? ['null', 'array'] : 'array', @@ -194,7 +194,7 @@ function embedToBsonSchema( const embeddedName = relation.to.model; const embedded = sources.models[embeddedName]; assertDefined(embedded, `Embedded model "${embeddedName}" is missing`); - const schema = objectSchemaOf('model', embeddedName, modelShape(embedded), sources); + const schema = objectSchemaOf(modelKey(embeddedName), modelShape(embedded), sources); return relation.cardinality === '1:N' ? { bsonType: 'array', items: schema } : schema; } @@ -284,7 +284,7 @@ function polymorphicSchema( for (const variant of variants) { const { properties: variantProperties, required } = propertiesOf( variant, - entering(typeKey('model', variant.modelName), sources), + entering(modelKey(variant.modelName), sources), base, ); @@ -310,7 +310,8 @@ function polymorphicSchema( export interface ModelValidatorSources { readonly models: Record; readonly lookups: TypeLookups; - readonly valueObjects: Record; + /** The storage the value objects are read from, at the coordinate each field names. */ + readonly storage: Pick; readonly valueSets: FieldValueSets; } @@ -322,7 +323,7 @@ export function deriveModelJsonSchema( model: ValidatorModel, sources: ModelValidatorSources, ): Record { - const schemaSources = sourcesFor(sources, typeKey('model', modelName)); + const schemaSources = sourcesFor(sources, modelKey(modelName)); if (!model.discriminator || !model.variants) { return deriveObjectSchema(modelShape(model), schemaSources, { isDocument: true }); } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/entity-kinds.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/entity-kinds.ts index a7b53915387a..88b2abb94b3e 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/entity-kinds.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/entity-kinds.ts @@ -1,3 +1,4 @@ +import { ContractValueObjectSchema } from '@internal/contract/types'; import type { AnyEntityKindDescriptor, EntityKindDescriptor, @@ -5,6 +6,7 @@ import type { import { structuredError } from '@internal/utils/structured-error'; import { StorageCollectionSchema, StorageValueSetSchema } from './contract-schema'; import { MongoCollection, type MongoCollectionInput } from './ir/mongo-collection'; +import { MongoValueObject, type MongoValueObjectInput } from './ir/mongo-value-object'; import { MongoValueSet, type MongoValueSetInput } from './ir/mongo-value-set'; export const collectionEntityKind: EntityKindDescriptor = { @@ -19,9 +21,16 @@ export const valueSetEntityKind: EntityKindDescriptor new MongoValueSet(input), }; +export const valueObjectEntityKind: EntityKindDescriptor = + { + kind: 'valueObject', + schema: ContractValueObjectSchema, + construct: (input) => new MongoValueObject(input), + }; + /** * Assembles the `kind → descriptor` registry for Mongo namespaces: the built-in - * `collection` kind plus any target `packKinds`. This builds the lookup table — + * `collection`, `valueSet` and `valueObject` kinds plus any target `packKinds`. This builds the lookup table — * it does not touch contract data. `hydrateNamespaceEntities` later consumes * this registry to turn a namespace's raw entries into IR instances. Throws on * a duplicate kind. @@ -32,6 +41,7 @@ export function composeMongoEntityKinds( const kinds = new Map([ ['collection', collectionEntityKind], ['valueSet', valueSetEntityKind], + ['valueObject', valueObjectEntityKind], ]); for (const descriptor of packKinds) { if (kinds.has(descriptor.kind)) { diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/entity-kinds.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/entity-kinds.ts index 1de79a414914..05bb30d4a848 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/entity-kinds.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/entity-kinds.ts @@ -1,5 +1,6 @@ export { collectionEntityKind, composeMongoEntityKinds, + valueObjectEntityKind, valueSetEntityKind, } from '../entity-kinds'; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts index 3514c18f1d26..1690a1877619 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.ts @@ -111,6 +111,8 @@ export type { MongoValidatorValidationLevel, } from '../ir/mongo-validator'; export { MongoValidator } from '../ir/mongo-validator'; +export type { MongoValueObjectInput } from '../ir/mongo-value-object'; +export { MongoValueObject } from '../ir/mongo-value-object'; export type { MongoValueSetInput } from '../ir/mongo-value-set'; export { MongoValueSet } from '../ir/mongo-value-set'; export { mongoValueSetsOf } from '../mongo-enum'; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts index de612c82e520..a38d1f972d8e 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.ts @@ -6,11 +6,13 @@ import { type Storage, } from '@internal/framework-components/ir'; import type { MongoCollection, MongoCollectionInput } from './mongo-collection'; +import type { MongoValueObject, MongoValueObjectInput } from './mongo-value-object'; import type { MongoValueSet, MongoValueSetInput } from './mongo-value-set'; export type MongoNamespaceEntries = Readonly>>> & { readonly collection?: Readonly>; readonly valueSet?: Readonly>; + readonly valueObject?: Readonly>; }; export interface MongoNamespaceCollectionsInput { @@ -18,6 +20,7 @@ export interface MongoNamespaceCollectionsInput { readonly entries: Readonly>>> & { readonly collection?: Readonly>; readonly valueSet?: Readonly>; + readonly valueObject?: Readonly>; }; } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-value-object.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-value-object.ts new file mode 100644 index 000000000000..4a1c9a60113a --- /dev/null +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-value-object.ts @@ -0,0 +1,34 @@ +import type { ContractValueObjectField } from '@internal/contract/types'; +import { freezeNode, IRNodeBase } from '@internal/framework-components/ir'; + +/** Hydration / construction input shape for {@link MongoValueObject}; mirrors the stored JSON, where a field may leave out `many`. */ +export interface MongoValueObjectInput { + readonly fields: Readonly>; +} + +/** + * Mongo Contract IR node for a value object in a namespace's `valueObject` map (`entries.valueObject`): the fields every subdocument that holds it has (ADR 309). Same shape as SQL's `StorageValueObject`. A domain field refers to it by its storage coordinate. The `kind` is not enumerable, so the stored JSON holds the fields only, as in SQL. Value objects are keyed by name in the parent namespace's `valueObject` map, so the entry name is not on the class. + */ +export class MongoValueObject extends IRNodeBase { + declare readonly kind: 'mongo-value-object'; + readonly fields: Readonly>; + + constructor(input: MongoValueObjectInput) { + super(); + Object.defineProperty(this, 'kind', { + value: 'mongo-value-object', + writable: false, + enumerable: false, + configurable: false, + }); + this.fields = Object.freeze( + Object.fromEntries( + Object.entries(input.fields).map(([name, field]) => [ + name, + Object.freeze({ ...field, many: field.many ?? false }), + ]), + ), + ); + freezeNode(this); + } +} diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/recursive-groups.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/recursive-groups.ts index 29681cebcf01..6b30acc539bd 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/recursive-groups.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/recursive-groups.ts @@ -1,33 +1,43 @@ -import type { ContractField, ContractValueObject } from '@internal/contract/types'; +import { + type ContractField, + type EntityCoordinate, + type StorageBase, + valueObjectsWithCoordinates, +} from '@internal/contract/types'; import type { ValidatorModel } from './derive-json-schema'; import { isEmbedRelation } from './relation-fields'; -export function typeKey(kind: 'valueObject' | 'model', name: string): string { - return `${kind}:${name}`; +export function modelKey(name: string): string { + return `model:${name}`; +} + +/** The key of a value object, by its storage coordinate, so value objects of one name in two namespaces stay apart. */ +export function valueObjectKey(reference: EntityCoordinate<'storage', 'valueObject'>): string { + return `valueObject:${JSON.stringify([reference.namespaceId, reference.entityName])}`; } function referencesOf(fields: Record): string[] { return Object.values(fields).flatMap((field) => - field.type.kind === 'valueObject' ? [typeKey('valueObject', field.type.name)] : [], + field.type.kind === 'valueObject' ? [valueObjectKey(field.type)] : [], ); } /** - * The recursive groups of value objects and embedded models: each set of types that can reach each other through value-object fields and embed relations (a strongly connected component of that graph), and each type that refers to itself. Maps the {@link typeKey} of every type in such a group to the group's number; a type in no recursive group is absent. + * The recursive groups of value objects and embedded models: each set of types that can reach each other through value-object fields and embed relations (a strongly connected component of that graph), and each type that refers to itself. Maps the {@link valueObjectKey} or {@link modelKey} of every type in such a group to the group's number; a type in no recursive group is absent. */ export function recursiveGroupsOf( - valueObjects: Record, + storage: Pick, models: Record>, ): ReadonlyMap { const edges = new Map(); - for (const [name, valueObject] of Object.entries(valueObjects)) { - edges.set(typeKey('valueObject', name), referencesOf(valueObject.fields)); + for (const { coordinate, valueObject } of valueObjectsWithCoordinates(storage)) { + edges.set(valueObjectKey(coordinate), referencesOf(valueObject.fields)); } for (const [name, model] of Object.entries(models)) { const embeds = Object.values(model.relations).flatMap((relation) => - isEmbedRelation(relation) ? [typeKey('model', relation.to.model)] : [], + isEmbedRelation(relation) ? [modelKey(relation.to.model)] : [], ); - edges.set(typeKey('model', name), [...referencesOf(model.fields), ...embeds]); + edges.set(modelKey(name), [...referencesOf(model.fields), ...embeds]); } const groups = new Map(); diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts index d0edc78041f4..2c9d9cb9ef01 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/src/validate-storage.ts @@ -1,4 +1,5 @@ import type { ContractModel, ContractRelation } from '@internal/contract/types'; +import { validateStorageValueObjects } from '@internal/contract/validate-storage'; import { structuredError } from '@internal/utils/structured-error'; import { collectionRootModelsOf } from './collection-root-models'; import { lookupFreeRefusalsOf } from './contract-refusals'; @@ -162,6 +163,7 @@ function bothStoredAs( } export function validateMongoStorage(contract: MongoContract): void { + validateStorageValueObjects(contract.storage); const errors: string[] = []; let fieldStorageComplete = true; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/collection-validator-helpers.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/collection-validator-helpers.ts index b3a22d9f1643..143d3ac2aba3 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/collection-validator-helpers.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/collection-validator-helpers.ts @@ -1,4 +1,10 @@ -import type { ContractEmbedRelation, ContractField, ContractModel } from '@internal/contract/types'; +import type { + ContractEmbedRelation, + ContractField, + ContractModel, + ContractValueObject, + StorageBase, +} from '@internal/contract/types'; import { type CrossReference, crossRef } from '@internal/contract/types'; import type { MongoModelStorage } from '../src/contract-types'; import { @@ -47,14 +53,30 @@ export function embedMany(to: string): ContractEmbedRelation { return { to: crossRef(to), cardinality: '1:N' }; } +/** Storage holding `valueObjects` in the unbound namespace, where the test helpers' value object fields point. */ +export function storageWithValueObjects( + valueObjects: Record, +): Pick { + return { + namespaces: { __unbound__: { id: '__unbound__', entries: { valueObject: valueObjects } } }, + }; +} + +/** Test inputs that may give value objects by name, as stored in the unbound namespace, in place of storage. */ +export type ValidatorTestSources = Partial< + Pick & { + readonly valueObjects: Record; + } +>; + /** The validators of `models`, with each model in `roots` stored in the collection that keys it. */ export function derive( input: { readonly roots: Record; } & Pick & - Partial>, + ValidatorTestSources, ) { - const { roots, models, ...sources } = input; + const { roots, models, valueObjects, ...sources } = input; const stored = { ...models }; for (const [collection, root] of Object.entries(roots)) { const model = stored[root.model]; @@ -64,7 +86,7 @@ export function derive( } return deriveCollectionValidators({ lookups: mongoTypeLookups, - valueObjects: {}, + storage: storageWithValueObjects(valueObjects ?? {}), valueSets: {}, controlPolicies: { collections: {}, defaultControlPolicy: undefined }, ...sources, @@ -87,7 +109,7 @@ function itemsValidator(validators: Record): MongoValida /** The validator of collection `items`, whose root model has `fields`. */ export function rootValidator( fields: Record, - sources: Partial> = {}, + sources: Pick = {}, ): MongoValidator { return itemsValidator( derive({ roots: { items: crossRef('Item') }, models: { Item: model(fields) }, ...sources }), diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-refusals.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-refusals.test.ts index 46bc6fa464a7..a670c7dad9f3 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-refusals.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-refusals.test.ts @@ -3,7 +3,13 @@ import { describe, expect, it } from 'vitest'; import { mongoContractRefusalsOf } from '../src/contract-refusals'; import type { MongoModelStorage } from '../src/contract-types'; import { mongoTypeLookups } from '../src/fake-type-lookups'; -import { arrayField, enumField, scalarField, voField } from './derive-json-schema-helpers'; +import { + arrayField, + enumField, + memberEnumField, + scalarField, + voField, +} from './derive-json-schema-helpers'; function model( fields: ContractModel['fields'], @@ -154,16 +160,26 @@ describe('mongoContractRefusalsOf', () => { ).toEqual([]); }); - it('refuses an enum a validator cannot list, used in a value object of a validated collection', () => { + it('refuses an enum a validator cannot list, used by a value object member through its storage value set', () => { expect( refusals({ - enums: { Big: unlistableEnums.Big }, - valueObjects: { Sizing: { fields: { size: enumField('mongo/int64@1', 'Big') } } }, + enums: unlistableEnums, + valueObjects: { + Sizing: { + fields: { + size: memberEnumField('mongo/int64@1', 'Big'), + ratio: memberEnumField('mongo/double@1', 'Ratio'), + }, + }, + }, models: { Job: model({ sizing: voField('Sizing') }, { storage: { collection: 'jobs' } }), }, }), - ).toEqual([expect.objectContaining({ kind: 'enumNotListable', enumName: 'Big' })]); + ).toEqual([ + expect.objectContaining({ kind: 'enumNotListable', enumName: 'Big' }), + expect.objectContaining({ kind: 'enumMemberNotListable', enumName: 'Ratio' }), + ]); }); it('refuses an enum a validator cannot list, and a member it cannot list', () => { diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts index 233564c4c00f..546fef8d34d6 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts @@ -198,16 +198,34 @@ type ContractWithVO = MongoContractWithTypeMaps< readonly homeAddress: { readonly nullable: false; readonly many: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly workAddress: { readonly nullable: true; readonly many: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly previousAddresses: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly many: { readonly elementNullable: false }; }; }; @@ -215,27 +233,6 @@ type ContractWithVO = MongoContractWithTypeMaps< readonly storage: { readonly collection: 'users' }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly city: { - readonly nullable: false; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; @@ -244,7 +241,30 @@ type ContractWithVO = MongoContractWithTypeMaps< readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'mongo-namespace'; - readonly entries: { readonly collection: { readonly users: MongoCollection } }; + readonly entries: { + readonly collection: { readonly users: MongoCollection }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly street: { + readonly nullable: false; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly city: { + readonly nullable: false; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; }; }; readonly storageHash: StorageHashBase<'test-storage'>; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-collection-validators.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-collection-validators.test.ts index e6538f4b147b..62dcc5cbb542 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-collection-validators.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-collection-validators.test.ts @@ -131,6 +131,47 @@ describe('deriveCollectionValidators', () => { }); }); + it('reads each value object from the storage namespace its field names', () => { + const closedObject = (field: string) => ({ + bsonType: 'object', + required: [field], + properties: { [field]: { bsonType: 'string' } }, + additionalProperties: false, + }); + const validators = derive({ + roots: { users: crossRef('User') }, + models: { + User: model({ + _id: objectId(), + home: voField('Address', false, 'home'), + work: voField('Address', false, 'work'), + }), + }, + storage: { + namespaces: { + home: { + id: 'home', + entries: { + valueObject: { Address: { fields: { city: scalarField('mongo/string@1') } } }, + }, + }, + work: { + id: 'work', + entries: { + valueObject: { Address: { fields: { desk: scalarField('mongo/string@1') } } }, + }, + }, + }, + }, + }); + + expect(jsonSchemas(validators)['users']?.['properties']).toEqual({ + _id: { bsonType: 'objectId' }, + home: closedObject('city'), + work: closedObject('desk'), + }); + }); + describe('embedded models', () => { const Address = model({ city: scalarField('mongo/string@1') }); diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-json-schema-helpers.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-json-schema-helpers.ts index 4c78e3f0635e..a42e30b41114 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-json-schema-helpers.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/derive-json-schema-helpers.ts @@ -1,6 +1,9 @@ -import type { ContractField } from '@internal/contract/types'; +import type { ContractField, ContractValueObjectField } from '@internal/contract/types'; -export function scalarField(codecId: string, nullable = false): ContractField { +/** A field with no value set: valid both on a model and as a value object member. */ +type FieldWithoutValueSet = Omit; + +export function scalarField(codecId: string, nullable = false): FieldWithoutValueSet { return { type: { kind: 'scalar', codecId }, nullable, many: false }; } @@ -22,7 +25,7 @@ export function arrayField( codecId: string, nullable = false, elementNullable = false, -): ContractField { +): FieldWithoutValueSet { return { type: { kind: 'scalar', codecId }, nullable, many: { elementNullable } }; } @@ -45,14 +48,43 @@ export function arrayEnumField( }; } -export function voField(name: string, nullable = false): ContractField { - return { type: { kind: 'valueObject', name }, nullable, many: false }; +/** A value object member typed by an enum: it refers to the enum's storage value set, as a storage entity's field does. */ +export function memberEnumField(codecId: string, enumName: string): ContractValueObjectField { + return { + type: { kind: 'scalar', codecId }, + nullable: false, + many: false, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: '__unbound__', + entityName: enumName, + }, + }; +} + +function valueObjectType(entityName: string, namespaceId = '__unbound__'): ContractField['type'] { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName, + }; +} + +export function voField( + name: string, + nullable = false, + namespaceId = '__unbound__', +): FieldWithoutValueSet { + return { type: valueObjectType(name, namespaceId), nullable, many: false }; } export function voArrayField( name: string, nullable = false, elementNullable = false, -): ContractField { - return { type: { kind: 'valueObject', name }, nullable, many: { elementNullable } }; +): FieldWithoutValueSet { + return { type: valueObjectType(name), nullable, many: { elementNullable } }; } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/field-schema.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/field-schema.test.ts index 73dd4755b44d..7980f6a35434 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/field-schema.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/field-schema.test.ts @@ -2,32 +2,41 @@ import { type } from 'arktype'; import { describe, expect, it } from 'vitest'; import { MongoContractSchema } from '../src/contract-schema'; -function contractWithField(kind: 'models' | 'valueObjects', field: Record) { - const entry = - kind === 'models' - ? { fields: { name: field }, storage: { fields: { name: { field: 'name' } } } } - : { fields: { name: field } }; - return { - targetFamily: 'mongo', - roots: {}, - domain: { - namespaces: { - app: { models: {}, [kind]: { Item: entry } }, - }, - }, - storage: { namespaces: {} }, - }; +function contractWithField(kind: 'model' | 'storage value object', field: Record) { + return kind === 'model' + ? { + targetFamily: 'mongo', + roots: {}, + domain: { + namespaces: { + app: { + models: { + Item: { fields: { name: field }, storage: { fields: { name: { field: 'name' } } } }, + }, + }, + }, + }, + storage: { namespaces: {} }, + } + : { + targetFamily: 'mongo', + roots: {}, + domain: { namespaces: { app: { models: {} } } }, + storage: { + namespaces: { + app: { id: 'app', entries: { valueObject: { Item: { fields: { name: field } } } } }, + }, + }, + }; } -describe.each(['models', 'valueObjects'] as const)('%s field schema', (kind) => { +describe.each(['model', 'storage value object'] as const)('%s field schema', (kind) => { const fieldType = { kind: 'scalar', codecId: 'mongo/string@1' }; - it('normalizes an absent many property to false', () => { - const field = { type: fieldType }; - expect(Object.hasOwn(field, 'many')).toBe(false); - expect(MongoContractSchema(contractWithField(kind, field))).toEqual( - contractWithField(kind, { type: fieldType, nullable: false, many: false }), - ); + it('accepts a field with no many property', () => { + expect( + MongoContractSchema(contractWithField(kind, { type: fieldType, nullable: false })), + ).not.toBeInstanceOf(type.errors); }); it.each([false, { elementNullable: false }, { elementNullable: true }])( @@ -46,8 +55,8 @@ describe.each(['models', 'valueObjects'] as const)('%s field schema', (kind) => { elementNullable: 'false' }, { elementNullable: 0 }, ])('rejects malformed many %j', (many) => { - expect(MongoContractSchema(contractWithField(kind, { type: fieldType, many }))).toBeInstanceOf( - type.errors, - ); + expect( + MongoContractSchema(contractWithField(kind, { type: fieldType, nullable: false, many })), + ).toBeInstanceOf(type.errors); }); }); diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts index 7657e10fc0b4..10e3d96664d2 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.d.ts @@ -1,7 +1,7 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma contract emit'. // To regenerate, run: prisma contract emit -import type { CodecTypes as MongoCodecTypes } from '@internal/target-mongo/codec-types'; +import type { CodecTypes as MongoCodecTypes } from '../../../../../3-targets/mongo/1-target/src/exports/codec-types'; import type { MongoCollection, @@ -12,143 +12,79 @@ import type { import type { Contract as ContractType, ExecutionHashBase, + FieldTypesFromDomain, NamespaceId, ProfileHashBase, StorageHashBase, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; -export type StorageHash = StorageHashBase<'orm-storage'>; +export type StorageHash = StorageHashBase<"orm-storage">; export type ExecutionHash = ExecutionHashBase; -export type ProfileHash = ProfileHashBase<'orm-profile'>; +export type ProfileHash = ProfileHashBase<"orm-profile">; export type CodecTypes = MongoCodecTypes; -export type HomeAddressOutput = { - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; -}; -export type HomeAddressInput = { - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; -}; -export type FieldOutputTypes = { - readonly __unbound__: { - readonly Address: { - readonly street: CodecTypes['mongo/string@1']['output']; - readonly city: CodecTypes['mongo/string@1']['output']; - readonly zip: CodecTypes['mongo/string@1']['output']; - }; - readonly Bug: { readonly severity: CodecTypes['mongo/string@1']['output'] }; - readonly Comment: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly text: CodecTypes['mongo/string@1']['output']; - readonly createdAt: CodecTypes['mongo/date@1']['output']; - }; - readonly Feature: { - readonly priority: CodecTypes['mongo/string@1']['output']; - readonly targetRelease: CodecTypes['mongo/string@1']['output']; - }; - readonly Task: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly title: CodecTypes['mongo/string@1']['output']; - readonly type: CodecTypes['mongo/string@1']['output']; - readonly assigneeId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly email: CodecTypes['mongo/string@1']['output']; - readonly loginCount: CodecTypes['mongo/int32@1']['output']; - readonly tags: ReadonlyArray; - readonly homeAddress: HomeAddressOutput | null; - }; - }; -}; -export type FieldInputTypes = { - readonly __unbound__: { - readonly Address: { - readonly street: CodecTypes['mongo/string@1']['input']; - readonly city: CodecTypes['mongo/string@1']['input']; - readonly zip: CodecTypes['mongo/string@1']['input']; - }; - readonly Bug: { readonly severity: CodecTypes['mongo/string@1']['input'] }; - readonly Comment: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly text: CodecTypes['mongo/string@1']['input']; - readonly createdAt: CodecTypes['mongo/date@1']['input']; - }; - readonly Feature: { - readonly priority: CodecTypes['mongo/string@1']['input']; - readonly targetRelease: CodecTypes['mongo/string@1']['input']; - }; - readonly Task: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly title: CodecTypes['mongo/string@1']['input']; - readonly type: CodecTypes['mongo/string@1']['input']; - readonly assigneeId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly email: CodecTypes['mongo/string@1']['input']; - readonly loginCount: CodecTypes['mongo/int32@1']['input']; - readonly tags: ReadonlyArray; - readonly homeAddress: HomeAddressInput | null; - }; - }; -}; +type ParameterizedOutputTypeFunctions = never; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage; +type ValueObjectInputTypes = ValueObjectTypesFromStorage; +export type HomeAddressOutput = ValueObjectOutputTypes["__unbound__"]["HomeAddress"]; +export type HomeAddressInput = ValueObjectInputTypes["__unbound__"]["HomeAddress"]; +type FieldTypeOverrides = { }; +export type FieldOutputTypes = FieldTypesFromDomain; +export type FieldInputTypes = FieldTypesFromDomain; export namespace Models { export type unbound_Task = { - _id: CodecTypes['mongo/objectId@1']['output']; - title: CodecTypes['mongo/string@1']['output']; - type: 'bug' | 'feature'; - assigneeId: CodecTypes['mongo/objectId@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Task"]["_id"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + type: "bug" | "feature"; + assigneeId: FieldOutputTypes["__unbound__"]["Task"]["assigneeId"]; comments: unbound_Comment[]; assignee: unbound_User; - readonly [RelationKeys]?: 'assignee'; + readonly [RelationKeys]?: "assignee"; }; export type unbound_Bug = { - _id: CodecTypes['mongo/objectId@1']['output']; - title: CodecTypes['mongo/string@1']['output']; - type: 'bug'; - assigneeId: CodecTypes['mongo/objectId@1']['output']; - severity: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Task"]["_id"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + type: "bug"; + assigneeId: FieldOutputTypes["__unbound__"]["Task"]["assigneeId"]; + severity: FieldOutputTypes["__unbound__"]["Bug"]["severity"]; comments: unbound_Comment[]; assignee: unbound_User; - readonly [RelationKeys]?: 'assignee'; + readonly [RelationKeys]?: "assignee"; }; export type unbound_Feature = { - _id: CodecTypes['mongo/objectId@1']['output']; - title: CodecTypes['mongo/string@1']['output']; - type: 'feature'; - assigneeId: CodecTypes['mongo/objectId@1']['output']; - priority: CodecTypes['mongo/string@1']['output']; - targetRelease: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Task"]["_id"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + type: "feature"; + assigneeId: FieldOutputTypes["__unbound__"]["Task"]["assigneeId"]; + priority: FieldOutputTypes["__unbound__"]["Feature"]["priority"]; + targetRelease: FieldOutputTypes["__unbound__"]["Feature"]["targetRelease"]; comments: unbound_Comment[]; assignee: unbound_User; - readonly [RelationKeys]?: 'assignee'; + readonly [RelationKeys]?: "assignee"; }; export type unbound_User = { - _id: CodecTypes['mongo/objectId@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - email: CodecTypes['mongo/string@1']['output']; - loginCount: CodecTypes['mongo/int32@1']['output']; - tags: ReadonlyArray; - homeAddress: HomeAddressOutput | null; + _id: FieldOutputTypes["__unbound__"]["User"]["_id"]; + name: FieldOutputTypes["__unbound__"]["User"]["name"]; + email: FieldOutputTypes["__unbound__"]["User"]["email"]; + loginCount: FieldOutputTypes["__unbound__"]["User"]["loginCount"]; + tags: FieldOutputTypes["__unbound__"]["User"]["tags"]; + homeAddress: FieldOutputTypes["__unbound__"]["User"]["homeAddress"]; addresses: unbound_Address[]; tasks: unbound_AnyTask[]; - readonly [RelationKeys]?: 'tasks'; + readonly [RelationKeys]?: "tasks"; }; export type unbound_Address = { - street: CodecTypes['mongo/string@1']['output']; - city: CodecTypes['mongo/string@1']['output']; - zip: CodecTypes['mongo/string@1']['output']; + street: FieldOutputTypes["__unbound__"]["Address"]["street"]; + city: FieldOutputTypes["__unbound__"]["Address"]["city"]; + zip: FieldOutputTypes["__unbound__"]["Address"]["zip"]; }; export type unbound_Comment = { - _id: CodecTypes['mongo/objectId@1']['output']; - text: CodecTypes['mongo/string@1']['output']; - createdAt: CodecTypes['mongo/date@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Comment"]["_id"]; + text: FieldOutputTypes["__unbound__"]["Comment"]["text"]; + createdAt: FieldOutputTypes["__unbound__"]["Comment"]["createdAt"]; }; export type unbound_AnyTask = unbound_Bug | unbound_Feature; } @@ -168,238 +104,22 @@ export declare const models: { export type TypeMaps = MongoTypeMaps; type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly entries: { - readonly collection: { readonly tasks: MongoCollection; readonly users: MongoCollection }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, + ContractType<{ readonly namespaces: { readonly __unbound__: { readonly id: "__unbound__"; readonly kind: "mongo-namespace"; readonly entries: { readonly collection: { readonly tasks: MongoCollection; readonly users: MongoCollection }; readonly valueObject: { readonly HomeAddress: { readonly fields: { readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly country: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } } } } } }; readonly storageHash: StorageHash }>, 'roots' | 'domain' > & { - readonly target: 'mongo'; - readonly targetFamily: 'mongo'; - readonly roots: { - readonly tasks: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Task' }; - readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; - }; + readonly target: "mongo"; + readonly targetFamily: "mongo"; + readonly roots: { readonly tasks: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" }; readonly users: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" } }; readonly domain: { readonly namespaces: { readonly __unbound__: { - readonly models: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly fields: { readonly street: { readonly field: 'street' }; readonly city: { readonly field: 'city' }; readonly zip: { readonly field: 'zip' } } }; - readonly owner: 'User'; - }; - readonly Bug: { - readonly fields: { - readonly severity: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'tasks'; readonly fields: { readonly severity: { readonly field: 'severity' } } }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Task'; - }; - }; - readonly Comment: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly fields: { readonly _id: { readonly field: '_id' }; readonly text: { readonly field: 'text' }; readonly createdAt: { readonly field: 'createdAt' } } }; - readonly owner: 'Task'; - }; - readonly Feature: { - readonly fields: { - readonly priority: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly targetRelease: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'tasks'; readonly fields: { readonly priority: { readonly field: 'priority' }; readonly targetRelease: { readonly field: 'targetRelease' } } }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Task'; - }; - }; - readonly Task: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly title: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly assigneeId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly assignee: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['assigneeId']; - readonly targetFields: readonly ['_id']; - }; - }; - readonly comments: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Comment'; - }; - readonly cardinality: '1:N'; - }; - }; - readonly storage: { readonly collection: 'tasks'; readonly fields: { readonly _id: { readonly field: '_id' }; readonly title: { readonly field: 'title' }; readonly type: { readonly field: 'type' }; readonly assigneeId: { readonly field: 'assigneeId' } }; readonly relations: { readonly comments: { readonly field: 'comments' } } }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly bug: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Bug'; - }; - readonly feature: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Feature'; - }; - }; - }; - readonly User: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly loginCount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly tags: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly homeAddress: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'HomeAddress' }; - }; - }; - readonly relations: { - readonly addresses: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Address'; - }; - readonly cardinality: '1:N'; - }; - readonly tasks: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Task'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['assigneeId']; - }; - }; - }; - readonly storage: { readonly collection: 'users'; readonly fields: { readonly _id: { readonly field: '_id' }; readonly name: { readonly field: 'name' }; readonly email: { readonly field: 'email' }; readonly loginCount: { readonly field: 'loginCount' }; readonly tags: { readonly field: 'tags' }; readonly homeAddress: { readonly field: 'homeAddress' } }; readonly relations: { readonly addresses: { readonly field: 'addresses' } } }; - }; - }; - readonly valueObjects: { - readonly HomeAddress: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; - }; - }; - }; - readonly capabilities: {}; - readonly extensions: {}; - readonly meta: {}; - readonly valueObjects: { - readonly HomeAddress: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; + readonly models: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly zip: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly fields: { readonly street: { readonly field: "street" }; readonly city: { readonly field: "city" }; readonly zip: { readonly field: "zip" } } }; readonly owner: "User" }; readonly Bug: { readonly fields: { readonly severity: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly severity: { readonly field: "severity" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" } }; readonly Comment: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly text: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly createdAt: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } } }; readonly relations: Record; readonly storage: { readonly fields: { readonly _id: { readonly field: "_id" }; readonly text: { readonly field: "text" }; readonly createdAt: { readonly field: "createdAt" } } }; readonly owner: "Task" }; readonly Feature: { readonly fields: { readonly priority: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly targetRelease: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly priority: { readonly field: "priority" }; readonly targetRelease: { readonly field: "targetRelease" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" } }; readonly Task: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly type: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly assigneeId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } } }; readonly relations: { readonly assignee: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["assigneeId"]; readonly targetFields: readonly ["_id"] } }; readonly comments: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Comment" }; readonly cardinality: "1:N" } }; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly title: { readonly field: "title" }; readonly type: { readonly field: "type" }; readonly assigneeId: { readonly field: "assigneeId" } }; readonly relations: { readonly comments: { readonly field: "comments" } } }; readonly discriminator: { readonly field: "type" }; readonly variants: { readonly bug: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Bug" }; readonly feature: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Feature" } } }; readonly User: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly email: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly loginCount: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/int32@1" } }; readonly tags: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" }; readonly many: { readonly elementNullable: false } }; readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "HomeAddress" } } }; readonly relations: { readonly addresses: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Address" }; readonly cardinality: "1:N" }; readonly tasks: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["_id"]; readonly targetFields: readonly ["assigneeId"] } } }; readonly storage: { readonly collection: "users"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly name: { readonly field: "name" }; readonly email: { readonly field: "email" }; readonly loginCount: { readonly field: "loginCount" }; readonly tags: { readonly field: "tags" }; readonly homeAddress: { readonly field: "homeAddress" } }; readonly relations: { readonly addresses: { readonly field: "addresses" } } } } }; }; }; }; + readonly capabilities: { }; + readonly extensions: { }; + readonly meta: { }; readonly profileHash: ProfileHash; }; diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json index 25d3202d0593..a95f3db83085 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json @@ -19,6 +19,28 @@ "collection": { "tasks": {}, "users": {} + }, + "valueObject": { + "HomeAddress": { + "fields": { + "city": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + }, + "country": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + } + } + } } } } @@ -257,7 +279,10 @@ "homeAddress": { "type": { "kind": "valueObject", - "name": "HomeAddress" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "HomeAddress" }, "nullable": true, "many": false @@ -370,28 +395,6 @@ "relations": {}, "owner": "Task" } - }, - "valueObjects": { - "HomeAddress": { - "fields": { - "city": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - }, - "country": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - } - } - } } } } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/recursive-groups.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/recursive-groups.test.ts index 8a6d15a4b22c..e85a975748a1 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/recursive-groups.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/recursive-groups.test.ts @@ -1,14 +1,26 @@ import { describe, expect, it } from 'vitest'; -import { recursiveGroupsOf, typeKey } from '../src/recursive-groups'; -import { embedMany, embedOne, model, objectId } from './collection-validator-helpers'; +import { modelKey, recursiveGroupsOf, valueObjectKey } from '../src/recursive-groups'; +import { + embedMany, + embedOne, + model, + objectId, + storageWithValueObjects, +} from './collection-validator-helpers'; import { scalarField, voArrayField, voField } from './derive-json-schema-helpers'; -const vo = (name: string) => typeKey('valueObject', name); +const vo = (entityName: string) => + valueObjectKey({ + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName, + }); describe('recursiveGroupsOf', () => { it('puts a value object that refers to itself in its own group', () => { const groups = recursiveGroupsOf( - { NavItem: { fields: { children: voArrayField('NavItem') } } }, + storageWithValueObjects({ NavItem: { fields: { children: voArrayField('NavItem') } } }), {}, ); expect(groups.has(vo('NavItem'))).toBe(true); @@ -16,12 +28,12 @@ describe('recursiveGroupsOf', () => { it('puts value objects that reach each other in one group, and leaves the others out', () => { const groups = recursiveGroupsOf( - { + storageWithValueObjects({ A: { fields: { b: voField('B'), icon: voField('Icon') } }, B: { fields: { a: voField('A') } }, C: { fields: { a: voField('A') } }, Icon: { fields: { name: scalarField('mongo/string@1') } }, - }, + }), {}, ); expect(groups.get(vo('A'))).toBeDefined(); @@ -30,15 +42,12 @@ describe('recursiveGroupsOf', () => { }); it('follows embed relations between models', () => { - const groups = recursiveGroupsOf( - {}, - { - Root: model({ _id: objectId() }, { relations: { a: embedOne('A') } }), - A: model({}, { relations: { b: embedMany('B') } }), - B: model({}, { relations: { a: embedOne('A') } }), - }, - ); - expect(groups.get(typeKey('model', 'A'))).toBe(groups.get(typeKey('model', 'B'))); - expect(groups.has(typeKey('model', 'Root'))).toBe(false); + const groups = recursiveGroupsOf(storageWithValueObjects({}), { + Root: model({ _id: objectId() }, { relations: { a: embedOne('A') } }), + A: model({}, { relations: { b: embedMany('B') } }), + B: model({}, { relations: { a: embedOne('A') } }), + }); + expect(groups.get(modelKey('A'))).toBe(groups.get(modelKey('B'))); + expect(groups.has(modelKey('Root'))).toBe(false); }); }); diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-domain.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-domain.test.ts index 1375971797fc..2a8094edc54a 100644 --- a/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-domain.test.ts +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/validate-domain.test.ts @@ -51,6 +51,7 @@ function makeValidContract(overrides: Record = {}): DomainContr domainOverride !== undefined ? (domainOverride as DomainContractShape['domain']) : applicationDomainOf({ models }), + storage: { namespaces: {} }, ...rest, } as DomainContractShape; } diff --git a/packages/2-mongo-family/1-foundation/mongo-contract/test/value-object-schema.test.ts b/packages/2-mongo-family/1-foundation/mongo-contract/test/value-object-schema.test.ts new file mode 100644 index 000000000000..9699b30bddf2 --- /dev/null +++ b/packages/2-mongo-family/1-foundation/mongo-contract/test/value-object-schema.test.ts @@ -0,0 +1,193 @@ +import { + type DomainContractShape, + validateContractDomain, +} from '@internal/contract/validate-domain'; +import { hydrateNamespaceEntities } from '@internal/framework-components/ir'; +import { blindCast } from '@internal/utils/casts'; +import { type } from 'arktype'; +import { describe, expect, it } from 'vitest'; +import { MongoContractSchema } from '../src/contract-schema'; +import { composeMongoEntityKinds } from '../src/entity-kinds'; +import { MongoValueObject } from '../src/ir/mongo-value-object'; + +const NAMESPACE = '__unbound__'; +const text = { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + nullable: false, + many: false, +} as const; + +function valueObjectType(name: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: NAMESPACE, + entityKind: 'valueObject', + entityName: name, + }; +} + +const address = { fields: { street: text, city: text } }; + +function deepFreeze(value: T): T { + if (typeof value === 'object' && value !== null) { + for (const child of Object.values(value)) deepFreeze(child); + Object.freeze(value); + } + return value; +} + +function contractWith( + addressField: Record, + valueObject: Record = { Address: address }, +) { + return { + targetFamily: 'mongo', + roots: { users: { namespace: NAMESPACE, model: 'User' } }, + domain: { + namespaces: { + [NAMESPACE]: { + models: { + User: { + fields: { + _id: { ...text, type: { kind: 'scalar', codecId: 'mongo/objectId@1' } }, + address: addressField, + }, + relations: {}, + storage: { collection: 'users', fields: { address: { field: 'address' } } }, + }, + }, + }, + }, + }, + storage: { + namespaces: { + [NAMESPACE]: { id: NAMESPACE, entries: { collection: { users: {} }, valueObject } }, + }, + }, + }; +} + +describe('MongoValueObject', () => { + it('holds its fields, frozen, and serializes to them', () => { + const valueObject = new MongoValueObject(address); + expect({ + json: JSON.parse(JSON.stringify(valueObject)), + frozen: Object.isFrozen(valueObject), + }).toEqual({ json: address, frozen: true }); + }); + + it('is hydrated from the valueObject slot', () => { + const hydrated = hydrateNamespaceEntities( + { valueObject: { Address: address } }, + composeMongoEntityKinds(), + 'fail', + ); + expect(hydrated['valueObject']?.['Address']).toBeInstanceOf(MongoValueObject); + }); +}); + +describe('the Mongo contract schema with value objects in storage', () => { + it('accepts a field that refers to a storage value object, and nested and self references', () => { + const contract = contractWith( + { type: valueObjectType('Profile'), nullable: false, many: false }, + { + Address: address, + Profile: { + fields: { + home: { type: valueObjectType('Address'), nullable: true, many: false }, + parent: { type: valueObjectType('Profile'), nullable: true, many: false }, + }, + }, + }, + ); + expect(MongoContractSchema(contract)).toEqual(contract); + }); + + it('leaves frozen storage unchanged, and the value object fills in an absent many', () => { + const contract = contractWith( + { type: valueObjectType('Address'), nullable: false, many: false }, + { Address: { fields: { street: { type: text.type, nullable: false } } } }, + ); + deepFreeze(contract.storage); + expect({ + parsed: MongoContractSchema(contract), + valueObject: new MongoValueObject({ + fields: { street: { type: text.type, nullable: false } }, + }).fields, + }).toEqual({ parsed: contract, valueObject: { street: text } }); + }); + + it.each([ + ['without nullable', { type: text.type }], + ['with dict false', { ...text, dict: false }], + ])('refuses a value object member %s', (_case, street) => { + expect( + MongoContractSchema( + contractWith( + { type: valueObjectType('Address'), nullable: false }, + { Address: { fields: { street } } }, + ), + ), + ).toBeInstanceOf(type.errors); + }); + + it('refuses a field type that names its value object the old way', () => { + expect( + MongoContractSchema( + contractWith({ type: { kind: 'valueObject', name: 'Address' }, nullable: false }), + ), + ).toBeInstanceOf(type.errors); + }); + + it('refuses a member value set that points into the domain plane', () => { + const contract = contractWith( + { type: valueObjectType('Address'), nullable: false }, + { + Address: { + fields: { + kind: { + ...text, + valueSet: { + plane: 'domain', + namespaceId: NAMESPACE, + entityKind: 'enum', + entityName: 'Kind', + }, + }, + }, + }, + }, + ); + expect(MongoContractSchema(contract)).toBeInstanceOf(type.errors); + }); + + it('refuses value objects in the domain plane', () => { + const contract = contractWith({ type: valueObjectType('Address'), nullable: false }); + const old = { + ...contract, + domain: { + namespaces: { + [NAMESPACE]: { + ...contract.domain.namespaces[NAMESPACE], + valueObjects: { Address: address }, + }, + }, + }, + }; + expect(MongoContractSchema(old)).toBeInstanceOf(type.errors); + }); + + it('lets domain validation refuse a reference to a value object storage does not have', () => { + const parsed = MongoContractSchema( + contractWith({ type: valueObjectType('Missing'), nullable: false }), + ); + expect(() => + validateContractDomain( + blindCast(parsed), + ), + ).toThrow( + 'Model "__unbound__:User" field "address" refers to value object "Missing" in storage namespace "__unbound__", which storage.namespaces.__unbound__.entries.valueObject does not have', + ); + }); +}); diff --git a/packages/2-mongo-family/2-authoring/contract-prisma6/src/interpreter.ts b/packages/2-mongo-family/2-authoring/contract-prisma6/src/interpreter.ts index a69cecdddbf8..22723c5c41fc 100644 --- a/packages/2-mongo-family/2-authoring/contract-prisma6/src/interpreter.ts +++ b/packages/2-mongo-family/2-authoring/contract-prisma6/src/interpreter.ts @@ -803,7 +803,13 @@ function resolveFieldType( if (ctx.compositeTypeNames.has(field.typeName)) { if (nativeType !== undefined) return nativeTypeUnsupported(nativeType); return { - type: { kind: 'valueObject', name: field.typeName }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: ctx.namespaceId, + entityKind: 'valueObject', + entityName: field.typeName, + }, nullable: field.optional, ...many, }; @@ -871,7 +877,7 @@ function readCompositeFields( ctx.diagnostics.push( prisma6Diagnostic( 'PSL.PRISMA6_MONGO_COMPOSITE_MAP_UNSUPPORTED', - `${label}: @map on a field of a composite type is not supported yet. The stored field keeps the name "${stringArgument(attribute) ?? field.name}", so removing @map renames it in storage; keep the schema as it is until Prisma 8 supports it.`, + `${label}: @map on a field of a composite type is not supported: Prisma 8 stores each field of a value object under its own name. Rename the field to "${stringArgument(attribute) ?? field.name}", the name it is stored under, and remove @map.`, sourceId, attribute.span, ), diff --git a/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-map-unsupported/expected-diagnostics.json b/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-map-unsupported/expected-diagnostics.json index f34dc6db9dbc..65eaed41f11f 100644 --- a/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-map-unsupported/expected-diagnostics.json +++ b/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-map-unsupported/expected-diagnostics.json @@ -3,6 +3,6 @@ "code": "PSL.PRISMA6_MONGO_COMPOSITE_MAP_UNSUPPORTED", "file": "schema.prisma", "line": 12, - "message": "Field \"Address.street\": @map on a field of a composite type is not supported yet. The stored field keeps the name \"street_name\", so removing @map renames it in storage; keep the schema as it is until Prisma 8 supports it." + "message": "Field \"Address.street\": @map on a field of a composite type is not supported: Prisma 8 stores each field of a value object under its own name. Rename the field to \"street_name\", the name it is stored under, and remove @map." } ] diff --git a/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-types/expected-contract.json b/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-types/expected-contract.json index 16b15259c85a..316ec6f230c3 100644 --- a/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-types/expected-contract.json +++ b/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/composite-types/expected-contract.json @@ -1,6 +1,6 @@ { - "targetFamily": "mongo", "target": "mongo", + "targetFamily": "mongo", "roots": { "Post": { "namespace": "__unbound__", @@ -24,7 +24,10 @@ "address": { "type": { "kind": "valueObject", - "name": "Address" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Address" }, "nullable": false, "many": false @@ -32,7 +35,10 @@ "history": { "type": { "kind": "valueObject", - "name": "Address" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Address" }, "nullable": false, "many": { @@ -42,7 +48,10 @@ "backup": { "type": { "kind": "valueObject", - "name": "Address" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Address" }, "nullable": true, "many": false @@ -67,74 +76,16 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "street": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - }, - "zip": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": true, - "many": false - }, - "ownerId": { - "type": { - "kind": "scalar", - "codecId": "mongo/objectId@1" - }, - "nullable": false, - "many": false - }, - "geo": { - "type": { - "kind": "valueObject", - "name": "Geo" - }, - "nullable": false, - "many": false - } - } - }, - "Geo": { - "fields": { - "lat": { - "type": { - "kind": "scalar", - "codecId": "mongo/double@1" - }, - "nullable": false, - "many": false - }, - "lng": { - "type": { - "kind": "scalar", - "codecId": "mongo/double@1" - }, - "nullable": false, - "many": false - } - } - } } } } }, - "extensions": {}, "capabilities": {}, + "extensions": {}, "profileHash": "251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3", "meta": {}, "storage": { - "storageHash": "dd22a5221b155cd435b52d49dd4a066c207d083079a291c2340674f70d8e067a", + "storageHash": "ae4ed29eb19b7d02a12b90b87853eb23103557cd076483c0110cd1b4d95e11da", "namespaces": { "__unbound__": { "id": "__unbound__", @@ -144,6 +95,67 @@ "Post": { "kind": "mongo-collection" } + }, + "valueObject": { + "Address": { + "fields": { + "street": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + }, + "zip": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": true, + "many": false + }, + "ownerId": { + "type": { + "kind": "scalar", + "codecId": "mongo/objectId@1" + }, + "nullable": false, + "many": false + }, + "geo": { + "type": { + "kind": "valueObject", + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Geo" + }, + "nullable": false, + "many": false + } + } + }, + "Geo": { + "fields": { + "lat": { + "type": { + "kind": "scalar", + "codecId": "mongo/double@1" + }, + "nullable": false, + "many": false + }, + "lng": { + "type": { + "kind": "scalar", + "codecId": "mongo/double@1" + }, + "nullable": false, + "many": false + } + } + } } } } diff --git a/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/lists/expected-contract.json b/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/lists/expected-contract.json index 4c4abf92894c..0b9036cd1029 100644 --- a/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/lists/expected-contract.json +++ b/packages/2-mongo-family/2-authoring/contract-prisma6/test/fixtures/lists/expected-contract.json @@ -1,6 +1,6 @@ { - "targetFamily": "mongo", "target": "mongo", + "targetFamily": "mongo", "roots": { "Post": { "namespace": "__unbound__", @@ -57,20 +57,23 @@ "codecId": "mongo/string@1" }, "nullable": false, + "many": { + "elementNullable": false + }, "valueSet": { "plane": "domain", "entityKind": "enum", "namespaceId": "__unbound__", "entityName": "Role" - }, - "many": { - "elementNullable": false } }, "address": { "type": { "kind": "valueObject", - "name": "Address" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Address" }, "nullable": false, "many": { @@ -104,20 +107,6 @@ } } }, - "valueObjects": { - "Address": { - "fields": { - "street": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - } - } - } - }, "enum": { "Role": { "codecId": "mongo/string@1", @@ -136,12 +125,12 @@ } } }, - "extensions": {}, "capabilities": {}, + "extensions": {}, "profileHash": "251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3", "meta": {}, "storage": { - "storageHash": "3da02d07cd29acc61bc54a1929d31fe70720137e369c44564b6ca3e8f3e46546", + "storageHash": "e6d057cae9c68e89ed7207907298557efb1fdf16c57f31373bd9cffaec8b65be", "namespaces": { "__unbound__": { "id": "__unbound__", @@ -157,6 +146,20 @@ "kind": "valueSet", "values": ["USER", "ADMIN"] } + }, + "valueObject": { + "Address": { + "fields": { + "street": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + } + } + } } } } diff --git a/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts b/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts index 66bd7efdcffc..53bf6c4313d3 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts @@ -789,7 +789,13 @@ function resolveNonRelationField( if (resolution?.kind === 'compositeType') { return { field: { - type: { kind: 'valueObject', name: resolution.symbol.name }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: resolution.namespace.id, + entityKind: 'valueObject', + entityName: resolution.symbol.name, + }, nullable: field.optional, many: field.list ? { elementNullable: field.elementOptional } : false, }, diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.aliases.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.aliases.test.ts index 304e27cf70ff..61a07035e31d 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.aliases.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.aliases.test.ts @@ -167,7 +167,11 @@ model Item { id ObjectId @id @map("_id") ref Ref }`); - expect(contract.domain.namespaces[UNBOUND_NAMESPACE_ID]?.valueObjects?.['Ref']).toEqual({ + expect( + JSON.parse( + JSON.stringify(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.entries['valueObject']), + )['Ref'], + ).toEqual({ fields: { target: { type: { kind: 'scalar', codecId: 'mongo/objectId@1' }, diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.single-voice.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.single-voice.test.ts index 084e7e73b961..d2a1992ea9b1 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.single-voice.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.single-voice.test.ts @@ -115,9 +115,25 @@ model Item { if (!result.ok) throw new Error(JSON.stringify(result.failure)); expect(result.value.domain.namespaces[UNBOUND_NAMESPACE_ID]?.models['Item']?.fields).toEqual({ id: { type: { kind: 'scalar', codecId: 'mongo/objectId@1' }, nullable: false, many: false }, - label: { type: { kind: 'valueObject', name: 'String' }, nullable: false, many: false }, + label: { + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'String', + }, + nullable: false, + many: false, + }, }); - expect(result.value.domain.namespaces[UNBOUND_NAMESPACE_ID]?.valueObjects).toEqual({ + expect( + JSON.parse( + JSON.stringify( + result.value.storage.namespaces[UNBOUND_NAMESPACE_ID]?.entries['valueObject'], + ), + ), + ).toEqual({ String: { fields: { length: { diff --git a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts index 7fe7d0bcb478..505802f492f1 100644 --- a/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts @@ -92,7 +92,8 @@ function modelsOf(ir: Contract): Record { } function valueObjectsOf(ir: Contract): Record | undefined { - return ir.domain.namespaces[UNBOUND_NAMESPACE_ID]!.valueObjects; + const slot = ir.storage.namespaces[UNBOUND_NAMESPACE_ID]?.entries['valueObject']; + return slot === undefined ? undefined : JSON.parse(JSON.stringify(slot)); } function model(ir: Contract, name: string): MongoModel { @@ -1336,7 +1337,13 @@ model Item { expect(modelsOf(ir)['User']).toMatchObject({ fields: { homeAddress: { - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, nullable: true, many: false, }, @@ -1360,7 +1367,13 @@ model Item { expect(modelsOf(ir)['User']).toMatchObject({ fields: { addresses: { - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, nullable: false, many: { elementNullable: false }, }, @@ -1420,7 +1433,13 @@ model Item { many: false, }, location: { - type: { kind: 'valueObject', name: 'GeoPoint' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'GeoPoint', + }, nullable: false, many: false, }, @@ -2629,12 +2648,24 @@ model Item { `); expect(model(ir, 'User').fields).toMatchObject({ nullableElements: { - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, nullable: false, many: { elementNullable: true }, }, fullyNullable: { - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, nullable: true, many: { elementNullable: true }, }, diff --git a/packages/2-mongo-family/2-authoring/contract-ts/src/build-contract.ts b/packages/2-mongo-family/2-authoring/contract-ts/src/build-contract.ts index ba7f87a12618..163a7eb57185 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/src/build-contract.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/src/build-contract.ts @@ -121,7 +121,11 @@ export function buildMongoContractFromDefinition( ? deriveCollectionValidators({ models: builtModels, lookups, - valueObjects: builtValueObjects, + storage: { + namespaces: { + [namespaceId]: { id: namespaceId, entries: { valueObject: builtValueObjects } }, + }, + }, valueSets: storageValueSets, controlPolicies, }) @@ -135,6 +139,7 @@ export function buildMongoContractFromDefinition( ]), ), valueSets: storageValueSets, + valueObjects: builtValueObjects, }); // Contracts carry no capabilities when built; emit adds the stack's. The profile hash fingerprints this empty set. const capabilities: Record> = {}; @@ -154,9 +159,6 @@ export function buildMongoContractFromDefinition( namespaces: { [namespaceId]: { models: builtModels, - ...(Object.keys(builtValueObjects).length > 0 - ? { valueObjects: builtValueObjects } - : {}), ...(Object.keys(builtEnums).length > 0 ? { enum: builtEnums } : {}), }, }, diff --git a/packages/2-mongo-family/2-authoring/contract-ts/src/build-models.ts b/packages/2-mongo-family/2-authoring/contract-ts/src/build-models.ts index 1e97dac01860..f92b6e5529c0 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/src/build-models.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/src/build-models.ts @@ -4,9 +4,9 @@ import { type ContractModel, type ContractReferenceRelation, type ContractValueObject, + type ContractValueObjectField, type CrossReference, crossRef, - type ValueSetRef, } from '@internal/contract/types'; import { resolveToOneRelationNullable } from '@internal/contract-authoring'; import { type MongoModelStorage, unsupportedStoredNameReason } from '@internal/mongo-contract'; @@ -38,26 +38,59 @@ export function storedFieldNameIn(fields: readonly FieldNode[], fieldName: strin return field === undefined ? fieldName : storedNameOf(field); } -function buildContractField(field: FieldNode, namespaceId: string): ContractField { - const valueSet: ValueSetRef | undefined = - field.enumName === undefined - ? undefined - : { plane: 'domain', entityKind: 'enum', namespaceId, entityName: field.enumName }; - - return { - type: field.type, - nullable: field.nullable, - many: field.many, - ...ifDefined('valueSet', valueSet), - }; +function fieldWithoutValueSet(field: FieldNode): Omit { + return { type: field.type, nullable: field.nullable, many: field.many }; } +/** A model's fields. A field an enum types refers to the domain enum. */ function buildFields( fields: readonly FieldNode[], namespaceId: string, ): Record { return Object.fromEntries( - fields.map((field) => [field.fieldName, buildContractField(field, namespaceId)]), + fields.map((field) => [ + field.fieldName, + { + ...fieldWithoutValueSet(field), + ...ifDefined( + 'valueSet', + field.enumName === undefined + ? undefined + : ({ + plane: 'domain', + entityKind: 'enum', + namespaceId, + entityName: field.enumName, + } as const), + ), + }, + ]), + ); +} + +/** A value object's members. A value object is a storage entity, and storage never refers to the domain plane, so a member an enum types refers to the enum's storage value set. */ +function buildValueObjectMembers( + fields: readonly FieldNode[], + namespaceId: string, +): Record { + return Object.fromEntries( + fields.map((field) => [ + field.fieldName, + { + ...fieldWithoutValueSet(field), + ...ifDefined( + 'valueSet', + field.enumName === undefined + ? undefined + : ({ + plane: 'storage', + entityKind: 'valueSet', + namespaceId, + entityName: field.enumName, + } as const), + ), + }, + ]), ); } @@ -165,7 +198,9 @@ export function buildValueObjects( }); } } - builtValueObjects[valueObject.name] = { fields: buildFields(valueObject.fields, namespaceId) }; + builtValueObjects[valueObject.name] = { + fields: buildValueObjectMembers(valueObject.fields, namespaceId), + }; } return builtValueObjects; diff --git a/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts b/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts index 154bbc7db145..625516f99374 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts @@ -40,7 +40,7 @@ import { type TargetPackRef, } from '@internal/framework-components/components'; import { extractCodecLookup } from '@internal/framework-components/control'; -import type { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; +import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import type { MongoCollection, MongoCollectionOptionsAuthoringInput, @@ -524,20 +524,30 @@ type DefinitionTargetId = Definition extends { ? TargetId : string; -type DefinitionStorage = Simplify< - MongoStorageShape & { - readonly collections: StorageCollectionsFromModels>; - readonly storageHash: StorageHashBase; - } ->; +type StorageValueObjectNamespaces> = { + readonly [K in typeof UNBOUND_NAMESPACE_ID]: { + readonly entries: { readonly valueObject: ContractValueObjectsFromRecord }; + }; +}; -type MaybeValueObjectsSection> = +type MaybeStorageValueObjectsSection> = keyof ValueObjects extends never ? EmptyObject : { - readonly valueObjects: ContractValueObjectsFromRecord; + readonly namespaces: { + readonly [K in typeof UNBOUND_NAMESPACE_ID]: MongoStorageShape['namespaces'][string] & + StorageValueObjectNamespaces[K]; + }; }; +type DefinitionStorage = Simplify< + MongoStorageShape & { + readonly collections: StorageCollectionsFromModels>; + readonly storageHash: StorageHashBase; + } +> & + MaybeStorageValueObjectsSection>; + type HandleName = Handle extends EnumTypeHandle ? Name : never; // A member is stored in its codec's JSON form, which is the authored literal only when that literal is JSON; a bigint or Date member is stored as text. @@ -592,8 +602,7 @@ type MaybeEnumsSection< type MongoDomainNamespaceFromDefinition = Simplify< { readonly models: ContractModelsFromRecord>; - } & MaybeValueObjectsSection> & - MaybeEnumsSection, CodecTypesFromDefinition> + } & MaybeEnumsSection, CodecTypesFromDefinition> >; type MongoContractBaseFromDefinition = Simplify<{ @@ -734,7 +743,8 @@ type FieldTypesFromDefinition = Fie CodecTypesFromDefinition, ParameterizedOutputTypesFromDefinition, FieldTypeOverridesFromDefinition, - Side + Side, + StorageValueObjectNamespaces> >; export type MongoContractResult = MongoContractWithTypeMaps< @@ -1126,12 +1136,18 @@ export const field = refusingRemovedFieldPresets({ type: blindCast< { readonly kind: 'valueObject'; - readonly name: NormalizeValueObjectName; + readonly plane: 'storage'; + readonly namespaceId: typeof UNBOUND_NAMESPACE_ID; + readonly entityKind: 'valueObject'; + readonly entityName: NormalizeValueObjectName; }, 'resolveValueObjectName returns the normalized name the type computes' >({ kind: 'valueObject', - name: resolveValueObjectName(valueObjectName), + plane: 'storage', + namespaceId: UNBOUND_NAMESPACE_ID, + entityKind: 'valueObject', + entityName: resolveValueObjectName(valueObjectName), }), nullable: false, many: false, diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts index a11ca44d8131..2ee06148d59c 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.dsl.test.ts @@ -1,7 +1,7 @@ import { crossRef, domainModelsAtDefaultNamespace, - domainValueObjectsAtDefaultNamespace, + valueObjectsWithCoordinates, } from '@internal/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { describe, expect, it } from 'vitest'; @@ -368,7 +368,18 @@ describe('mongo contract builder', () => { }), }, }); - expect(domainValueObjectsAtDefaultNamespace(contract.domain)).toEqual({ + expect( + JSON.parse( + JSON.stringify( + Object.fromEntries( + valueObjectsWithCoordinates(contract.storage).map(({ coordinate, valueObject }) => [ + coordinate.entityName, + valueObject, + ]), + ), + ), + ), + ).toEqual({ Address: { fields: { street: { diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.storage-value-objects.test.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.storage-value-objects.test.ts new file mode 100644 index 000000000000..7c3e1a3c2aa4 --- /dev/null +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.storage-value-objects.test.ts @@ -0,0 +1,97 @@ +import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; +import { describe, expect, it } from 'vitest'; +import { defineContract, field, model, valueObject } from '../src/contract-builder'; +import { enumType, member } from '../src/enum-type'; +import { mongoFamilyPack, mongoTargetPack } from './mongo-test-packs'; + +const Role = enumType('Role', { codecId: 'mongo/string@1' }, member('Admin', 'admin')); + +function contractWith(street: 'street' | 'line1') { + const Geo = valueObject('Geo', { fields: { lat: field.double() } }); + const Address = valueObject('Address', { + fields: { + [street]: field.string(), + role: field.namedType(Role), + geo: field.valueObject(Geo).optional(), + }, + }); + const User = model('User', { + collection: 'users', + fields: { _id: field.objectId(), address: field.valueObject(Address) }, + }); + return defineContract({ + family: mongoFamilyPack, + target: mongoTargetPack, + enums: { Role }, + models: { User }, + valueObjects: { Address, Geo }, + }); +} + +function json(value: unknown) { + return JSON.parse(JSON.stringify(value)); +} + +function valueObjectRef(entityName: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: UNBOUND_NAMESPACE_ID, + entityKind: 'valueObject', + entityName, + }; +} + +describe('the Mongo builder writes value objects into storage', () => { + it('writes them under entries.valueObject, and fields refer to them by coordinate', () => { + const contract = contractWith('street'); + expect({ + valueObjects: json(contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.entries)['valueObject'], + field: json(contract.domain.namespaces[UNBOUND_NAMESPACE_ID]?.models['User']?.fields)[ + 'address' + ], + domainKeys: Object.keys(contract.domain.namespaces[UNBOUND_NAMESPACE_ID] ?? {}).sort(), + }).toEqual({ + valueObjects: { + Address: { + fields: { + street: { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + nullable: false, + many: false, + }, + role: { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + nullable: false, + many: false, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: UNBOUND_NAMESPACE_ID, + entityName: 'Role', + }, + }, + geo: { type: valueObjectRef('Geo'), nullable: true, many: false }, + }, + }, + Geo: { + fields: { + lat: { + type: { kind: 'scalar', codecId: 'mongo/double@1' }, + nullable: false, + many: false, + }, + }, + }, + }, + field: { type: valueObjectRef('Address'), nullable: false, many: false }, + domainKeys: ['enum', 'models'], + }); + }); + + it('changes the storage hash when a member changes', () => { + expect(String(contractWith('street').storage.storageHash)).not.toBe( + String(contractWith('line1').storage.storageHash), + ); + }); +}); diff --git a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.types.test-d.ts b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.types.test-d.ts index 0d9c6a676e4a..8077935e0d2e 100644 --- a/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.types.test-d.ts +++ b/packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.types.test-d.ts @@ -428,6 +428,17 @@ const VOContract = defineContract({ type VORow = InferModelRow; type VOInput = MongoUnboundFieldInputTypes['VOModel']; +test('value objects are typed in the storage namespace, not the domain', () => { + type Namespace = (typeof VOContract)['storage']['namespaces']['__unbound__']; + expectTypeOf().toEqualTypeOf<'Inner' | 'Outer'>(); + expectTypeOf< + Namespace['entries']['valueObject']['Outer']['fields']['inner']['type']['entityName'] + >().toEqualTypeOf<'Inner'>(); + expectTypeOf< + keyof (typeof VOContract)['domain']['namespaces']['__unbound__'] + >().toEqualTypeOf<'models'>(); +}); + test('VO output: plain value object resolves to the full nested shape', () => { expectTypeOf().toEqualTypeOf<{ readonly inner: { readonly x: string; readonly y: number }; diff --git a/packages/2-mongo-family/3-tooling/emitter/src/index.ts b/packages/2-mongo-family/3-tooling/emitter/src/index.ts index ae66d24f0d01..fdaf2298b2ef 100644 --- a/packages/2-mongo-family/3-tooling/emitter/src/index.ts +++ b/packages/2-mongo-family/3-tooling/emitter/src/index.ts @@ -1,5 +1,9 @@ import type { Contract, ContractModelBase, JsonValue } from '@internal/contract/types'; -import { serializeObjectKey, serializeValue } from '@internal/emitter/domain-type-generation'; +import { + generateValueObjectsDescriptorType, + serializeObjectKey, + serializeValue, +} from '@internal/emitter/domain-type-generation'; import type { ImportSpecifierResolver } from '@internal/framework-components/emission'; import type { Namespace } from '@internal/framework-components/ir'; import type { MongoCollection, MongoModelStorage, MongoStorage } from '@internal/mongo-contract'; @@ -47,9 +51,17 @@ function generateMongoNamespacesType(namespaces: MongoStorage['namespaces']): st } const parts: string[] = []; for (const [name, ns] of sorted) { - const collectionsType = generateMongoNamespaceCollectionsType(ns.entries.collection ?? {}); + const entriesParts = [ + `readonly collection: ${generateMongoNamespaceCollectionsType(ns.entries.collection ?? {})}`, + ]; + const valueObjects = ns.entries.valueObject; + if (valueObjects !== undefined && Object.keys(valueObjects).length > 0) { + entriesParts.push( + `readonly valueObject: ${generateValueObjectsDescriptorType(valueObjects)}`, + ); + } parts.push( - `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}; ${mongoNamespaceSerializedKind(ns)}; readonly entries: { readonly collection: ${collectionsType} } }`, + `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}; ${mongoNamespaceSerializedKind(ns)}; readonly entries: { ${entriesParts.join('; ')} } }`, ); } return `{ ${parts.join('; ')} }`; diff --git a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts index a26659d56d3a..6dbb17279c56 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/emitter-hook.generation.test.ts @@ -553,7 +553,7 @@ describe('mongoEmission.generateContractTypes', () => { expect(types).not.toMatch(/export type Address =/); }); - it('emits valueObjects descriptor on ContractBase', () => { + it('emits value objects in the storage namespace entries, and nowhere else', () => { const contract = createMongoContract({ valueObjects: { Address: { @@ -568,8 +568,10 @@ describe('mongoEmission.generateContractTypes', () => { }, }); const types = generateContractDts(contract, mongoEmission, [], testHashes); - expect(types).toContain('readonly valueObjects:'); - expect(types).toContain('readonly Address: { readonly fields:'); + expect(types).toContain( + 'readonly entries: { readonly collection: Record; readonly valueObject: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } } } }', + ); + expect(types).not.toContain('readonly valueObjects:'); }); it('emits model field with valueObject kind', () => { @@ -585,7 +587,13 @@ describe('mongoEmission.generateContractTypes', () => { homeAddress: { nullable: true, many: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, }, }, relations: {}, @@ -610,7 +618,7 @@ describe('mongoEmission.generateContractTypes', () => { }); const types = generateContractDts(contract, mongoEmission, [], testHashes); expect(types).toContain( - 'readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly name: "Address" } }', + 'readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Address" } }', ); }); @@ -626,7 +634,13 @@ describe('mongoEmission.generateContractTypes', () => { }, previousAddresses: { nullable: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, many: { elementNullable: false }, }, }, @@ -652,7 +666,7 @@ describe('mongoEmission.generateContractTypes', () => { }); const types = generateContractDts(contract, mongoEmission, [], testHashes); expect(types).toContain( - 'readonly previousAddresses: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly name: "Address" }; readonly many: { readonly elementNullable: false } }', + 'readonly previousAddresses: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Address" }; readonly many: { readonly elementNullable: false } }', ); }); @@ -668,7 +682,13 @@ describe('mongoEmission.generateContractTypes', () => { }, children: { nullable: false, - type: { kind: 'valueObject', name: 'NavItem' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'NavItem', + }, many: { elementNullable: false }, }, }, @@ -682,14 +702,58 @@ describe('mongoEmission.generateContractTypes', () => { 'export type NavItemOutput = ValueObjectOutputTypes["__unbound__"]["NavItem"];', ); expect(types).toContain( - 'readonly children: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly name: "NavItem" }; readonly many: { readonly elementNullable: false } }', + 'readonly children: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "NavItem" }; readonly many: { readonly elementNullable: false } }', ); }); - it('omits valueObjects when none exist', () => { + it('omits value objects when none exist', () => { const contract = createMongoContract(); const types = generateContractDts(contract, mongoEmission, [], testHashes); - expect(types).not.toContain('valueObjects'); + expect(types).not.toMatch(/valueObject/i); + }); + + it('types an enum member of a value object by the storage value set it names', () => { + const storage = namespacedMongoStorageFromCollections({}); + const contract = createMongoContract({ + storage: { + ...storage, + namespaces: { + __unbound__: { + id: '__unbound__', + entries: { + collection: {}, + valueSet: { Kind: { kind: 'valueSet', values: ['home', 'work'] } }, + }, + }, + }, + }, + valueObjects: { + Address: { + fields: { + kind: { + nullable: false, + many: false, + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: '__unbound__', + entityName: 'Kind', + }, + }, + }, + }, + }, + }); + const types = generateContractDts(contract, mongoEmission, [], testHashes, undefined, { + get: () => undefined, + renderOutputTypeFor: () => undefined, + renderValueLiteralFor: (_id, value) => + typeof value === 'string' ? JSON.stringify(value) : undefined, + }); + expect(types).toContain( + 'type FieldTypeOverrides = { readonly __unbound__: { readonly valueObjects: { readonly Address: { readonly kind: { readonly output: "home" | "work"; readonly input: "home" | "work" } } } } };', + ); }); it('emits nullable value object type alias field', () => { @@ -742,10 +806,10 @@ describe('mongoEmission.generateContractTypes', () => { }); const types = generateContractDts(contract, mongoEmission, [], testHashes); expect(types).toContain( - "export type FieldOutputTypes = FieldTypesFromDomain;", + "export type FieldOutputTypes = FieldTypesFromDomain;", ); expect(types).toContain( - "export type FieldInputTypes = FieldTypesFromDomain;", + "export type FieldInputTypes = FieldTypesFromDomain;", ); }); }); diff --git a/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts b/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts index b1f71e7f555f..46885ecb76d7 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/fixtures/create-mongo-contract.ts @@ -24,13 +24,32 @@ export function namespacedMongoStorageFromCollections( } as Contract['storage']; } +function withValueObjects( + storage: Contract['storage'], + valueObjects: Record, +): Contract['storage'] { + const namespace = storage.namespaces[UNBOUND_NAMESPACE_ID]; + return { + ...storage, + namespaces: { + ...storage.namespaces, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + ...namespace, + entries: { ...namespace?.entries, valueObject: valueObjects }, + }, + }, + }; +} + export function createMongoContract( overrides: Partial & { models?: Record; valueObjects?: Record; } = {}, ): Contract { - const { models, domain, valueObjects, ...rest } = overrides; + const { models, domain, valueObjects, storage, ...rest } = overrides; + const baseStorage = storage ?? namespacedMongoStorageFromCollections({}); return { targetFamily: 'mongo' as const, target: 'mongo', @@ -38,10 +57,9 @@ export function createMongoContract( domain ?? applicationDomainOf({ models: models ?? {}, - ...(valueObjects !== undefined ? { valueObjects } : {}), namespaceId: UNBOUND_DOMAIN_NAMESPACE_ID, }), - storage: namespacedMongoStorageFromCollections({}) as Contract['storage'], + storage: valueObjects === undefined ? baseStorage : withValueObjects(baseStorage, valueObjects), extensions: {}, capabilities: {}, meta: {}, diff --git a/packages/2-mongo-family/3-tooling/emitter/test/generate-orm-fixture.ts b/packages/2-mongo-family/3-tooling/emitter/test/generate-orm-fixture.ts index a4a3647e4d04..8e572b0f506a 100644 --- a/packages/2-mongo-family/3-tooling/emitter/test/generate-orm-fixture.ts +++ b/packages/2-mongo-family/3-tooling/emitter/test/generate-orm-fixture.ts @@ -1,22 +1,32 @@ import { writeFileSync } from 'node:fs'; -import { resolve } from 'node:path'; +import { relative, resolve } from 'node:path'; import { type Contract, coreHash, crossRef, profileHash } from '@internal/contract/types'; import { generateContractDts } from '@internal/emitter'; import { blindCast } from '@internal/utils/casts'; import ormContractJson from '../../../1-foundation/mongo-contract/test/fixtures/orm-contract.json'; import storedNamesContractJson from '../../../5-query-builders/orm/test/fixtures/stored-names-contract.json'; +import migrationContractJson from '../../../9-family/test/fixtures/migration-contract.json'; import { mongoEmission } from '../src/index'; -const codecImports = [ - { - package: '@internal/target-mongo/codec-types', - named: 'CodecTypes', - alias: 'MongoCodecTypes', - }, -]; +const targetCodecTypes = resolve( + import.meta.dirname, + '../../../../3-targets/mongo/1-target/src/exports/codec-types', +); + +function codecImportsFrom(fixtureDir: string, viaPackage: boolean) { + return [ + { + package: viaPackage + ? '@internal/target-mongo/codec-types' + : relative(fixtureDir, targetCodecTypes), + named: 'CodecTypes', + alias: 'MongoCodecTypes', + }, + ]; +} function fixtureContract( - json: typeof ormContractJson | typeof storedNamesContractJson, + json: typeof ormContractJson | typeof storedNamesContractJson | typeof migrationContractJson, hash: string, ) { return { @@ -40,46 +50,46 @@ function fixtureContract( } as Contract; } -const fixturesDir = resolve( +const mongoContractFixtures = resolve( import.meta.dirname, '../../../1-foundation/mongo-contract/test/fixtures', ); +const familyFixtures = resolve(import.meta.dirname, '../../../9-family/test/fixtures'); +const ormFixtures = resolve(import.meta.dirname, '../../../5-query-builders/orm/test/fixtures'); -const outputs = [ +const fixtures = [ { - output: generateContractDts( - fixtureContract(ormContractJson, 'orm'), - mongoEmission, - codecImports, - { - storageHash: 'orm-storage', - profileHash: 'orm-profile', - }, - ), + json: ormContractJson, + hash: 'orm', targets: [ - resolve(fixturesDir, 'orm-contract.d.ts'), - resolve(import.meta.dirname, '../../../9-family/test/fixtures/orm-contract.d.ts'), + { dir: mongoContractFixtures, file: 'orm-contract.d.ts', viaPackage: false }, + { dir: familyFixtures, file: 'orm-contract.d.ts', viaPackage: false }, ], }, { - output: generateContractDts( - fixtureContract(storedNamesContractJson, 'stored-names'), - mongoEmission, - codecImports, - { storageHash: 'stored-names-storage', profileHash: 'stored-names-profile' }, - ), - targets: [ - resolve( - import.meta.dirname, - '../../../5-query-builders/orm/test/fixtures/stored-names-contract.d.ts', - ), - ], + json: storedNamesContractJson, + hash: 'stored-names', + targets: [{ dir: ormFixtures, file: 'stored-names-contract.d.ts', viaPackage: true }], + }, + { + json: migrationContractJson, + hash: 'migration', + targets: [{ dir: familyFixtures, file: 'migration-contract.d.ts', viaPackage: false }], }, ]; -for (const { output, targets } of outputs) { - for (const target of targets) { - writeFileSync(target, output); +for (const { json, hash, targets } of fixtures) { + for (const { dir, file, viaPackage } of targets) { + const target = resolve(dir, file); + writeFileSync( + target, + generateContractDts( + fixtureContract(json, hash), + mongoEmission, + codecImportsFrom(dir, viaPackage), + { storageHash: `${hash}-storage`, profileHash: `${hash}-profile` }, + ), + ); console.log(`Generated ${target}`); } } diff --git a/packages/2-mongo-family/5-query-builders/orm/src/collection.ts b/packages/2-mongo-family/5-query-builders/orm/src/collection.ts index 2bbb60d087f4..e43392db04e1 100644 --- a/packages/2-mongo-family/5-query-builders/orm/src/collection.ts +++ b/packages/2-mongo-family/5-query-builders/orm/src/collection.ts @@ -4,8 +4,9 @@ import { type ContractModel, type ContractReferenceRelation, type ContractValueObject, - domainValueObjectsAtDefaultNamespace, type PlanMeta, + type ValueObjectReference, + valueObjectAt, } from '@internal/contract/types'; import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { @@ -259,6 +260,8 @@ function isUnknownRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } +const LIST_INDEX = /^\d+$/; + class MongoCollectionImpl< TContract extends MongoContractWithTypeMaps, ModelName extends string & keyof MongoModelsMap, @@ -909,7 +912,7 @@ class MongoCollectionImpl< this.#state, this.#contract.storage.storageHash, model, - this.#valueObjects(), + this.#contract.storage, ); } @@ -996,11 +999,11 @@ class MongoCollectionImpl< if (!model) { return Object.freeze({ kind: 'unknown' as const }); } - return contractModelToMongoResultShape(model, { valueObjects: this.#valueObjects() }); + return contractModelToMongoResultShape(model, { storage: this.#contract.storage }); } - #valueObjects(): Readonly> { - return domainValueObjectsAtDefaultNamespace(this.#contract.domain) ?? {}; + #valueObjectAt(reference: ValueObjectReference): ContractValueObject | undefined { + return valueObjectAt(this.#contract.storage, reference); } #compileWhereObject(data: Record): MongoFilterExpr[] { @@ -1058,9 +1061,12 @@ class MongoCollectionImpl< let field: ContractField | undefined = head === undefined ? undefined : this.#modelFields()[head]; for (const segment of rest) { + if (field?.many && LIST_INDEX.test(segment)) { + field = { ...field, many: false }; + continue; + } if (field?.type.kind !== 'valueObject') return undefined; - field = domainValueObjectsAtDefaultNamespace(this.#contract.domain)?.[field.type.name] - ?.fields[segment]; + field = this.#valueObjectAt(field.type)?.fields[segment]; } return field; } @@ -1083,8 +1089,7 @@ class MongoCollectionImpl< } if (field.type.kind === 'valueObject') { - const voName = field.type.name; - const voDef = domainValueObjectsAtDefaultNamespace(this.#contract.domain)?.[voName]; + const voDef = this.#valueObjectAt(field.type); if (!voDef) return new MongoParamRef(value); if (field.many && Array.isArray(value)) { @@ -1371,7 +1376,7 @@ class MongoCollectionImpl< return this.#fieldParam(value.value, field.type.codecId, path); } if (field.type.kind === 'valueObject' && isUnknownRecord(value.value)) { - const voDef = domainValueObjectsAtDefaultNamespace(this.#contract.domain)?.[field.type.name]; + const voDef = this.#valueObjectAt(field.type); if (voDef) return this.#wrapValueObject(value.value, voDef, path, 'write'); } return value; diff --git a/packages/2-mongo-family/5-query-builders/orm/src/compile.ts b/packages/2-mongo-family/5-query-builders/orm/src/compile.ts index ce81a721af5a..4509f8f71d6c 100644 --- a/packages/2-mongo-family/5-query-builders/orm/src/compile.ts +++ b/packages/2-mongo-family/5-query-builders/orm/src/compile.ts @@ -1,4 +1,4 @@ -import type { ContractModel, PlanMeta } from '@internal/contract/types'; +import type { ContractModel, PlanMeta, StorageBase } from '@internal/contract/types'; import type { MongoModelStorage } from '@internal/mongo-contract'; import type { MongoFieldShape, @@ -16,10 +16,7 @@ import { MongoSortStage, MongoUnwindStage, } from '@internal/mongo-query-ast/execution'; -import { - contractModelToMongoResultShape, - type MongoValueObjects, -} from '@internal/mongo-query-builder'; +import { contractModelToMongoResultShape } from '@internal/mongo-query-builder'; import { ifDefined } from '@internal/utils/defined'; import type { MongoCollectionState, MongoIncludeExpr } from './collection-state'; @@ -30,8 +27,11 @@ function isToOne(include: MongoIncludeExpr): boolean { /** * An included document is decoded through the related model's codecs, like the root document. A to-one include with no related document reads as `null`. */ -function includeShape(include: MongoIncludeExpr, valueObjects: MongoValueObjects): MongoFieldShape { - const target = contractModelToMongoResultShape(include.targetModel, { valueObjects }); +function includeShape( + include: MongoIncludeExpr, + storage: Pick, +): MongoFieldShape { + const target = contractModelToMongoResultShape(include.targetModel, { storage }); const fields = target.kind === 'document' ? target.fields : {}; return isToOne(include) ? { kind: 'document', nullable: true, fields } @@ -64,7 +64,7 @@ export function compileMongoQuery( state: MongoCollectionState, storageHash: string, model: ContractModel, - valueObjects: MongoValueObjects, + storage: Pick, ): MongoQueryPlan { const stages: MongoPipelineStage[] = []; @@ -119,9 +119,9 @@ export function compileMongoQuery( const resultShape = contractModelToMongoResultShape(model, { ...ifDefined('selection', selection), includes: Object.fromEntries( - state.includes.map((inc) => [inc.relationName, includeShape(inc, valueObjects)]), + state.includes.map((inc) => [inc.relationName, includeShape(inc, storage)]), ), - valueObjects, + storage, }); return { collection, command, meta, resultShape }; diff --git a/packages/2-mongo-family/5-query-builders/orm/src/field-accessor.ts b/packages/2-mongo-family/5-query-builders/orm/src/field-accessor.ts index f4e027245c5f..45d03a8901ac 100644 --- a/packages/2-mongo-family/5-query-builders/orm/src/field-accessor.ts +++ b/packages/2-mongo-family/5-query-builders/orm/src/field-accessor.ts @@ -1,7 +1,7 @@ import type { ContractField, - ContractValueObject, - ContractValueObjectDefinitions, + ContractValueObjectAt, + ValueObjectReference, } from '@internal/contract/types'; import type { AnyMongoTypeMaps, @@ -52,7 +52,7 @@ type ValueObjectFieldKeys< > = { [K in keyof MongoModelsMap[ModelName]['fields'] & string]: MongoModelsMap[ModelName]['fields'][K] extends { - readonly type: { readonly kind: 'valueObject'; readonly name: string }; + readonly type: ValueObjectReference; } ? K : never; @@ -93,26 +93,12 @@ export type FieldExpression> = pop(end: 1 | -1): FieldOperation; } & ([NumericOperand] extends [never] ? unknown : NumericOps); -type HasValueObjects = MongoContract; - -type MergedContractValueObjects = - ContractValueObjectDefinitions & - (TContract extends { readonly valueObjects?: infer VOs } - ? VOs extends Record - ? VOs - : Record - : Record); - -type VOFields< - TContract extends HasValueObjects, - VOName extends string, -> = VOName extends keyof MergedContractValueObjects - ? MergedContractValueObjects[VOName] extends { - readonly fields: infer F extends Record; - } +type VOFields = + ContractValueObjectAt extends { + readonly fields: infer F extends Record; + } ? F - : never - : never; + : never; type VOScalarFieldKeys> = { [K in keyof Fields & string]: Fields[K] extends { readonly type: { readonly kind: 'scalar' } } @@ -122,23 +108,36 @@ type VOScalarFieldKeys> = { type VOValueObjectFieldKeys> = { [K in keyof Fields & string]: Fields[K] extends { - readonly type: { readonly kind: 'valueObject'; readonly name: string }; + readonly type: ValueObjectReference; } ? K : never; }[keyof Fields & string]; +/** A value object's storage coordinate as one string, so the value objects a path has entered form a union. */ +type ValueObjectKey = + `${Reference['namespaceId']}.${Reference['entityName']}`; + +/** The dot paths into a value object's fields. A path ends at a value object it has already entered, so a value object that contains itself gives finitely many paths. */ type VODotPaths< - TContract extends HasValueObjects, + TContract extends MongoContract, Fields extends Record, Prefix extends string, + Entered extends string, > = | { [K in VOScalarFieldKeys]: `${Prefix}${K}` }[VOScalarFieldKeys] | { [K in VOValueObjectFieldKeys]: Fields[K] extends { - readonly type: { readonly kind: 'valueObject'; readonly name: infer N extends string }; + readonly type: infer Reference extends ValueObjectReference; } - ? VODotPaths, `${Prefix}${K}.`> + ? ValueObjectKey extends Entered + ? never + : VODotPaths< + TContract, + VOFields, + `${Prefix}${K}.`, + Entered | ValueObjectKey + > : never; }[VOValueObjectFieldKeys]; @@ -150,9 +149,9 @@ export type DotPath< TContract, ModelName >]: MongoModelsMap[ModelName]['fields'][K] extends { - readonly type: { readonly kind: 'valueObject'; readonly name: infer N extends string }; + readonly type: infer Reference extends ValueObjectReference; } - ? VODotPaths, `${K}.`> + ? VODotPaths, `${K}.`, ValueObjectKey> : never; }[ValueObjectFieldKeys]; diff --git a/packages/2-mongo-family/5-query-builders/orm/test/collection.mutation-defaults.test.ts b/packages/2-mongo-family/5-query-builders/orm/test/collection.mutation-defaults.test.ts index c839679b1470..4262ccb9ad5f 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/collection.mutation-defaults.test.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/collection.mutation-defaults.test.ts @@ -188,6 +188,11 @@ describe('ORM create paths apply onCreate defaults', () => { }); }); +/** The payload a JavaScript caller passes with `field` set to `undefined`, which the typed input refuses. */ +function withUndefinedField(payload: Payload, field: string): Payload { + return Object.assign(payload, { [field]: undefined }); +} + describe('ORM update paths apply onUpdate defaults', () => { it('update adds the generated value to $set', async () => { const defaults = fakeMutationDefaults(); @@ -203,7 +208,7 @@ describe('ORM update paths apply onUpdate defaults', () => { it('update whose only field is undefined adds nothing', async () => { const defaults = fakeMutationDefaults(); const { executor, plans } = recordingExecutor([]); - await users(executor, defaults).where(byEmail).update({ name: undefined }); + await users(executor, defaults).where(byEmail).update(withUndefinedField({}, 'name')); expect(unwrap(commandOf(plans, 'findOneAndUpdate')['update'])).toEqual({ $set: {} }); expect(defaults.calls[0]?.values).toEqual({}); }); diff --git a/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts b/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts index 1cc730079fd3..4e0673d7f434 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/compile.test.ts @@ -66,7 +66,13 @@ const testUserModel: ContractModel = { address: { nullable: false, many: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, }, }, }; @@ -98,6 +104,8 @@ const testPostModel: ContractModel = { const testHash = 'test-hash'; +const noStorage = { namespaces: {} }; + function stages(plan: MongoQueryPlan): ReadonlyArray { if (plan.command.kind !== 'aggregate') throw new Error(`Expected aggregate, got ${plan.command.kind}`); @@ -106,7 +114,13 @@ function stages(plan: MongoQueryPlan): ReadonlyArray { describe('compileMongoQuery', () => { it('produces empty pipeline from empty state', () => { - const plan = compileMongoQuery('users', emptyCollectionState(), testHash, testUserModel, {}); + const plan = compileMongoQuery( + 'users', + emptyCollectionState(), + testHash, + testUserModel, + noStorage, + ); expect(plan.collection).toBe('users'); expect(plan.command.kind).toBe('aggregate'); expect(stages(plan)).toEqual([]); @@ -119,7 +133,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), filters: [MongoFieldFilter.eq('name', 'Alice')], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); const match = stages(plan)[0] as MongoMatchStage; expect(match).toBeInstanceOf(MongoMatchStage); @@ -131,7 +145,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), filters: [MongoFieldFilter.eq('name', 'Alice'), MongoFieldFilter.gte('age', 18)], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); const match = stages(plan)[0] as MongoMatchStage; expect(match.filter.kind).toBe('and'); @@ -143,7 +157,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), selectedFields: ['name', 'email'], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); const project = stages(plan)[0] as MongoProjectStage; expect(project).toBeInstanceOf(MongoProjectStage); @@ -155,7 +169,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), selectedFields: ['_id', 'name'], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); const project = stages(plan)[0] as MongoProjectStage; expect(project.projection).toEqual({ _id: 1, name: 1 }); }); @@ -165,7 +179,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), selectedFields: [], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toEqual([]); }); @@ -174,7 +188,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), orderBy: { age: -1, name: 1 }, }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); const sort = stages(plan)[0] as MongoSortStage; expect(sort).toBeInstanceOf(MongoSortStage); @@ -186,7 +200,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), limit: 10, }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); const limit = stages(plan)[0] as MongoLimitStage; expect(limit).toBeInstanceOf(MongoLimitStage); @@ -198,7 +212,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), offset: 5, }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); const skip = stages(plan)[0] as MongoSkipStage; expect(skip).toBeInstanceOf(MongoSkipStage); @@ -220,7 +234,7 @@ describe('compileMongoQuery', () => { }, ], }; - const plan = compileMongoQuery('posts', state, testHash, testPostModel, {}); + const plan = compileMongoQuery('posts', state, testHash, testPostModel, noStorage); expect(stages(plan)).toHaveLength(2); const lookup = stages(plan)[0] as MongoLookupStage; expect(lookup).toBeInstanceOf(MongoLookupStage); @@ -249,7 +263,7 @@ describe('compileMongoQuery', () => { }, ], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); expect(stages(plan)).toHaveLength(1); expect(stages(plan)[0]).toBeInstanceOf(MongoLookupStage); }); @@ -273,7 +287,7 @@ describe('compileMongoQuery', () => { limit: 5, selectedFields: ['_id', 'name', 'email'], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); const stageKinds = stages(plan).map((s) => s.kind); expect(stageKinds).toEqual(['match', 'lookup', 'sort', 'skip', 'limit', 'project']); @@ -281,7 +295,13 @@ describe('compileMongoQuery', () => { describe('resultShape from contract', () => { it('full model maps scalar fields to leaf codec shapes', () => { - const plan = compileMongoQuery('users', emptyCollectionState(), testHash, testUserModel, {}); + const plan = compileMongoQuery( + 'users', + emptyCollectionState(), + testHash, + testUserModel, + noStorage, + ); expect(plan.resultShape?.kind).toBe('document'); if (plan.resultShape?.kind !== 'document') return; expect(plan.resultShape.fields['name']).toEqual({ @@ -293,7 +313,13 @@ describe('compileMongoQuery', () => { }); it('scalar many field is array with leaf element', () => { - const plan = compileMongoQuery('users', emptyCollectionState(), testHash, testUserModel, {}); + const plan = compileMongoQuery( + 'users', + emptyCollectionState(), + testHash, + testUserModel, + noStorage, + ); if (plan.resultShape?.kind !== 'document') throw new Error('expected document'); expect(plan.resultShape.fields['tags']).toEqual({ kind: 'array', @@ -307,7 +333,7 @@ describe('compileMongoQuery', () => { ...emptyCollectionState(), selectedFields: ['name', 'email'], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); if (plan.resultShape?.kind !== 'document') throw new Error('expected document'); expect(Object.keys(plan.resultShape.fields).sort()).toEqual(['email', 'name']); }); @@ -327,7 +353,7 @@ describe('compileMongoQuery', () => { }, ], }; - const plan = compileMongoQuery('posts', state, testHash, testPostModel, {}); + const plan = compileMongoQuery('posts', state, testHash, testPostModel, noStorage); if (plan.resultShape?.kind !== 'document') throw new Error('expected document'); expect(plan.resultShape.fields['author']).toMatchObject({ kind: 'document', @@ -357,7 +383,7 @@ describe('compileMongoQuery', () => { }, ], }; - const plan = compileMongoQuery('users', state, testHash, testUserModel, {}); + const plan = compileMongoQuery('users', state, testHash, testUserModel, noStorage); const projectStage = stages(plan).find((s) => s.kind === 'project') as | MongoProjectStage | undefined; @@ -381,7 +407,7 @@ describe('compileMongoQuery', () => { }, ], }; - const plan = compileMongoQuery('posts', state, testHash, testPostModel, {}); + const plan = compileMongoQuery('posts', state, testHash, testPostModel, noStorage); expect(stages(plan).map((s) => s.kind)).toEqual(['lookup', 'unwind', 'project']); const projectStage = stages(plan).find((s) => s.kind === 'project') as | MongoProjectStage diff --git a/packages/2-mongo-family/5-query-builders/orm/test/dot-path.recursive.test-d.ts b/packages/2-mongo-family/5-query-builders/orm/test/dot-path.recursive.test-d.ts new file mode 100644 index 000000000000..7324ebd25de1 --- /dev/null +++ b/packages/2-mongo-family/5-query-builders/orm/test/dot-path.recursive.test-d.ts @@ -0,0 +1,62 @@ +import { expectTypeOf, test } from 'vitest'; +import type { Contract } from '../../../1-foundation/mongo-contract/test/fixtures/orm-contract'; +import type { DotPath } from '../src/field-accessor'; + +type NodeRef = { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Node'; +}; + +type WithTree = Contract & { + readonly storage: { + readonly namespaces: { + readonly __unbound__: { + readonly entries: { + readonly valueObject: { + readonly Node: { + readonly fields: { + readonly label: { + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + readonly nullable: false; + readonly many: false; + }; + readonly child: { + readonly type: NodeRef; + readonly nullable: true; + readonly many: false; + }; + }; + }; + }; + }; + }; + }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly fields: { + readonly tree: { + readonly type: NodeRef; + readonly nullable: false; + readonly many: false; + }; + }; + }; + }; + }; + }; + }; +}; + +test('dot paths end at a value object already on the path', () => { + type Paths = DotPath; + expectTypeOf<'tree.label'>().toExtend(); + expectTypeOf<'tree.child.label'>().not.toExtend(); + expectTypeOf<'homeAddress.city'>().toExtend(); +}); diff --git a/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.d.ts b/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.d.ts index 989f1dd4760f..9d80ed3c8616 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.d.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.d.ts @@ -12,9 +12,11 @@ import type { import type { Contract as ContractType, ExecutionHashBase, + FieldTypesFromDomain, NamespaceId, ProfileHashBase, StorageHashBase, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = StorageHashBase<"stored-names-storage">; @@ -23,44 +25,48 @@ export type ProfileHash = ProfileHashBase<"stored-names-profile">; export type CodecTypes = MongoCodecTypes; -export type HomeAddressOutput = { readonly city: CodecTypes["mongo/string@1"]["output"] }; -export type HomeAddressInput = { readonly city: CodecTypes["mongo/string@1"]["input"] }; -export type FieldOutputTypes = { readonly __unbound__: { readonly Address: { readonly street: CodecTypes["mongo/string@1"]["output"]; readonly city: CodecTypes["mongo/string@1"]["output"] }; readonly Bug: { readonly severity: CodecTypes["mongo/string@1"]["output"] }; readonly Post: { readonly id: CodecTypes["mongo/objectId@1"]["output"]; readonly authorId: CodecTypes["mongo/objectId@1"]["output"]; readonly title: CodecTypes["mongo/string@1"]["output"] }; readonly Task: { readonly id: CodecTypes["mongo/objectId@1"]["output"]; readonly kind: CodecTypes["mongo/string@1"]["output"]; readonly title: CodecTypes["mongo/string@1"]["output"] }; readonly User: { readonly id: CodecTypes["mongo/objectId@1"]["output"]; readonly displayName: CodecTypes["mongo/string@1"]["output"]; readonly email: CodecTypes["mongo/string@1"]["output"] | null; readonly createdAt: CodecTypes["mongo/date@1"]["output"]; readonly home: HomeAddressOutput | null } } }; -export type FieldInputTypes = { readonly __unbound__: { readonly Address: { readonly street: CodecTypes["mongo/string@1"]["input"]; readonly city: CodecTypes["mongo/string@1"]["input"] }; readonly Bug: { readonly severity: CodecTypes["mongo/string@1"]["input"] }; readonly Post: { readonly id: CodecTypes["mongo/objectId@1"]["input"]; readonly authorId: CodecTypes["mongo/objectId@1"]["input"]; readonly title: CodecTypes["mongo/string@1"]["input"] }; readonly Task: { readonly id: CodecTypes["mongo/objectId@1"]["input"]; readonly kind: CodecTypes["mongo/string@1"]["input"]; readonly title: CodecTypes["mongo/string@1"]["input"] }; readonly User: { readonly id: CodecTypes["mongo/objectId@1"]["input"]; readonly displayName: CodecTypes["mongo/string@1"]["input"]; readonly email: CodecTypes["mongo/string@1"]["input"] | null; readonly createdAt: CodecTypes["mongo/date@1"]["input"]; readonly home: HomeAddressInput | null } } }; +type ParameterizedOutputTypeFunctions = never; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage; +type ValueObjectInputTypes = ValueObjectTypesFromStorage; +export type HomeAddressOutput = ValueObjectOutputTypes["__unbound__"]["HomeAddress"]; +export type HomeAddressInput = ValueObjectInputTypes["__unbound__"]["HomeAddress"]; +type FieldTypeOverrides = { }; +export type FieldOutputTypes = FieldTypesFromDomain; +export type FieldInputTypes = FieldTypesFromDomain; export namespace Models { export type unbound_User = { - id: CodecTypes["mongo/objectId@1"]["output"]; - displayName: CodecTypes["mongo/string@1"]["output"]; - email: CodecTypes["mongo/string@1"]["output"] | null; - createdAt: CodecTypes["mongo/date@1"]["output"]; - home: HomeAddressOutput | null; + id: FieldOutputTypes["__unbound__"]["User"]["id"]; + displayName: FieldOutputTypes["__unbound__"]["User"]["displayName"]; + email: FieldOutputTypes["__unbound__"]["User"]["email"]; + createdAt: FieldOutputTypes["__unbound__"]["User"]["createdAt"]; + home: FieldOutputTypes["__unbound__"]["User"]["home"]; addresses: unbound_Address[]; posts: unbound_Post[]; readonly [RelationKeys]?: "posts"; }; export type unbound_Address = { - street: CodecTypes["mongo/string@1"]["output"]; - city: CodecTypes["mongo/string@1"]["output"]; + street: FieldOutputTypes["__unbound__"]["Address"]["street"]; + city: FieldOutputTypes["__unbound__"]["Address"]["city"]; }; export type unbound_Post = { - id: CodecTypes["mongo/objectId@1"]["output"]; - authorId: CodecTypes["mongo/objectId@1"]["output"]; - title: CodecTypes["mongo/string@1"]["output"]; + id: FieldOutputTypes["__unbound__"]["Post"]["id"]; + authorId: FieldOutputTypes["__unbound__"]["Post"]["authorId"]; + title: FieldOutputTypes["__unbound__"]["Post"]["title"]; author: unbound_User; readonly [RelationKeys]?: "author"; }; export type unbound_Task = { - id: CodecTypes["mongo/objectId@1"]["output"]; + id: FieldOutputTypes["__unbound__"]["Task"]["id"]; kind: "bug"; - title: CodecTypes["mongo/string@1"]["output"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; readonly [RelationKeys]?: never; }; export type unbound_Bug = { - id: CodecTypes["mongo/objectId@1"]["output"]; + id: FieldOutputTypes["__unbound__"]["Task"]["id"]; kind: "bug"; - title: CodecTypes["mongo/string@1"]["output"]; - severity: CodecTypes["mongo/string@1"]["output"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + severity: FieldOutputTypes["__unbound__"]["Bug"]["severity"]; readonly [RelationKeys]?: never; }; export type unbound_AnyTask = unbound_Bug; @@ -80,7 +86,7 @@ export declare const models: { export type TypeMaps = MongoTypeMaps; type ContractBase = Omit< - ContractType<{ readonly namespaces: { readonly __unbound__: { readonly id: "__unbound__"; readonly kind: "mongo-namespace"; readonly entries: { readonly collection: { readonly posts: MongoCollection; readonly tasks: MongoCollection; readonly users: MongoCollection } } } }; readonly storageHash: StorageHash }>, + ContractType<{ readonly namespaces: { readonly __unbound__: { readonly id: "__unbound__"; readonly kind: "mongo-namespace"; readonly entries: { readonly collection: { readonly posts: MongoCollection; readonly tasks: MongoCollection; readonly users: MongoCollection }; readonly valueObject: { readonly HomeAddress: { readonly fields: { readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } } } } } }; readonly storageHash: StorageHash }>, 'roots' | 'domain' > & { readonly target: "mongo"; @@ -89,8 +95,7 @@ type ContractBase = Omit< readonly domain: { readonly namespaces: { readonly __unbound__: { - readonly models: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly fields: { readonly street: { readonly field: "street_name" }; readonly city: { readonly field: "city" } } }; readonly owner: "User" }; readonly Bug: { readonly fields: { readonly severity: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly severity: { readonly field: "bug_severity" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" } }; readonly Post: { readonly fields: { readonly id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly authorId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: { readonly author: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["authorId"]; readonly targetFields: readonly ["id"] } } }; readonly storage: { readonly collection: "posts"; readonly fields: { readonly id: { readonly field: "_id" }; readonly authorId: { readonly field: "author_id" }; readonly title: { readonly field: "title" } } } }; readonly Task: { readonly fields: { readonly id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly kind: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly id: { readonly field: "_id" }; readonly kind: { readonly field: "_kind" }; readonly title: { readonly field: "task_title" } } }; readonly discriminator: { readonly field: "kind" }; readonly variants: { readonly bug: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Bug" } } }; readonly User: { readonly fields: { readonly id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly displayName: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly email: { readonly nullable: true; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly createdAt: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } }; readonly home: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly name: "HomeAddress" } } }; readonly relations: { readonly posts: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Post" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["id"]; readonly targetFields: readonly ["authorId"] } }; readonly addresses: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Address" }; readonly cardinality: "1:N" } }; readonly storage: { readonly collection: "users"; readonly fields: { readonly id: { readonly field: "_id" }; readonly displayName: { readonly field: "display_name" }; readonly email: { readonly field: "email" }; readonly createdAt: { readonly field: "created_at" }; readonly home: { readonly field: "home_address" } }; readonly relations: { readonly addresses: { readonly field: "user_addresses" } } } } }; - readonly valueObjects: { readonly HomeAddress: { readonly fields: { readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } } }; + readonly models: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly fields: { readonly street: { readonly field: "street_name" }; readonly city: { readonly field: "city" } } }; readonly owner: "User" }; readonly Bug: { readonly fields: { readonly severity: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly severity: { readonly field: "bug_severity" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" } }; readonly Post: { readonly fields: { readonly id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly authorId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: { readonly author: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["authorId"]; readonly targetFields: readonly ["id"] } } }; readonly storage: { readonly collection: "posts"; readonly fields: { readonly id: { readonly field: "_id" }; readonly authorId: { readonly field: "author_id" }; readonly title: { readonly field: "title" } } } }; readonly Task: { readonly fields: { readonly id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly kind: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly id: { readonly field: "_id" }; readonly kind: { readonly field: "_kind" }; readonly title: { readonly field: "task_title" } } }; readonly discriminator: { readonly field: "kind" }; readonly variants: { readonly bug: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Bug" } } }; readonly User: { readonly fields: { readonly id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly displayName: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly email: { readonly nullable: true; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly createdAt: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } }; readonly home: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "HomeAddress" } } }; readonly relations: { readonly posts: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Post" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["id"]; readonly targetFields: readonly ["authorId"] } }; readonly addresses: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Address" }; readonly cardinality: "1:N" } }; readonly storage: { readonly collection: "users"; readonly fields: { readonly id: { readonly field: "_id" }; readonly displayName: { readonly field: "display_name" }; readonly email: { readonly field: "email" }; readonly createdAt: { readonly field: "created_at" }; readonly home: { readonly field: "home_address" } }; readonly relations: { readonly addresses: { readonly field: "user_addresses" } } } } }; }; }; }; @@ -98,7 +103,6 @@ type ContractBase = Omit< readonly extensions: { }; readonly execution: { readonly executionHash: ExecutionHash; readonly mutations: { readonly defaults: readonly [{ readonly onCreate: { readonly id: "timestampNow"; readonly kind: "generator" }; readonly ref: { readonly entry: "users"; readonly field: "created_at"; readonly namespace: "__unbound__" } }] } }; readonly meta: { }; - readonly valueObjects: { readonly HomeAddress: { readonly fields: { readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } } }; readonly profileHash: ProfileHash; }; diff --git a/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.json b/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.json index 197ee3401579..16cdbb7cc382 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.json +++ b/packages/2-mongo-family/5-query-builders/orm/test/fixtures/stored-names-contract.json @@ -25,6 +25,20 @@ "users": {}, "posts": {}, "tasks": {} + }, + "valueObject": { + "HomeAddress": { + "fields": { + "city": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + } + } + } } } } @@ -88,7 +102,10 @@ "home": { "type": { "kind": "valueObject", - "name": "HomeAddress" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "HomeAddress" }, "nullable": true, "many": false @@ -305,20 +322,6 @@ "namespace": "__unbound__" } } - }, - "valueObjects": { - "HomeAddress": { - "fields": { - "city": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - } - } - } } } } diff --git a/packages/2-mongo-family/5-query-builders/orm/test/integration/value-objects.test.ts b/packages/2-mongo-family/5-query-builders/orm/test/integration/value-objects.test.ts new file mode 100644 index 000000000000..ec3bde065542 --- /dev/null +++ b/packages/2-mongo-family/5-query-builders/orm/test/integration/value-objects.test.ts @@ -0,0 +1,223 @@ +import mongoRuntimeAdapter from '@internal/adapter-mongo/runtime'; +import { createMongoDriver } from '@internal/driver-mongo'; +import type { + AnyMongoTypeMaps, + MongoContract, + MongoContractWithTypeMaps, +} from '@internal/mongo-contract'; +import { MongoFieldFilter, type MongoFilterExpr } from '@internal/mongo-query-ast/execution'; +import { + buildMongoEnums, + createMongoExecutionContext, + createMongoExecutionStack, + createMongoRuntime, + type MongoRuntime, +} from '@internal/mongo-runtime'; +import mongoRuntimeTarget from '@internal/target-mongo/runtime'; +import { timeouts } from '@repo/test-utils'; +import { MongoClient, ObjectId } from 'mongodb'; +import { MongoMemoryReplSet } from 'mongodb-memory-server'; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import type { MongoOrmEnums } from '../../src/collection'; +import type { FieldExpression, FieldOperation } from '../../src/field-accessor'; +import { mongoOrm } from '../../src/mongo-orm'; + +const objectId = { kind: 'scalar', codecId: 'mongo/objectId@1' } as const; +const text = { kind: 'scalar', codecId: 'mongo/string@1' } as const; + +function valueObjectRef(entityName: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName, + }; +} + +const contractJson = { + target: 'mongo', + targetFamily: 'mongo', + storageHash: 'value-objects-hash', + roots: { places: { model: 'Place', namespace: '__unbound__' } }, + storage: { + namespaces: { + __unbound__: { + id: '__unbound__', + entries: { + collection: { places: {} }, + valueObject: { + Stop: { + fields: { + label: { type: text, nullable: false, many: false }, + placeId: { type: objectId, nullable: false, many: false }, + }, + }, + Address: { + fields: { + city: { type: text, nullable: false, many: false }, + landmark: { type: valueObjectRef('Stop'), nullable: false, many: false }, + }, + }, + }, + }, + }, + }, + }, + domain: { + namespaces: { + __unbound__: { + models: { + Place: { + storage: { + collection: 'places', + fields: { + _id: { field: '_id' }, + address: { field: 'address' }, + stops: { field: 'stops' }, + }, + }, + fields: { + _id: { type: objectId, nullable: false, many: false }, + address: { type: valueObjectRef('Address'), nullable: false, many: false }, + stops: { + type: valueObjectRef('Stop'), + nullable: false, + many: { elementNullable: false }, + }, + }, + relations: {}, + }, + }, + }, + }, + }, + capabilities: {}, + extensions: {}, + profileHash: 'value-objects-profile', + meta: {}, +}; + +interface Places { + create(row: Record): Promise<{ readonly _id: unknown }>; + where(filter: MongoFilterExpr | Record): { + first(): Promise; + update( + operations: (field: (path: string) => FieldExpression) => FieldOperation[], + ): Promise; + }; +} + +function placesOf(orm: unknown): Places { + return (orm as { readonly places: Places }).places; +} + +const contract = contractJson as unknown as MongoContractWithTypeMaps< + MongoContract, + AnyMongoTypeMaps +>; + +describe('value objects in the Mongo ORM', { timeout: timeouts.spinUpMongoMemoryServer }, () => { + let replSet: MongoMemoryReplSet; + let client: MongoClient; + let runtime: MongoRuntime; + let enums: MongoOrmEnums; + const dbName = 'orm_value_objects_test'; + + beforeAll(async () => { + replSet = await MongoMemoryReplSet.create({ + replSet: { count: 1, storageEngine: 'wiredTiger' }, + }); + client = new MongoClient(replSet.getUri()); + await client.connect(); + const stack = createMongoExecutionStack({ + target: mongoRuntimeTarget, + adapter: mongoRuntimeAdapter, + }); + const context = createMongoExecutionContext({ contract: {}, stack }); + const driver = await createMongoDriver(replSet.getUri(), dbName); + runtime = createMongoRuntime({ context, driver }); + enums = buildMongoEnums(contract, context.codecs); + }, timeouts.spinUpMongoMemoryServer); + + beforeEach(async () => { + await client.db(dbName).dropDatabase(); + }); + + afterAll(async () => { + await Promise.allSettled([runtime?.close(), client?.close(), replSet?.stop()]); + }, timeouts.spinUpMongoMemoryServer); + + it('writes and reads a nested and a list value object through the codecs of their members', async () => { + const places = placesOf(mongoOrm({ contract, executor: runtime, enums })); + const landmarkId = new ObjectId().toHexString(); + const stopId = new ObjectId().toHexString(); + const created = await places.create({ + address: { city: 'Lyon', landmark: { label: 'Fourvière', placeId: landmarkId } }, + stops: [{ label: 'Bellecour', placeId: stopId }], + }); + + const stored = await client.db(dbName).collection('places').findOne({}); + const read = await places.where({ _id: created._id }).first(); + + expect({ stored, read }).toEqual({ + stored: { + _id: expect.any(ObjectId), + address: { + city: 'Lyon', + landmark: { label: 'Fourvière', placeId: new ObjectId(landmarkId) }, + }, + stops: [{ label: 'Bellecour', placeId: new ObjectId(stopId) }], + }, + read: { + _id: created._id, + address: { city: 'Lyon', landmark: { label: 'Fourvière', placeId: landmarkId } }, + stops: [{ label: 'Bellecour', placeId: stopId }], + }, + }); + }); + + it('filters and updates by a dot path into a nested value object', async () => { + const places = placesOf(mongoOrm({ contract, executor: runtime, enums })); + const landmarkId = new ObjectId().toHexString(); + await places.create({ + address: { city: 'Lyon', landmark: { label: 'Fourvière', placeId: landmarkId } }, + stops: [], + }); + + await places + .where(MongoFieldFilter.eq('address.landmark.placeId', landmarkId)) + .update((p) => [p('address.landmark.label').set('Basilique')]); + + const stored = await client.db(dbName).collection('places').findOne({}); + expect(stored?.['address']).toEqual({ + city: 'Lyon', + landmark: { label: 'Basilique', placeId: new ObjectId(landmarkId) }, + }); + }); + + it('filters by a dot path through a list index into a list of value objects', async () => { + const places = placesOf(mongoOrm({ contract, executor: runtime, enums })); + const firstId = new ObjectId().toHexString(); + const secondId = new ObjectId().toHexString(); + await places.create({ + address: { city: 'Paris', landmark: { label: 'Louvre', placeId: secondId } }, + stops: [ + { label: 'Châtelet', placeId: secondId }, + { label: 'Bastille', placeId: firstId }, + ], + }); + await places.create({ + address: { city: 'Lyon', landmark: { label: 'Fourvière', placeId: firstId } }, + stops: [ + { label: 'Bellecour', placeId: firstId }, + { label: 'Terreaux', placeId: secondId }, + ], + }); + + const found = await places.where(MongoFieldFilter.eq('stops.1.placeId', secondId)).first(); + expect(found).toMatchObject({ + stops: [{ label: 'Bellecour' }, { label: 'Terreaux' }], + }); + }); +}); diff --git a/packages/2-mongo-family/5-query-builders/orm/test/orm-types.test-d.ts b/packages/2-mongo-family/5-query-builders/orm/test/orm-types.test-d.ts index 92927ad69dc2..e09f2564604c 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/orm-types.test-d.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/orm-types.test-d.ts @@ -10,7 +10,6 @@ import type { DotPath, FieldAccessor, FieldExpression, - FieldOperation, ResolveDotPathType, } from '../src/field-accessor'; import type { MongoOrmClient } from '../src/mongo-orm'; @@ -289,7 +288,6 @@ type ContractWithAssignee = Omit & 'relations' > & { readonly relations: { readonly assignee: AssigneeRelation } }; }; - readonly valueObjects: Contract['domain']['namespaces']['__unbound__']['valueObjects']; }; }; }; @@ -508,10 +506,7 @@ test('include() on 1:N reference relation all() returns array type', () => { test('FieldAccessor has FieldExpression for scalar fields', () => { type Accessor = FieldAccessor; expectTypeOf().toExtend>(); - expectTypeOf().toMatchTypeOf<{ - set: (v: unknown) => FieldOperation; - unset: () => FieldOperation; - }>(); + expectTypeOf().toExtend>(); }); test('FieldAccessor has FieldExpression for array fields', () => { diff --git a/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts b/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts index 9e36b2a2c9df..25bebdc09162 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/value-object-inputs.test-d.ts @@ -61,7 +61,13 @@ type VOContract = MongoContractWithTypeMaps< readonly contactInfo: { readonly nullable: true; readonly many: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'ContactInfo' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'ContactInfo'; + }; }; readonly tags: { readonly nullable: false; @@ -81,22 +87,6 @@ type VOContract = MongoContractWithTypeMaps< }; }; }; - readonly valueObjects: { - readonly ContactInfo: { - readonly fields: { - readonly phone: { - readonly nullable: false; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly website: { - readonly nullable: true; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; @@ -106,7 +96,25 @@ type VOContract = MongoContractWithTypeMaps< readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'mongo-namespace'; - readonly entries: { readonly collection: { readonly users: MongoCollection } }; + readonly entries: { + readonly collection: { readonly users: MongoCollection }; + readonly valueObject: { + readonly ContactInfo: { + readonly fields: { + readonly phone: { + readonly nullable: false; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly website: { + readonly nullable: true; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; }; }; }; @@ -232,7 +240,13 @@ type VOContractWithFieldTypes = MongoContractWithTypeMaps< readonly contactInfo: { readonly nullable: true; readonly many: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'ContactInfo' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'ContactInfo'; + }; }; readonly tags: { readonly nullable: false; @@ -252,22 +266,6 @@ type VOContractWithFieldTypes = MongoContractWithTypeMaps< }; }; }; - readonly valueObjects: { - readonly ContactInfo: { - readonly fields: { - readonly phone: { - readonly nullable: false; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly website: { - readonly nullable: true; - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; @@ -277,7 +275,25 @@ type VOContractWithFieldTypes = MongoContractWithTypeMaps< readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'mongo-namespace'; - readonly entries: { readonly collection: { readonly users: MongoCollection } }; + readonly entries: { + readonly collection: { readonly users: MongoCollection }; + readonly valueObject: { + readonly ContactInfo: { + readonly fields: { + readonly phone: { + readonly nullable: false; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly website: { + readonly nullable: true; + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; }; }; }; diff --git a/packages/2-mongo-family/5-query-builders/orm/test/where-equality.test.ts b/packages/2-mongo-family/5-query-builders/orm/test/where-equality.test.ts index 245116a929f4..58ef00b80559 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/where-equality.test.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/where-equality.test.ts @@ -8,7 +8,13 @@ const stringType = { kind: 'scalar', codecId: 'mongo/string@1' } as const; const entryList: ContractField = { nullable: true, - type: { kind: 'valueObject', name: 'Entry' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Entry', + }, many: { elementNullable: true }, }; diff --git a/packages/2-mongo-family/5-query-builders/orm/test/where-list-fields.test-d.ts b/packages/2-mongo-family/5-query-builders/orm/test/where-list-fields.test-d.ts index f1a1bf599261..050cfb47c9f1 100644 --- a/packages/2-mongo-family/5-query-builders/orm/test/where-list-fields.test-d.ts +++ b/packages/2-mongo-family/5-query-builders/orm/test/where-list-fields.test-d.ts @@ -69,18 +69,19 @@ type ListsContract = MongoContractWithTypeMaps< readonly entries: { readonly nullable: false; readonly many: { readonly elementNullable: false }; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Entry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Entry'; + }; }; }; readonly relations: Record; readonly storage: { readonly collection: 'lists' }; }; }; - readonly valueObjects: { - readonly Entry: { - readonly fields: { readonly value: ScalarField }; - }; - }; }; }; }; @@ -90,7 +91,14 @@ type ListsContract = MongoContractWithTypeMaps< readonly __unbound__: { readonly id: '__unbound__'; readonly kind: 'mongo-namespace'; - readonly entries: { readonly collection: { readonly lists: MongoStorageCollection } }; + readonly entries: { + readonly collection: { readonly lists: MongoStorageCollection }; + readonly valueObject: { + readonly Entry: { + readonly fields: { readonly value: ScalarField }; + }; + }; + }; }; }; }; diff --git a/packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts b/packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts index 40c0b9fa6369..7e8764451256 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts @@ -1,4 +1,4 @@ -import { domainValueObjectsAtDefaultNamespace, type PlanMeta } from '@internal/contract/types'; +import type { PlanMeta } from '@internal/contract/types'; import { type AnyMongoTypeMaps, defaultMongoDomainNamespaceId, @@ -832,9 +832,7 @@ export class PipelineChain< resultShape = model ? computePipelineResultShape( this.#state.stages, - contractModelToMongoResultShape(model, { - valueObjects: domainValueObjectsAtDefaultNamespace(contractNarrow.domain) ?? {}, - }), + contractModelToMongoResultShape(model, { storage: contractNarrow.storage }), ) : { kind: 'unknown' as const }; } diff --git a/packages/2-mongo-family/5-query-builders/query-builder/src/exports/index.ts b/packages/2-mongo-family/5-query-builders/query-builder/src/exports/index.ts index ac02cf05bcdd..3188bc38d6c8 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/src/exports/index.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/src/exports/index.ts @@ -37,7 +37,6 @@ export type { export { contractFieldToMongoFieldShape, contractModelToMongoResultShape, - type MongoValueObjects, } from '../result-shape'; export { CollectionHandle, FilteredCollection } from '../state-classes'; export type { diff --git a/packages/2-mongo-family/5-query-builders/query-builder/src/resolve-path.ts b/packages/2-mongo-family/5-query-builders/query-builder/src/resolve-path.ts index 36261796f0b1..c141dfaa3e51 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/src/resolve-path.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/src/resolve-path.ts @@ -1,4 +1,4 @@ -import type { ContractValueObjectDefinitions } from '@internal/contract/types'; +import type { ContractValueObjectAt, ValueObjectReference } from '@internal/contract/types'; import type { MongoContract, MongoModelsMap, OwnStoredNameOf } from '@internal/mongo-contract'; import type { DocField } from './types'; @@ -48,7 +48,7 @@ export interface ModelArrayField extends DocField { * Phantom-symbol brand placed on `ModelToDocShape`'s output (and inherited * through shape-extending stages like `addFields`, `match`, `lookup`) so * `ResolveRow` can route value-object resolution through `InferModelRow` - * — which walks the contract's `valueObjects` registry and resolves + * — which reads the storage value objects fields refer to and resolves * nested types — instead of falling through the codec-lookup branch to * `unknown`. * @@ -122,48 +122,57 @@ type FieldToLeaf = F extends { * `ModelNestedShape` hover output and `keyof`/indexed-access resolution * concrete instead of collapsing to `{ [x: string]: … }`. */ -type TranslateField = F extends { +type TranslateField = F extends { readonly many: { readonly elementNullable: boolean }; } ? FieldToLeaf : F extends { - readonly type: { - readonly kind: 'valueObject'; - readonly name: infer VOName extends string; - }; + readonly type: infer Reference extends ValueObjectReference; readonly nullable: infer Null extends boolean; } - ? ObjectField> & { readonly nullable: Null } + ? ValueObjectKey extends Entered + ? DocField & { readonly nullable: Null } + : ObjectField>> & { + readonly nullable: Null; + } : F extends { readonly type: { readonly kind: 'scalar'; readonly codecId: string }; } ? FieldToLeaf : DocField; +/** A value object's storage coordinate as one string, so the value objects a path has entered form a union. */ +type ValueObjectKey = + `${Reference['namespaceId']}.${Reference['entityName']}`; + /** - * Resolve a named value object from the contract into its own - * `NestedDocShape`. The mapped iteration is inlined here (not delegated - * to a generic helper) so that the homomorphism over - * `VOs[VOName]['fields']` is preserved and the hover / indexed-access - * surface stays concrete at instantiation time. + * Resolve the storage value object a field's reference names into its own + * `NestedDocShape`. A field that refers to a value object the path has already + * entered is an opaque leaf, so a value object that contains itself ends there. The mapped iteration is inlined here (not delegated + * to a generic helper) so that the homomorphism over the value object's + * `fields` is preserved and the hover / indexed-access surface stays + * concrete at instantiation time. */ -type VONestedShape = [ - ContractValueObjectDefinitions, -] extends [infer VOs extends Record }>] - ? VOName extends keyof VOs & string - ? { - readonly [K in keyof VOs[VOName]['fields'] & string]: TranslateField< - TContract, - VOs[VOName]['fields'][K] - >; - } - : never +type VONestedShape< + TContract extends MongoContract, + Reference extends ValueObjectReference, + Entered extends string, +> = [ContractValueObjectAt] extends [ + infer VO extends { readonly fields: Record }, +] + ? { + readonly [K in keyof VO['fields'] & string]: TranslateField< + TContract, + VO['fields'][K], + Entered + >; + } : never; /** * Build the `NestedDocShape` for a model, keyed by stored name. Scalar leaves resolve to their - * concrete codec id; value-object fields recurse into the referenced - * `valueObjects[VOName].fields` table, producing a tree that + * concrete codec id; value-object fields recurse into the fields of the + * storage value object their reference names, producing a tree that * `ResolvePath` / `ValidPaths` can walk. * * The mapped iteration is inlined (not hidden behind a helper type that @@ -182,7 +191,7 @@ export type ModelNestedShape< TContract, ModelName, K - >]: TranslateField[ModelName]['fields'][K]>; + >]: TranslateField[ModelName]['fields'][K], never>; }; // ── Path walking ───────────────────────────────────────────────────────── diff --git a/packages/2-mongo-family/5-query-builders/query-builder/src/result-shape.ts b/packages/2-mongo-family/5-query-builders/query-builder/src/result-shape.ts index ea7fc6a80e6a..304d2e188d5f 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/src/result-shape.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/src/result-shape.ts @@ -1,52 +1,58 @@ -import type { ContractField, ContractModel, ContractValueObject } from '@internal/contract/types'; +import { + type ContractField, + type ContractModel, + type ContractValueObject, + type StorageBase, + valueObjectAt, +} from '@internal/contract/types'; import { type MongoModelStorage, ownStoredNamesOf } from '@internal/mongo-contract'; import type { MongoFieldShape, MongoResultShape } from '@internal/mongo-query-ast/execution'; import { freezeMongoFieldShape, freezeMongoResultShape } from '@internal/mongo-query-ast/execution'; -export type MongoValueObjects = Readonly>; - const UNKNOWN: MongoFieldShape = Object.freeze({ kind: 'unknown' as const }); +const NO_STORAGE: Pick = { namespaces: {} }; + function valueObjectFields( valueObject: ContractValueObject, - valueObjects: MongoValueObjects, - enclosing: ReadonlySet, + storage: Pick, + enclosing: ReadonlySet, ): Record { return Object.fromEntries( Object.entries(valueObject.fields).map(([name, field]) => [ name, - fieldShape(field, valueObjects, enclosing), + fieldShape(field, storage, enclosing), ]), ); } function elementShape( field: ContractField, - valueObjects: MongoValueObjects, - enclosing: ReadonlySet, + storage: Pick, + enclosing: ReadonlySet, ): MongoFieldShape { const { type } = field; const nullable = !!field.many && field.many.elementNullable; if (type.kind === 'scalar') { return { kind: 'leaf', codecId: type.codecId, nullable }; } - if (type.kind !== 'valueObject' || enclosing.has(type.name)) return UNKNOWN; - const valueObject = valueObjects[type.name]; - if (valueObject === undefined) return UNKNOWN; + if (type.kind !== 'valueObject') return UNKNOWN; + const valueObject = valueObjectAt(storage, type); + if (valueObject === undefined || enclosing.has(valueObject)) return UNKNOWN; return { kind: 'document', nullable, - fields: valueObjectFields(valueObject, valueObjects, new Set([...enclosing, type.name])), + fields: valueObjectFields(valueObject, storage, new Set([...enclosing, valueObject])), }; } function fieldShape( field: ContractField, - valueObjects: MongoValueObjects, - enclosing: ReadonlySet, + storage: Pick, + enclosing: ReadonlySet, ): MongoFieldShape { if (field.dict === true) return UNKNOWN; - const element = elementShape(field, valueObjects, enclosing); + const element = elementShape(field, storage, enclosing); if (element.kind === 'unknown') return UNKNOWN; if (field.many) { return { kind: 'array', nullable: field.nullable, element }; @@ -55,13 +61,13 @@ function fieldShape( } /** - * The shape the runtime decodes one field against. A value-object field becomes a document of its own fields when `valueObjects` holds its definition; a value object nested inside itself is left undecoded. + * The shape the runtime decodes one field against. A value-object field becomes a document of its own fields when `storage` holds the value object its reference names; a value object nested inside itself is left undecoded. */ export function contractFieldToMongoFieldShape( field: ContractField, - valueObjects: MongoValueObjects = {}, + storage: Pick = NO_STORAGE, ): MongoFieldShape { - return freezeMongoFieldShape(fieldShape(field, valueObjects, new Set())); + return freezeMongoFieldShape(fieldShape(field, storage, new Set())); } /** @@ -73,7 +79,7 @@ export function contractModelToMongoResultShape( readonly selection?: readonly string[]; /** The shape of each included relation, keyed by relation name. */ readonly includes?: Readonly>; - readonly valueObjects?: MongoValueObjects; + readonly storage?: Pick; }, ): MongoResultShape { const fields: Record = { ...options?.includes }; @@ -96,7 +102,7 @@ export function contractModelToMongoResultShape( fields[key] = UNKNOWN; continue; } - fields[key] = contractFieldToMongoFieldShape(cf, options?.valueObjects); + fields[key] = contractFieldToMongoFieldShape(cf, options?.storage); } return freezeMongoResultShape({ kind: 'document', fields }); } diff --git a/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts b/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts index d6a946de1646..f6e607e57a97 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.ts @@ -11,6 +11,24 @@ import type { } from '@internal/mongo-contract'; import { blindCast } from '@internal/utils/casts'; +type ValueObjectRef = { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: Name; +}; + +function valueObjectRef(entityName: Name): ValueObjectRef { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName, + }; +} + type TestModels = { readonly Order: { readonly fields: { @@ -111,17 +129,17 @@ type TestModels = { readonly many: false; }; readonly address: { - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: ValueObjectRef<'Address'>; readonly nullable: false; readonly many: false; }; readonly workAddress: { - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: ValueObjectRef<'Address'>; readonly nullable: true; readonly many: false; }; readonly stats: { - readonly type: { readonly kind: 'valueObject'; readonly name: 'Stats' }; + readonly type: ValueObjectRef<'Stats'>; readonly nullable: false; readonly many: false; }; @@ -151,7 +169,7 @@ type TestModels = { readonly many: false; }; readonly home: { - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: ValueObjectRef<'Address'>; readonly nullable: true; readonly many: false; }; @@ -187,7 +205,7 @@ type TestValueObjects = { readonly many: false; }; readonly geo: { - readonly type: { readonly kind: 'valueObject'; readonly name: 'GeoPoint' }; + readonly type: ValueObjectRef<'GeoPoint'>; readonly nullable: false; readonly many: false; }; @@ -236,6 +254,7 @@ type TestStorage = { readonly customers: { readonly kind: 'mongo-collection' }; readonly accounts: { readonly kind: 'mongo-collection' }; }; + readonly valueObject: TestValueObjects; }; }; }; @@ -254,7 +273,6 @@ export type TestContract = Omit, 'domain'> & { readonly namespaces: { readonly __unbound__: { readonly models: TestModels; - readonly valueObjects: TestValueObjects; }; }; }; @@ -431,16 +449,16 @@ export const testContractJson = { many: false, }, address: { - type: { kind: 'valueObject', name: 'Address' }, + type: valueObjectRef('Address'), nullable: false, many: false, }, workAddress: { - type: { kind: 'valueObject', name: 'Address' }, + type: valueObjectRef('Address'), nullable: true, many: false, }, - stats: { type: { kind: 'valueObject', name: 'Stats' }, nullable: false, many: false }, + stats: { type: valueObjectRef('Stats'), nullable: false, many: false }, }, relations: {}, storage: { @@ -466,7 +484,7 @@ export const testContractJson = { nullable: false, many: false, }, - home: { type: { kind: 'valueObject', name: 'Address' }, nullable: true, many: false }, + home: { type: valueObjectRef('Address'), nullable: true, many: false }, }, relations: {}, storage: { @@ -479,60 +497,6 @@ export const testContractJson = { }, }, }, - valueObjects: { - Address: { - fields: { - street: { - type: { kind: 'scalar', codecId: 'mongo/string@1' }, - nullable: false, - many: false, - }, - city: { - type: { kind: 'scalar', codecId: 'mongo/string@1' }, - nullable: false, - many: false, - }, - zip: { - type: { kind: 'scalar', codecId: 'mongo/string@1' }, - nullable: true, - many: false, - }, - geo: { - type: { kind: 'valueObject', name: 'GeoPoint' }, - nullable: false, - many: false, - }, - }, - }, - GeoPoint: { - fields: { - lat: { - type: { kind: 'scalar', codecId: 'mongo/double@1' }, - nullable: false, - many: false, - }, - lng: { - type: { kind: 'scalar', codecId: 'mongo/double@1' }, - nullable: false, - many: false, - }, - }, - }, - Stats: { - fields: { - visits: { - type: { kind: 'scalar', codecId: 'mongo/double@1' }, - nullable: false, - many: false, - }, - lastSeen: { - type: { kind: 'scalar', codecId: 'mongo/date@1' }, - nullable: true, - many: false, - }, - }, - }, - }, }, }, }, @@ -548,6 +512,62 @@ export const testContractJson = { customers: { kind: 'mongo-collection' }, accounts: { kind: 'mongo-collection' }, }, + entries: { + valueObject: { + Address: { + fields: { + street: { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + nullable: false, + many: false, + }, + city: { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + nullable: false, + many: false, + }, + zip: { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + nullable: true, + many: false, + }, + geo: { + type: valueObjectRef('GeoPoint'), + nullable: false, + many: false, + }, + }, + }, + GeoPoint: { + fields: { + lat: { + type: { kind: 'scalar', codecId: 'mongo/double@1' }, + nullable: false, + many: false, + }, + lng: { + type: { kind: 'scalar', codecId: 'mongo/double@1' }, + nullable: false, + many: false, + }, + }, + }, + Stats: { + fields: { + visits: { + type: { kind: 'scalar', codecId: 'mongo/double@1' }, + nullable: false, + many: false, + }, + lastSeen: { + type: { kind: 'scalar', codecId: 'mongo/date@1' }, + nullable: true, + many: false, + }, + }, + }, + }, + }, }, }, }, diff --git a/packages/2-mongo-family/5-query-builders/query-builder/test/resolve-path.recursive.test-d.ts b/packages/2-mongo-family/5-query-builders/query-builder/test/resolve-path.recursive.test-d.ts new file mode 100644 index 000000000000..e72f21327eed --- /dev/null +++ b/packages/2-mongo-family/5-query-builders/query-builder/test/resolve-path.recursive.test-d.ts @@ -0,0 +1,80 @@ +import { expectTypeOf } from 'vitest'; +import type { ModelNestedShape, ValidPaths } from '../src/resolve-path'; +import type { TestContract } from './fixtures/test-contract'; + +type NodeRef = { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Node'; +}; + +type RecursiveContract = Omit & { + readonly storage: { + readonly storageHash: TestContract['storage']['storageHash']; + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-namespace'; + readonly entries: { + readonly collection: { readonly trees: { readonly kind: 'mongo-collection' } }; + readonly valueObject: { + readonly Node: { + readonly fields: { + readonly label: { + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + readonly nullable: false; + readonly many: false; + }; + readonly child: { + readonly type: NodeRef; + readonly nullable: true; + readonly many: false; + }; + }; + }; + }; + }; + }; + }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Tree: { + readonly fields: { + readonly _id: { + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + readonly nullable: false; + readonly many: false; + }; + readonly tree: { + readonly type: NodeRef; + readonly nullable: false; + readonly many: false; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'trees'; + readonly fields: { + readonly _id: { readonly field: '_id' }; + readonly tree: { readonly field: 'tree' }; + }; + }; + }; + }; + }; + }; + }; +}; + +describe('a value object that contains itself', () => { + it('ends dot paths at the value object already on the path', () => { + expectTypeOf>>().toEqualTypeOf< + '_id' | 'tree' | 'tree.label' | 'tree.child' + >(); + }); +}); diff --git a/packages/2-mongo-family/5-query-builders/query-builder/test/result-shape.test.ts b/packages/2-mongo-family/5-query-builders/query-builder/test/result-shape.test.ts index e6bb51c324e6..0f9d145340f9 100644 --- a/packages/2-mongo-family/5-query-builders/query-builder/test/result-shape.test.ts +++ b/packages/2-mongo-family/5-query-builders/query-builder/test/result-shape.test.ts @@ -1,3 +1,4 @@ +import type { ContractValueObject } from '@internal/contract/types'; import { MongoFieldFilter, MongoProjectStage } from '@internal/mongo-query-ast/execution'; import { describe, expect, it } from 'vitest'; import { mongoQuery } from '../src/query'; @@ -7,6 +8,25 @@ import { } from '../src/result-shape'; import { testContract } from './fixtures/test-contract'; +function valueObjectRef(entityName: string, namespaceId = '__unbound__') { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName, + } as const; +} + +function storageWith( + valueObjects: Readonly>, + namespaceId = '__unbound__', +) { + return { + namespaces: { [namespaceId]: { id: namespaceId, entries: { valueObject: valueObjects } } }, + }; +} + describe('contractModelToMongoResultShape', () => { it('treats omitted cardinality as scalar', () => { expect( @@ -34,10 +54,10 @@ describe('contractModelToMongoResultShape', () => { }); it('maps a value-object field to a document of its own fields, nested value objects too', () => { - const namespace = testContract.domain.namespaces.__unbound__!; - const shape = contractModelToMongoResultShape(namespace.models['Customer'], { - valueObjects: namespace.valueObjects, - }); + const shape = contractModelToMongoResultShape( + testContract.domain.namespaces.__unbound__!.models['Customer'], + { storage: testContract.storage }, + ); if (shape.kind !== 'document') throw new Error('expected document'); const address = { street: { kind: 'leaf', codecId: 'mongo/string@1', nullable: false }, @@ -60,12 +80,12 @@ describe('contractModelToMongoResultShape', () => { it.each([false, true])('maps value-object lists with elementNullable=%s', (elementNullable) => { const shape = contractFieldToMongoFieldShape( - { type: { kind: 'valueObject', name: 'Point' }, nullable: false, many: { elementNullable } }, - { + { type: valueObjectRef('Point'), nullable: false, many: { elementNullable } }, + storageWith({ Point: { fields: { x: { type: { kind: 'scalar', codecId: 'mongo/double@1' }, nullable: false } }, }, - }, + }), ); expect(shape).toEqual({ kind: 'array', @@ -80,15 +100,15 @@ describe('contractModelToMongoResultShape', () => { it('stops at a value object that contains itself', () => { const shape = contractFieldToMongoFieldShape( - { type: { kind: 'valueObject', name: 'Node' }, nullable: false }, - { + { type: valueObjectRef('Node'), nullable: false }, + storageWith({ Node: { fields: { label: { type: { kind: 'scalar', codecId: 'mongo/string@1' }, nullable: false }, - child: { type: { kind: 'valueObject', name: 'Node' }, nullable: true }, + child: { type: valueObjectRef('Node'), nullable: true }, }, }, - }, + }), ); expect(shape).toEqual({ kind: 'document', @@ -100,7 +120,41 @@ describe('contractModelToMongoResultShape', () => { }); }); - it('maps a value-object field to unknown when no value objects are given', () => { + it('finds a value object in the storage namespace its reference names', () => { + const shape = contractFieldToMongoFieldShape( + { type: valueObjectRef('Point', 'geo'), nullable: false }, + { + namespaces: { + ...storageWith({ Point: { fields: {} } }).namespaces, + ...storageWith( + { + Point: { + fields: { + x: { type: { kind: 'scalar', codecId: 'mongo/double@1' }, nullable: false }, + }, + }, + }, + 'geo', + ).namespaces, + }, + }, + ); + expect(shape).toEqual({ + kind: 'document', + nullable: false, + fields: { x: { kind: 'leaf', codecId: 'mongo/double@1', nullable: false } }, + }); + }); + + it('maps a value-object field to unknown when storage has no such value object', () => { + const shape = contractFieldToMongoFieldShape( + { type: valueObjectRef('Missing'), nullable: false }, + storageWith({}), + ); + expect(shape).toEqual({ kind: 'unknown' }); + }); + + it('maps a value-object field to unknown when no storage is given', () => { const model = testContract.domain.namespaces.__unbound__!.models['Customer']; const shape = contractModelToMongoResultShape(model); if (shape.kind !== 'document') throw new Error('expected document'); @@ -201,6 +255,19 @@ describe('PipelineChain build resultShape', () => { expect(plan.resultShape?.kind).toBe('document'); }); + it('decodes a value-object field through the value object storage holds', () => { + const plan = mongoQuery({ contract: testContract }).from('customers').build(); + if (plan.resultShape?.kind !== 'document') throw new Error('expected document'); + expect(plan.resultShape.fields['stats']).toEqual({ + kind: 'document', + nullable: false, + fields: { + visits: { kind: 'leaf', codecId: 'mongo/double@1', nullable: false }, + lastSeen: { kind: 'leaf', codecId: 'mongo/date@1', nullable: true }, + }, + }); + }); + it('project stage reifies a document shape retaining _id and the projected field', () => { const plan = mongoQuery({ contract: testContract }) .from('orders') diff --git a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts index 5297c7b54cf9..f5adc367b399 100644 --- a/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts +++ b/packages/2-mongo-family/9-family/src/core/ir/mongo-contract-serializer-base.ts @@ -1,5 +1,8 @@ import type { ContractModel } from '@internal/contract/types'; -import { validateContractDomain } from '@internal/contract/validate-domain'; +import { + removedDomainKeyProblems, + validateContractDomain, +} from '@internal/contract/validate-domain'; import { withDerivedToOneRelationNullability } from '@internal/contract-authoring'; import type { ContractSerializer } from '@internal/framework-components/control'; import { @@ -120,11 +123,18 @@ export abstract class MongoContractSerializerBase * `storage.namespaces[namespaceId].entries.collection[collectionName]` (each value is a * `MongoCollection`, with nested `MongoIndex` / `MongoValidator` / * `MongoCollectionOptions` constructed by the `MongoCollection` constructor). - * The rest of the contract envelope (models, valueObjects, capabilities, …) + * The rest of the contract envelope (models, capabilities, …) * remains in plain-JSON form; those IR layers are handled by sibling * subsystems and don't sit behind this SPI. */ protected parseMongoContractStructure(json: unknown): MongoContract { + const removedKeys = removedDomainKeyProblems(json); + if (removedKeys.length > 0) { + throw structuredError( + 'CONTRACT.VALIDATION_FAILED', + `Contract structural validation failed: ${removedKeys.join('; ')}`, + ); + } const schema = this.contractSchema ?? MongoContractSchema; const parsed = schema(json); if (parsed instanceof arktypeType.errors) { diff --git a/packages/2-mongo-family/9-family/test/fixtures/migration-contract.d.ts b/packages/2-mongo-family/9-family/test/fixtures/migration-contract.d.ts index fb0ae579ef53..8af46af010b8 100644 --- a/packages/2-mongo-family/9-family/test/fixtures/migration-contract.d.ts +++ b/packages/2-mongo-family/9-family/test/fixtures/migration-contract.d.ts @@ -1,7 +1,7 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma contract emit'. // To regenerate, run: prisma contract emit -import type { CodecTypes as MongoCodecTypes, Vector } from '@internal/target-mongo/codec-types'; +import type { CodecTypes as MongoCodecTypes } from '../../../../3-targets/mongo/1-target/src/exports/codec-types'; import type { MongoCollection, @@ -12,342 +12,143 @@ import type { import type { Contract as ContractType, ExecutionHashBase, + FieldTypesFromDomain, NamespaceId, ProfileHashBase, StorageHashBase, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; -export type StorageHash = - StorageHashBase<'bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b'>; +export type StorageHash = StorageHashBase<"migration-storage">; export type ExecutionHash = ExecutionHashBase; -export type ProfileHash = - ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; +export type ProfileHash = ProfileHashBase<"migration-profile">; export type CodecTypes = MongoCodecTypes; -export type AddressOutput = { - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; - readonly postalCode: CodecTypes['mongo/string@1']['output']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['output']; -}; -export type AddressInput = { - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; - readonly postalCode: CodecTypes['mongo/string@1']['input']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['input']; -}; -export type CartItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly productId: CodecTypes['mongo/string@1']['output']; -}; -export type CartItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly productId: CodecTypes['mongo/string@1']['input']; -}; -export type ImageOutput = { readonly url: CodecTypes['mongo/string@1']['output'] }; -export type ImageInput = { readonly url: CodecTypes['mongo/string@1']['input'] }; -export type InvoiceLineItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly lineTotal: CodecTypes['mongo/double@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly unitPrice: CodecTypes['mongo/double@1']['output']; -}; -export type InvoiceLineItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly lineTotal: CodecTypes['mongo/double@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly unitPrice: CodecTypes['mongo/double@1']['input']; -}; -export type OrderLineItemOutput = { - readonly amount: CodecTypes['mongo/int32@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly productId: CodecTypes['mongo/string@1']['output']; -}; -export type OrderLineItemInput = { - readonly amount: CodecTypes['mongo/int32@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly productId: CodecTypes['mongo/string@1']['input']; -}; -export type PriceOutput = { - readonly amount: CodecTypes['mongo/double@1']['output']; - readonly currency: CodecTypes['mongo/string@1']['output']; -}; -export type PriceInput = { - readonly amount: CodecTypes['mongo/double@1']['input']; - readonly currency: CodecTypes['mongo/string@1']['input']; -}; -export type StatusEntryOutput = { - readonly status: CodecTypes['mongo/string@1']['output']; - readonly timestamp: CodecTypes['mongo/date@1']['output']; -}; -export type StatusEntryInput = { - readonly status: CodecTypes['mongo/string@1']['input']; - readonly timestamp: CodecTypes['mongo/date@1']['input']; -}; -export type FieldOutputTypes = { - readonly __unbound__: { - readonly AddToCartEvent: { - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly productId: CodecTypes['mongo/string@1']['output']; - }; - readonly Cart: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly userId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly Event: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly sessionId: CodecTypes['mongo/string@1']['output']; - readonly timestamp: CodecTypes['mongo/date@1']['output']; - readonly type: CodecTypes['mongo/string@1']['output']; - readonly userId: CodecTypes['mongo/string@1']['output']; - }; - readonly Invoice: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly issuedAt: CodecTypes['mongo/date@1']['output']; - readonly items: ReadonlyArray; - readonly orderId: CodecTypes['mongo/objectId@1']['output']; - readonly subtotal: CodecTypes['mongo/double@1']['output']; - readonly tax: CodecTypes['mongo/double@1']['output']; - readonly total: CodecTypes['mongo/double@1']['output']; - }; - readonly Location: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly postalCode: CodecTypes['mongo/string@1']['output']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['output']; - }; - readonly Order: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly shippingAddress: CodecTypes['mongo/string@1']['output']; - readonly statusHistory: ReadonlyArray; - readonly type: 'delivery' | 'pickup'; - readonly userId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly Product: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly articleType: CodecTypes['mongo/string@1']['output']; - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly code: CodecTypes['mongo/string@1']['output']; - readonly description: CodecTypes['mongo/string@1']['output']; - readonly embedding: ReadonlyArray | null; - readonly image: ImageOutput; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly price: PriceOutput; - readonly primaryCategory: CodecTypes['mongo/string@1']['output']; - readonly status: 'active' | 'discontinued' | 'out-of-stock'; - readonly subCategory: CodecTypes['mongo/string@1']['output']; - }; - readonly SearchEvent: { readonly query: CodecTypes['mongo/string@1']['output'] }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly address: AddressOutput | null; - readonly email: CodecTypes['mongo/string@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - }; - readonly ViewProductEvent: { - readonly brand: CodecTypes['mongo/string@1']['output']; - readonly exitMethod: CodecTypes['mongo/string@1']['output'] | null; - readonly productId: CodecTypes['mongo/string@1']['output']; - readonly subCategory: CodecTypes['mongo/string@1']['output']; - }; - }; -}; -export type FieldInputTypes = { - readonly __unbound__: { - readonly AddToCartEvent: { - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly productId: CodecTypes['mongo/string@1']['input']; - }; - readonly Cart: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly userId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly Event: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly sessionId: CodecTypes['mongo/string@1']['input']; - readonly timestamp: CodecTypes['mongo/date@1']['input']; - readonly type: CodecTypes['mongo/string@1']['input']; - readonly userId: CodecTypes['mongo/string@1']['input']; - }; - readonly Invoice: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly issuedAt: CodecTypes['mongo/date@1']['input']; - readonly items: ReadonlyArray; - readonly orderId: CodecTypes['mongo/objectId@1']['input']; - readonly subtotal: CodecTypes['mongo/double@1']['input']; - readonly tax: CodecTypes['mongo/double@1']['input']; - readonly total: CodecTypes['mongo/double@1']['input']; - }; - readonly Location: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly postalCode: CodecTypes['mongo/string@1']['input']; - readonly streetAndNumber: CodecTypes['mongo/string@1']['input']; - }; - readonly Order: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly shippingAddress: CodecTypes['mongo/string@1']['input']; - readonly statusHistory: ReadonlyArray; - readonly type: 'delivery' | 'pickup'; - readonly userId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly Product: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly articleType: CodecTypes['mongo/string@1']['input']; - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly code: CodecTypes['mongo/string@1']['input']; - readonly description: CodecTypes['mongo/string@1']['input']; - readonly embedding: ReadonlyArray | null; - readonly image: ImageInput; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly price: PriceInput; - readonly primaryCategory: CodecTypes['mongo/string@1']['input']; - readonly status: 'active' | 'discontinued' | 'out-of-stock'; - readonly subCategory: CodecTypes['mongo/string@1']['input']; - }; - readonly SearchEvent: { readonly query: CodecTypes['mongo/string@1']['input'] }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly address: AddressInput | null; - readonly email: CodecTypes['mongo/string@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - }; - readonly ViewProductEvent: { - readonly brand: CodecTypes['mongo/string@1']['input']; - readonly exitMethod: CodecTypes['mongo/string@1']['input'] | null; - readonly productId: CodecTypes['mongo/string@1']['input']; - readonly subCategory: CodecTypes['mongo/string@1']['input']; - }; - }; -}; +type ParameterizedOutputTypeFunctions = never; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage; +type ValueObjectInputTypes = ValueObjectTypesFromStorage; +export type AddressOutput = ValueObjectOutputTypes["__unbound__"]["Address"]; +export type AddressInput = ValueObjectInputTypes["__unbound__"]["Address"]; +export type CartItemOutput = ValueObjectOutputTypes["__unbound__"]["CartItem"]; +export type CartItemInput = ValueObjectInputTypes["__unbound__"]["CartItem"]; +export type ImageOutput = ValueObjectOutputTypes["__unbound__"]["Image"]; +export type ImageInput = ValueObjectInputTypes["__unbound__"]["Image"]; +export type InvoiceLineItemOutput = ValueObjectOutputTypes["__unbound__"]["InvoiceLineItem"]; +export type InvoiceLineItemInput = ValueObjectInputTypes["__unbound__"]["InvoiceLineItem"]; +export type OrderLineItemOutput = ValueObjectOutputTypes["__unbound__"]["OrderLineItem"]; +export type OrderLineItemInput = ValueObjectInputTypes["__unbound__"]["OrderLineItem"]; +export type PriceOutput = ValueObjectOutputTypes["__unbound__"]["Price"]; +export type PriceInput = ValueObjectInputTypes["__unbound__"]["Price"]; +export type StatusEntryOutput = ValueObjectOutputTypes["__unbound__"]["StatusEntry"]; +export type StatusEntryInput = ValueObjectInputTypes["__unbound__"]["StatusEntry"]; +type FieldTypeOverrides = { }; +export type FieldOutputTypes = FieldTypesFromDomain; +export type FieldInputTypes = FieldTypesFromDomain; export namespace Models { export type unbound_AddToCartEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'add-to-cart'; - userId: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - productId: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Event"]["_id"]; + sessionId: FieldOutputTypes["__unbound__"]["Event"]["sessionId"]; + timestamp: FieldOutputTypes["__unbound__"]["Event"]["timestamp"]; + type: "add-to-cart"; + userId: FieldOutputTypes["__unbound__"]["Event"]["userId"]; + brand: FieldOutputTypes["__unbound__"]["AddToCartEvent"]["brand"]; + productId: FieldOutputTypes["__unbound__"]["AddToCartEvent"]["productId"]; readonly [RelationKeys]?: never; }; export type unbound_Cart = { - _id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - userId: CodecTypes['mongo/objectId@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Cart"]["_id"]; + items: FieldOutputTypes["__unbound__"]["Cart"]["items"]; + userId: FieldOutputTypes["__unbound__"]["Cart"]["userId"]; user: unbound_User; - readonly [RelationKeys]?: 'user'; + readonly [RelationKeys]?: "user"; }; export type unbound_Event = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'add-to-cart' | 'search' | 'view-product'; - userId: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Event"]["_id"]; + sessionId: FieldOutputTypes["__unbound__"]["Event"]["sessionId"]; + timestamp: FieldOutputTypes["__unbound__"]["Event"]["timestamp"]; + type: "add-to-cart" | "search" | "view-product"; + userId: FieldOutputTypes["__unbound__"]["Event"]["userId"]; readonly [RelationKeys]?: never; }; export type unbound_Invoice = { - _id: CodecTypes['mongo/objectId@1']['output']; - issuedAt: CodecTypes['mongo/date@1']['output']; - items: ReadonlyArray; - orderId: CodecTypes['mongo/objectId@1']['output']; - subtotal: CodecTypes['mongo/double@1']['output']; - tax: CodecTypes['mongo/double@1']['output']; - total: CodecTypes['mongo/double@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Invoice"]["_id"]; + issuedAt: FieldOutputTypes["__unbound__"]["Invoice"]["issuedAt"]; + items: FieldOutputTypes["__unbound__"]["Invoice"]["items"]; + orderId: FieldOutputTypes["__unbound__"]["Invoice"]["orderId"]; + subtotal: FieldOutputTypes["__unbound__"]["Invoice"]["subtotal"]; + tax: FieldOutputTypes["__unbound__"]["Invoice"]["tax"]; + total: FieldOutputTypes["__unbound__"]["Invoice"]["total"]; order: unbound_Order; - readonly [RelationKeys]?: 'order'; + readonly [RelationKeys]?: "order"; }; export type unbound_Location = { - _id: CodecTypes['mongo/objectId@1']['output']; - city: CodecTypes['mongo/string@1']['output']; - country: CodecTypes['mongo/string@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - postalCode: CodecTypes['mongo/string@1']['output']; - streetAndNumber: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Location"]["_id"]; + city: FieldOutputTypes["__unbound__"]["Location"]["city"]; + country: FieldOutputTypes["__unbound__"]["Location"]["country"]; + name: FieldOutputTypes["__unbound__"]["Location"]["name"]; + postalCode: FieldOutputTypes["__unbound__"]["Location"]["postalCode"]; + streetAndNumber: FieldOutputTypes["__unbound__"]["Location"]["streetAndNumber"]; readonly [RelationKeys]?: never; }; export type unbound_Order = { - _id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - shippingAddress: CodecTypes['mongo/string@1']['output']; - statusHistory: ReadonlyArray; - type: 'delivery' | 'pickup'; - userId: CodecTypes['mongo/objectId@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Order"]["_id"]; + items: FieldOutputTypes["__unbound__"]["Order"]["items"]; + shippingAddress: FieldOutputTypes["__unbound__"]["Order"]["shippingAddress"]; + statusHistory: FieldOutputTypes["__unbound__"]["Order"]["statusHistory"]; + type: FieldOutputTypes["__unbound__"]["Order"]["type"]; + userId: FieldOutputTypes["__unbound__"]["Order"]["userId"]; invoices: unbound_Invoice[]; user: unbound_User; - readonly [RelationKeys]?: 'invoices' | 'user'; + readonly [RelationKeys]?: "invoices" | "user"; }; export type unbound_Product = { - _id: CodecTypes['mongo/objectId@1']['output']; - articleType: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - code: CodecTypes['mongo/string@1']['output']; - description: CodecTypes['mongo/string@1']['output']; - embedding: ReadonlyArray | null; - image: ImageOutput; - name: CodecTypes['mongo/string@1']['output']; - price: PriceOutput; - primaryCategory: CodecTypes['mongo/string@1']['output']; - status: 'active' | 'discontinued' | 'out-of-stock'; - subCategory: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Product"]["_id"]; + articleType: FieldOutputTypes["__unbound__"]["Product"]["articleType"]; + brand: FieldOutputTypes["__unbound__"]["Product"]["brand"]; + code: FieldOutputTypes["__unbound__"]["Product"]["code"]; + description: FieldOutputTypes["__unbound__"]["Product"]["description"]; + embedding: FieldOutputTypes["__unbound__"]["Product"]["embedding"]; + image: FieldOutputTypes["__unbound__"]["Product"]["image"]; + name: FieldOutputTypes["__unbound__"]["Product"]["name"]; + price: FieldOutputTypes["__unbound__"]["Product"]["price"]; + primaryCategory: FieldOutputTypes["__unbound__"]["Product"]["primaryCategory"]; + status: FieldOutputTypes["__unbound__"]["Product"]["status"]; + subCategory: FieldOutputTypes["__unbound__"]["Product"]["subCategory"]; readonly [RelationKeys]?: never; }; export type unbound_SearchEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'search'; - userId: CodecTypes['mongo/string@1']['output']; - query: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Event"]["_id"]; + sessionId: FieldOutputTypes["__unbound__"]["Event"]["sessionId"]; + timestamp: FieldOutputTypes["__unbound__"]["Event"]["timestamp"]; + type: "search"; + userId: FieldOutputTypes["__unbound__"]["Event"]["userId"]; + query: FieldOutputTypes["__unbound__"]["SearchEvent"]["query"]; readonly [RelationKeys]?: never; }; export type unbound_User = { - _id: CodecTypes['mongo/objectId@1']['output']; - address: AddressOutput | null; - email: CodecTypes['mongo/string@1']['output']; - name: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["User"]["_id"]; + address: FieldOutputTypes["__unbound__"]["User"]["address"]; + email: FieldOutputTypes["__unbound__"]["User"]["email"]; + name: FieldOutputTypes["__unbound__"]["User"]["name"]; carts: unbound_Cart[]; orders: unbound_Order[]; - readonly [RelationKeys]?: 'carts' | 'orders'; + readonly [RelationKeys]?: "carts" | "orders"; }; export type unbound_ViewProductEvent = { - _id: CodecTypes['mongo/objectId@1']['output']; - sessionId: CodecTypes['mongo/string@1']['output']; - timestamp: CodecTypes['mongo/date@1']['output']; - type: 'view-product'; - userId: CodecTypes['mongo/string@1']['output']; - brand: CodecTypes['mongo/string@1']['output']; - exitMethod: CodecTypes['mongo/string@1']['output'] | null; - productId: CodecTypes['mongo/string@1']['output']; - subCategory: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Event"]["_id"]; + sessionId: FieldOutputTypes["__unbound__"]["Event"]["sessionId"]; + timestamp: FieldOutputTypes["__unbound__"]["Event"]["timestamp"]; + type: "view-product"; + userId: FieldOutputTypes["__unbound__"]["Event"]["userId"]; + brand: FieldOutputTypes["__unbound__"]["ViewProductEvent"]["brand"]; + exitMethod: FieldOutputTypes["__unbound__"]["ViewProductEvent"]["exitMethod"]; + productId: FieldOutputTypes["__unbound__"]["ViewProductEvent"]["productId"]; + subCategory: FieldOutputTypes["__unbound__"]["ViewProductEvent"]["subCategory"]; readonly [RelationKeys]?: never; }; - export type unbound_AnyEvent = - unbound_AddToCartEvent | unbound_SearchEvent | unbound_ViewProductEvent; + export type unbound_AnyEvent = unbound_AddToCartEvent | unbound_SearchEvent | unbound_ViewProductEvent; } export declare const models: { @@ -369,1138 +170,24 @@ export declare const models: { export type TypeMaps = MongoTypeMaps; type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-database'; - readonly entries: { - readonly collection: { - readonly carts: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'userId' }]; - readonly kind: 'mongo-index'; - readonly unique: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly brand: { readonly bsonType: 'string' }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly productId: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'amount', - 'brand', - 'image', - 'name', - 'price', - 'productId', - ]; - }; - }; - readonly userId: { readonly bsonType: 'objectId' }; - }; - readonly required: readonly ['_id', 'items', 'userId']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly events: { - readonly indexes: readonly [ - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'userId' }, - { readonly direction: -1; readonly field: 'timestamp' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly expireAfterSeconds: 7776000; - readonly keys: readonly [{ readonly direction: 1; readonly field: 'timestamp' }]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly bsonType: 'object'; - readonly oneOf: readonly [ - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly brand: { readonly bsonType: 'string' }; - readonly exitMethod: { readonly bsonType: readonly ['null', 'string'] }; - readonly productId: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly subCategory: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['view-product'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['brand', 'productId', 'subCategory', 'type']; - }, - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly query: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['search'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['query', 'type']; - }, - { - readonly additionalProperties: false; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly brand: { readonly bsonType: 'string' }; - readonly productId: { readonly bsonType: 'string' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly enum: readonly ['add-to-cart'] }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['brand', 'productId', 'type']; - }, - ]; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly sessionId: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - readonly type: { readonly bsonType: 'string' }; - readonly userId: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['_id', 'sessionId', 'timestamp', 'type', 'userId']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly invoices: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'orderId' }]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [{ readonly direction: -1; readonly field: 'issuedAt' }]; - readonly kind: 'mongo-index'; - readonly sparse: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly issuedAt: { readonly bsonType: 'date' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly lineTotal: { readonly bsonType: 'double' }; - readonly name: { readonly bsonType: 'string' }; - readonly unitPrice: { readonly bsonType: 'double' }; - }; - readonly required: readonly ['amount', 'lineTotal', 'name', 'unitPrice']; - }; - }; - readonly orderId: { readonly bsonType: 'objectId' }; - readonly subtotal: { readonly bsonType: 'double' }; - readonly tax: { readonly bsonType: 'double' }; - readonly total: { readonly bsonType: 'double' }; - }; - readonly required: readonly [ - '_id', - 'issuedAt', - 'items', - 'orderId', - 'subtotal', - 'tax', - 'total', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly locations: { - readonly indexes: readonly [ - { - readonly collation: { readonly locale: 'en'; readonly strength: 2 }; - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'city' }, - { readonly direction: 1; readonly field: 'country' }, - ]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly city: { readonly bsonType: 'string' }; - readonly country: { readonly bsonType: 'string' }; - readonly name: { readonly bsonType: 'string' }; - readonly postalCode: { readonly bsonType: 'string' }; - readonly streetAndNumber: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - '_id', - 'city', - 'country', - 'name', - 'postalCode', - 'streetAndNumber', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly orders: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'userId' }]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly items: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'int' }; - readonly brand: { readonly bsonType: 'string' }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly productId: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'amount', - 'brand', - 'image', - 'name', - 'price', - 'productId', - ]; - }; - }; - readonly shippingAddress: { readonly bsonType: 'string' }; - readonly statusHistory: { - readonly bsonType: 'array'; - readonly items: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly status: { readonly bsonType: 'string' }; - readonly timestamp: { readonly bsonType: 'date' }; - }; - readonly required: readonly ['status', 'timestamp']; - }; - }; - readonly type: { - readonly bsonType: 'string'; - readonly enum: readonly ['delivery', 'pickup']; - }; - readonly userId: { readonly bsonType: 'objectId' }; - }; - readonly required: readonly [ - '_id', - 'items', - 'shippingAddress', - 'statusHistory', - 'type', - 'userId', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly products: { - readonly indexes: readonly [ - { - readonly keys: readonly [ - { readonly direction: 'text'; readonly field: 'name' }, - { readonly direction: 'text'; readonly field: 'description' }, - ]; - readonly kind: 'mongo-index'; - readonly weights: { readonly description: 1; readonly name: 10 }; - }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'brand' }, - { readonly direction: 1; readonly field: 'subCategory' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [ - { readonly direction: 1; readonly field: 'primaryCategory' }, - { readonly direction: 1; readonly field: 'articleType' }, - ]; - readonly kind: 'mongo-index'; - }, - { - readonly keys: readonly [ - { readonly direction: 'hashed'; readonly field: 'code' }, - ]; - readonly kind: 'mongo-index'; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly articleType: { readonly bsonType: 'string' }; - readonly brand: { readonly bsonType: 'string' }; - readonly code: { readonly bsonType: 'string' }; - readonly description: { readonly bsonType: 'string' }; - readonly embedding: { - readonly bsonType: 'array'; - readonly items: { readonly bsonType: 'double' }; - }; - readonly image: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { readonly url: { readonly bsonType: 'string' } }; - readonly required: readonly ['url']; - }; - readonly name: { readonly bsonType: 'string' }; - readonly price: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly amount: { readonly bsonType: 'double' }; - readonly currency: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['amount', 'currency']; - }; - readonly primaryCategory: { readonly bsonType: 'string' }; - readonly status: { - readonly bsonType: 'string'; - readonly enum: readonly ['active', 'discontinued', 'out-of-stock']; - }; - readonly subCategory: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - '_id', - 'articleType', - 'brand', - 'code', - 'description', - 'image', - 'name', - 'price', - 'primaryCategory', - 'status', - 'subCategory', - ]; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - readonly users: { - readonly indexes: readonly [ - { - readonly keys: readonly [{ readonly direction: 1; readonly field: 'email' }]; - readonly kind: 'mongo-index'; - readonly unique: true; - }, - ]; - readonly kind: 'mongo-collection'; - readonly validator: { - readonly jsonSchema: { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly _id: { readonly bsonType: 'objectId' }; - readonly address: { - readonly oneOf: readonly [ - { readonly bsonType: 'null' }, - { - readonly additionalProperties: false; - readonly bsonType: 'object'; - readonly properties: { - readonly city: { readonly bsonType: 'string' }; - readonly country: { readonly bsonType: 'string' }; - readonly postalCode: { readonly bsonType: 'string' }; - readonly streetAndNumber: { readonly bsonType: 'string' }; - }; - readonly required: readonly [ - 'city', - 'country', - 'postalCode', - 'streetAndNumber', - ]; - }, - ]; - }; - readonly email: { readonly bsonType: 'string' }; - readonly name: { readonly bsonType: 'string' }; - }; - readonly required: readonly ['_id', 'email', 'name']; - }; - readonly kind: 'mongo-validator'; - readonly validationAction: 'error'; - readonly validationLevel: 'strict'; - }; - }; - }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, + ContractType<{ readonly namespaces: { readonly __unbound__: { readonly id: "__unbound__"; readonly kind: "mongo-database"; readonly entries: { readonly collection: { readonly carts: { readonly indexes: readonly [{ readonly keys: readonly [{ readonly direction: 1; readonly field: "userId" }]; readonly kind: "mongo-index"; readonly unique: true }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly items: { readonly bsonType: "array"; readonly items: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly amount: { readonly bsonType: "int" }; readonly brand: { readonly bsonType: "string" }; readonly image: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly url: { readonly bsonType: "string" } }; readonly required: readonly ["url"] }; readonly name: { readonly bsonType: "string" }; readonly price: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly amount: { readonly bsonType: "double" }; readonly currency: { readonly bsonType: "string" } }; readonly required: readonly ["amount", "currency"] }; readonly productId: { readonly bsonType: "string" } }; readonly required: readonly ["amount", "brand", "image", "name", "price", "productId"] } }; readonly userId: { readonly bsonType: "objectId" } }; readonly required: readonly ["_id", "items", "userId"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } }; readonly events: { readonly indexes: readonly [{ readonly keys: readonly [{ readonly direction: 1; readonly field: "userId" }, { readonly direction: -1; readonly field: "timestamp" }]; readonly kind: "mongo-index" }, { readonly expireAfterSeconds: 7776000; readonly keys: readonly [{ readonly direction: 1; readonly field: "timestamp" }]; readonly kind: "mongo-index" }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly bsonType: "object"; readonly oneOf: readonly [{ readonly additionalProperties: false; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly brand: { readonly bsonType: "string" }; readonly exitMethod: { readonly bsonType: readonly ["null", "string"] }; readonly productId: { readonly bsonType: "string" }; readonly sessionId: { readonly bsonType: "string" }; readonly subCategory: { readonly bsonType: "string" }; readonly timestamp: { readonly bsonType: "date" }; readonly type: { readonly enum: readonly ["view-product"] }; readonly userId: { readonly bsonType: "string" } }; readonly required: readonly ["brand", "productId", "subCategory", "type"] }, { readonly additionalProperties: false; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly query: { readonly bsonType: "string" }; readonly sessionId: { readonly bsonType: "string" }; readonly timestamp: { readonly bsonType: "date" }; readonly type: { readonly enum: readonly ["search"] }; readonly userId: { readonly bsonType: "string" } }; readonly required: readonly ["query", "type"] }, { readonly additionalProperties: false; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly brand: { readonly bsonType: "string" }; readonly productId: { readonly bsonType: "string" }; readonly sessionId: { readonly bsonType: "string" }; readonly timestamp: { readonly bsonType: "date" }; readonly type: { readonly enum: readonly ["add-to-cart"] }; readonly userId: { readonly bsonType: "string" } }; readonly required: readonly ["brand", "productId", "type"] }]; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly sessionId: { readonly bsonType: "string" }; readonly timestamp: { readonly bsonType: "date" }; readonly type: { readonly bsonType: "string" }; readonly userId: { readonly bsonType: "string" } }; readonly required: readonly ["_id", "sessionId", "timestamp", "type", "userId"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } }; readonly invoices: { readonly indexes: readonly [{ readonly keys: readonly [{ readonly direction: 1; readonly field: "orderId" }]; readonly kind: "mongo-index" }, { readonly keys: readonly [{ readonly direction: -1; readonly field: "issuedAt" }]; readonly kind: "mongo-index"; readonly sparse: true }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly issuedAt: { readonly bsonType: "date" }; readonly items: { readonly bsonType: "array"; readonly items: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly amount: { readonly bsonType: "int" }; readonly lineTotal: { readonly bsonType: "double" }; readonly name: { readonly bsonType: "string" }; readonly unitPrice: { readonly bsonType: "double" } }; readonly required: readonly ["amount", "lineTotal", "name", "unitPrice"] } }; readonly orderId: { readonly bsonType: "objectId" }; readonly subtotal: { readonly bsonType: "double" }; readonly tax: { readonly bsonType: "double" }; readonly total: { readonly bsonType: "double" } }; readonly required: readonly ["_id", "issuedAt", "items", "orderId", "subtotal", "tax", "total"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } }; readonly locations: { readonly indexes: readonly [{ readonly collation: { readonly locale: "en"; readonly strength: 2 }; readonly keys: readonly [{ readonly direction: 1; readonly field: "city" }, { readonly direction: 1; readonly field: "country" }]; readonly kind: "mongo-index" }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly city: { readonly bsonType: "string" }; readonly country: { readonly bsonType: "string" }; readonly name: { readonly bsonType: "string" }; readonly postalCode: { readonly bsonType: "string" }; readonly streetAndNumber: { readonly bsonType: "string" } }; readonly required: readonly ["_id", "city", "country", "name", "postalCode", "streetAndNumber"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } }; readonly orders: { readonly indexes: readonly [{ readonly keys: readonly [{ readonly direction: 1; readonly field: "userId" }]; readonly kind: "mongo-index" }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly items: { readonly bsonType: "array"; readonly items: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly amount: { readonly bsonType: "int" }; readonly brand: { readonly bsonType: "string" }; readonly image: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly url: { readonly bsonType: "string" } }; readonly required: readonly ["url"] }; readonly name: { readonly bsonType: "string" }; readonly price: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly amount: { readonly bsonType: "double" }; readonly currency: { readonly bsonType: "string" } }; readonly required: readonly ["amount", "currency"] }; readonly productId: { readonly bsonType: "string" } }; readonly required: readonly ["amount", "brand", "image", "name", "price", "productId"] } }; readonly shippingAddress: { readonly bsonType: "string" }; readonly statusHistory: { readonly bsonType: "array"; readonly items: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly status: { readonly bsonType: "string" }; readonly timestamp: { readonly bsonType: "date" } }; readonly required: readonly ["status", "timestamp"] } }; readonly type: { readonly bsonType: "string"; readonly enum: readonly ["delivery", "pickup"] }; readonly userId: { readonly bsonType: "objectId" } }; readonly required: readonly ["_id", "items", "shippingAddress", "statusHistory", "type", "userId"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } }; readonly products: { readonly indexes: readonly [{ readonly keys: readonly [{ readonly direction: "text"; readonly field: "name" }, { readonly direction: "text"; readonly field: "description" }]; readonly kind: "mongo-index"; readonly weights: { readonly description: 1; readonly name: 10 } }, { readonly keys: readonly [{ readonly direction: 1; readonly field: "brand" }, { readonly direction: 1; readonly field: "subCategory" }]; readonly kind: "mongo-index" }, { readonly keys: readonly [{ readonly direction: 1; readonly field: "primaryCategory" }, { readonly direction: 1; readonly field: "articleType" }]; readonly kind: "mongo-index" }, { readonly keys: readonly [{ readonly direction: "hashed"; readonly field: "code" }]; readonly kind: "mongo-index" }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly articleType: { readonly bsonType: "string" }; readonly brand: { readonly bsonType: "string" }; readonly code: { readonly bsonType: "string" }; readonly description: { readonly bsonType: "string" }; readonly embedding: { readonly bsonType: "array"; readonly items: { readonly bsonType: "double" } }; readonly image: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly url: { readonly bsonType: "string" } }; readonly required: readonly ["url"] }; readonly name: { readonly bsonType: "string" }; readonly price: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly amount: { readonly bsonType: "double" }; readonly currency: { readonly bsonType: "string" } }; readonly required: readonly ["amount", "currency"] }; readonly primaryCategory: { readonly bsonType: "string" }; readonly status: { readonly bsonType: "string"; readonly enum: readonly ["active", "discontinued", "out-of-stock"] }; readonly subCategory: { readonly bsonType: "string" } }; readonly required: readonly ["_id", "articleType", "brand", "code", "description", "image", "name", "price", "primaryCategory", "status", "subCategory"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } }; readonly users: { readonly indexes: readonly [{ readonly keys: readonly [{ readonly direction: 1; readonly field: "email" }]; readonly kind: "mongo-index"; readonly unique: true }]; readonly kind: "mongo-collection"; readonly validator: { readonly jsonSchema: { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly _id: { readonly bsonType: "objectId" }; readonly address: { readonly oneOf: readonly [{ readonly bsonType: "null" }, { readonly additionalProperties: false; readonly bsonType: "object"; readonly properties: { readonly city: { readonly bsonType: "string" }; readonly country: { readonly bsonType: "string" }; readonly postalCode: { readonly bsonType: "string" }; readonly streetAndNumber: { readonly bsonType: "string" } }; readonly required: readonly ["city", "country", "postalCode", "streetAndNumber"] }] }; readonly email: { readonly bsonType: "string" }; readonly name: { readonly bsonType: "string" } }; readonly required: readonly ["_id", "email", "name"] }; readonly kind: "mongo-validator"; readonly validationAction: "error"; readonly validationLevel: "strict" } } }; readonly valueObject: { readonly Address: { readonly fields: { readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly country: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly postalCode: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly streetAndNumber: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } }; readonly CartItem: { readonly fields: { readonly amount: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/int32@1" } }; readonly brand: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly image: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Image" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly price: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Price" } }; readonly productId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } }; readonly Image: { readonly fields: { readonly url: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } }; readonly InvoiceLineItem: { readonly fields: { readonly amount: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/int32@1" } }; readonly lineTotal: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly unitPrice: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" } } } }; readonly OrderLineItem: { readonly fields: { readonly amount: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/int32@1" } }; readonly brand: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly image: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Image" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly price: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Price" } }; readonly productId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } }; readonly Price: { readonly fields: { readonly amount: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" } }; readonly currency: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } }; readonly StatusEntry: { readonly fields: { readonly status: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly timestamp: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } } } } } } } }; readonly storageHash: StorageHash }>, 'roots' | 'domain' > & { - readonly target: 'mongo'; - readonly targetFamily: 'mongo'; - readonly roots: { - readonly carts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Cart' }; - readonly events: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Event' }; - readonly invoices: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Invoice'; - }; - readonly locations: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Location'; - }; - readonly orders: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Order' }; - readonly products: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Product'; - }; - readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; - }; + readonly target: "mongo"; + readonly targetFamily: "mongo"; + readonly roots: { readonly carts: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Cart" }; readonly events: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Event" }; readonly invoices: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Invoice" }; readonly locations: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Location" }; readonly orders: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Order" }; readonly products: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Product" }; readonly users: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" } }; readonly domain: { readonly namespaces: { readonly __unbound__: { - readonly models: { - readonly AddToCartEvent: { - readonly fields: { - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - readonly Cart: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CartItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly user: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'carts' }; - }; - readonly Event: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly sessionId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly 'add-to-cart': { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'AddToCartEvent'; - }; - readonly search: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'SearchEvent'; - }; - readonly 'view-product': { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'ViewProductEvent'; - }; - }; - }; - readonly Invoice: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly issuedAt: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'InvoiceLineItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly orderId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly subtotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly tax: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly total: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - readonly relations: { - readonly order: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Order'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['orderId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'invoices' }; - }; - readonly Location: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'locations' }; - }; - readonly Order: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly items: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'OrderLineItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly shippingAddress: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly statusHistory: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'StatusEntry' }; - readonly many: { readonly elementNullable: false }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly invoices: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Invoice'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['orderId']; - }; - }; - readonly user: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['userId']; - readonly targetFields: readonly ['_id']; - }; - }; - }; - readonly storage: { readonly collection: 'orders' }; - }; - readonly Product: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly articleType: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly code: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly description: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly embedding: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly primaryCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly subCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'products' }; - }; - readonly SearchEvent: { - readonly fields: { - readonly query: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - readonly User: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly address: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: { - readonly carts: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Cart'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['userId']; - }; - }; - readonly orders: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Order'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['userId']; - }; - }; - }; - readonly storage: { readonly collection: 'users' }; - }; - readonly ViewProductEvent: { - readonly fields: { - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly exitMethod: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly subCategory: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'events' }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Event'; - }; - }; - }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - }; - }; - readonly enum: { - readonly OrderType: { - readonly codecId: 'mongo/string@1'; - readonly members: readonly [ - { readonly name: 'Delivery'; readonly value: 'delivery' }, - { readonly name: 'Pickup'; readonly value: 'pickup' }, - ]; - }; - readonly ProductStatus: { - readonly codecId: 'mongo/string@1'; - readonly members: readonly [ - { readonly name: 'Active'; readonly value: 'active' }, - { readonly name: 'Discontinued'; readonly value: 'discontinued' }, - { readonly name: 'OutOfStock'; readonly value: 'out-of-stock' }, - ]; - }; - }; - }; - }; - }; - readonly capabilities: {}; - readonly extensions: {}; - readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly postalCode: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly streetAndNumber: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly CartItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Image: { - readonly fields: { - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly InvoiceLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly lineTotal: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly unitPrice: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - readonly OrderLineItem: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly brand: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly image: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Image' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly price: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Price' }; - }; - readonly productId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Price: { - readonly fields: { - readonly amount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly currency: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly StatusEntry: { - readonly fields: { - readonly status: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly timestamp: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; + readonly models: { readonly AddToCartEvent: { readonly fields: { readonly brand: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly productId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "events"; readonly fields: { readonly brand: { readonly field: "brand" }; readonly productId: { readonly field: "productId" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Event" } }; readonly Cart: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly items: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "CartItem" }; readonly many: { readonly elementNullable: false } }; readonly userId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } } }; readonly relations: { readonly user: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["userId"]; readonly targetFields: readonly ["_id"] } } }; readonly storage: { readonly collection: "carts"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly items: { readonly field: "items" }; readonly userId: { readonly field: "userId" } } } }; readonly Event: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly sessionId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly timestamp: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } }; readonly type: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly userId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "events"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly sessionId: { readonly field: "sessionId" }; readonly timestamp: { readonly field: "timestamp" }; readonly type: { readonly field: "type" }; readonly userId: { readonly field: "userId" } } }; readonly discriminator: { readonly field: "type" }; readonly variants: { readonly "add-to-cart": { readonly namespace: "__unbound__" & NamespaceId; readonly model: "AddToCartEvent" }; readonly search: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "SearchEvent" }; readonly "view-product": { readonly namespace: "__unbound__" & NamespaceId; readonly model: "ViewProductEvent" } } }; readonly Invoice: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly issuedAt: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } }; readonly items: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "InvoiceLineItem" }; readonly many: { readonly elementNullable: false } }; readonly orderId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly subtotal: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" } }; readonly tax: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" } }; readonly total: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" } } }; readonly relations: { readonly order: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Order" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["orderId"]; readonly targetFields: readonly ["_id"] } } }; readonly storage: { readonly collection: "invoices"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly issuedAt: { readonly field: "issuedAt" }; readonly items: { readonly field: "items" }; readonly orderId: { readonly field: "orderId" }; readonly subtotal: { readonly field: "subtotal" }; readonly tax: { readonly field: "tax" }; readonly total: { readonly field: "total" } } } }; readonly Location: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly country: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly postalCode: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly streetAndNumber: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "locations"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly city: { readonly field: "city" }; readonly country: { readonly field: "country" }; readonly name: { readonly field: "name" }; readonly postalCode: { readonly field: "postalCode" }; readonly streetAndNumber: { readonly field: "streetAndNumber" } } } }; readonly Order: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly items: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "OrderLineItem" }; readonly many: { readonly elementNullable: false } }; readonly shippingAddress: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly statusHistory: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "StatusEntry" }; readonly many: { readonly elementNullable: false } }; readonly type: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly userId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } } }; readonly relations: { readonly invoices: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Invoice" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["_id"]; readonly targetFields: readonly ["orderId"] } }; readonly user: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["userId"]; readonly targetFields: readonly ["_id"] } } }; readonly storage: { readonly collection: "orders"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly items: { readonly field: "items" }; readonly shippingAddress: { readonly field: "shippingAddress" }; readonly statusHistory: { readonly field: "statusHistory" }; readonly type: { readonly field: "type" }; readonly userId: { readonly field: "userId" } } } }; readonly Product: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly articleType: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly brand: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly code: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly description: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly embedding: { readonly nullable: true; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/double@1" }; readonly many: { readonly elementNullable: false } }; readonly image: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Image" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly price: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Price" } }; readonly primaryCategory: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly status: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly subCategory: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "products"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly articleType: { readonly field: "articleType" }; readonly brand: { readonly field: "brand" }; readonly code: { readonly field: "code" }; readonly description: { readonly field: "description" }; readonly embedding: { readonly field: "embedding" }; readonly image: { readonly field: "image" }; readonly name: { readonly field: "name" }; readonly price: { readonly field: "price" }; readonly primaryCategory: { readonly field: "primaryCategory" }; readonly status: { readonly field: "status" }; readonly subCategory: { readonly field: "subCategory" } } } }; readonly SearchEvent: { readonly fields: { readonly query: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "events"; readonly fields: { readonly query: { readonly field: "query" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Event" } }; readonly User: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly address: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Address" } }; readonly email: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: { readonly carts: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Cart" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["_id"]; readonly targetFields: readonly ["userId"] } }; readonly orders: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Order" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["_id"]; readonly targetFields: readonly ["userId"] } } }; readonly storage: { readonly collection: "users"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly address: { readonly field: "address" }; readonly email: { readonly field: "email" }; readonly name: { readonly field: "name" } } } }; readonly ViewProductEvent: { readonly fields: { readonly brand: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly exitMethod: { readonly nullable: true; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly productId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly subCategory: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "events"; readonly fields: { readonly brand: { readonly field: "brand" }; readonly exitMethod: { readonly field: "exitMethod" }; readonly productId: { readonly field: "productId" }; readonly subCategory: { readonly field: "subCategory" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Event" } } }; + readonly enum: { readonly OrderType: { readonly codecId: "mongo/string@1"; readonly members: readonly [{ readonly name: "Delivery"; readonly value: "delivery" }, { readonly name: "Pickup"; readonly value: "pickup" }] }; readonly ProductStatus: { readonly codecId: "mongo/string@1"; readonly members: readonly [{ readonly name: "Active"; readonly value: "active" }, { readonly name: "Discontinued"; readonly value: "discontinued" }, { readonly name: "OutOfStock"; readonly value: "out-of-stock" }] } }; + readonly enumMemberTypes?: { readonly OrderType: readonly [{ readonly name: "Delivery"; readonly value: CodecTypes["mongo/string@1"]["output"] }, { readonly name: "Pickup"; readonly value: CodecTypes["mongo/string@1"]["output"] }]; readonly ProductStatus: readonly [{ readonly name: "Active"; readonly value: CodecTypes["mongo/string@1"]["output"] }, { readonly name: "Discontinued"; readonly value: CodecTypes["mongo/string@1"]["output"] }, { readonly name: "OutOfStock"; readonly value: CodecTypes["mongo/string@1"]["output"] }] }; }; }; }; + readonly capabilities: { }; + readonly extensions: { }; + readonly meta: { }; readonly profileHash: ProfileHash; }; diff --git a/packages/2-mongo-family/9-family/test/fixtures/migration-contract.json b/packages/2-mongo-family/9-family/test/fixtures/migration-contract.json index 1656b5373c15..b2039b10d9ef 100644 --- a/packages/2-mongo-family/9-family/test/fixtures/migration-contract.json +++ b/packages/2-mongo-family/9-family/test/fixtures/migration-contract.json @@ -2,7 +2,7 @@ "_generated": { "message": "This file is automatically generated by \"prisma contract emit\".", "regenerate": "To regenerate, run: prisma contract emit", - "warning": "\u26a0\ufe0f GENERATED FILE - DO NOT EDIT" + "warning": "⚠️ GENERATED FILE - DO NOT EDIT" }, "capabilities": {}, "domain": { @@ -66,7 +66,14 @@ }, "relations": {}, "storage": { - "fields": { "brand": { "field": "brand" }, "productId": { "field": "productId" } }, + "fields": { + "brand": { + "field": "brand" + }, + "productId": { + "field": "productId" + } + }, "collection": "events" } }, @@ -87,7 +94,10 @@ "nullable": false, "type": { "kind": "valueObject", - "name": "CartItem" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "CartItem" } }, "userId": { @@ -103,15 +113,27 @@ "user": { "cardinality": "N:1", "nullable": false, - "on": { "localFields": ["userId"], "targetFields": ["_id"] }, - "to": { "model": "User", "namespace": "__unbound__" } + "on": { + "localFields": ["userId"], + "targetFields": ["_id"] + }, + "to": { + "model": "User", + "namespace": "__unbound__" + } } }, "storage": { "fields": { - "_id": { "field": "_id" }, - "items": { "field": "items" }, - "userId": { "field": "userId" } + "_id": { + "field": "_id" + }, + "items": { + "field": "items" + }, + "userId": { + "field": "userId" + } }, "collection": "carts" } @@ -165,11 +187,21 @@ "relations": {}, "storage": { "fields": { - "_id": { "field": "_id" }, - "sessionId": { "field": "sessionId" }, - "timestamp": { "field": "timestamp" }, - "type": { "field": "type" }, - "userId": { "field": "userId" } + "_id": { + "field": "_id" + }, + "sessionId": { + "field": "sessionId" + }, + "timestamp": { + "field": "timestamp" + }, + "type": { + "field": "type" + }, + "userId": { + "field": "userId" + } }, "collection": "events" }, @@ -213,7 +245,10 @@ "nullable": false, "type": { "kind": "valueObject", - "name": "InvoiceLineItem" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "InvoiceLineItem" } }, "orderId": { @@ -265,13 +300,27 @@ }, "storage": { "fields": { - "_id": { "field": "_id" }, - "issuedAt": { "field": "issuedAt" }, - "items": { "field": "items" }, - "orderId": { "field": "orderId" }, - "subtotal": { "field": "subtotal" }, - "tax": { "field": "tax" }, - "total": { "field": "total" } + "_id": { + "field": "_id" + }, + "issuedAt": { + "field": "issuedAt" + }, + "items": { + "field": "items" + }, + "orderId": { + "field": "orderId" + }, + "subtotal": { + "field": "subtotal" + }, + "tax": { + "field": "tax" + }, + "total": { + "field": "total" + } }, "collection": "invoices" } @@ -330,12 +379,24 @@ "relations": {}, "storage": { "fields": { - "_id": { "field": "_id" }, - "city": { "field": "city" }, - "country": { "field": "country" }, - "name": { "field": "name" }, - "postalCode": { "field": "postalCode" }, - "streetAndNumber": { "field": "streetAndNumber" } + "_id": { + "field": "_id" + }, + "city": { + "field": "city" + }, + "country": { + "field": "country" + }, + "name": { + "field": "name" + }, + "postalCode": { + "field": "postalCode" + }, + "streetAndNumber": { + "field": "streetAndNumber" + } }, "collection": "locations" } @@ -357,7 +418,10 @@ "nullable": false, "type": { "kind": "valueObject", - "name": "OrderLineItem" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "OrderLineItem" } }, "shippingAddress": { @@ -375,7 +439,10 @@ "nullable": false, "type": { "kind": "valueObject", - "name": "StatusEntry" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "StatusEntry" } }, "type": { @@ -416,18 +483,36 @@ "user": { "cardinality": "N:1", "nullable": false, - "on": { "localFields": ["userId"], "targetFields": ["_id"] }, - "to": { "model": "User", "namespace": "__unbound__" } + "on": { + "localFields": ["userId"], + "targetFields": ["_id"] + }, + "to": { + "model": "User", + "namespace": "__unbound__" + } } }, "storage": { "fields": { - "_id": { "field": "_id" }, - "items": { "field": "items" }, - "shippingAddress": { "field": "shippingAddress" }, - "statusHistory": { "field": "statusHistory" }, - "type": { "field": "type" }, - "userId": { "field": "userId" } + "_id": { + "field": "_id" + }, + "items": { + "field": "items" + }, + "shippingAddress": { + "field": "shippingAddress" + }, + "statusHistory": { + "field": "statusHistory" + }, + "type": { + "field": "type" + }, + "userId": { + "field": "userId" + } }, "collection": "orders" } @@ -488,7 +573,10 @@ "nullable": false, "type": { "kind": "valueObject", - "name": "Image" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Image" }, "many": false }, @@ -504,7 +592,10 @@ "nullable": false, "type": { "kind": "valueObject", - "name": "Price" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Price" }, "many": false }, @@ -542,18 +633,42 @@ "relations": {}, "storage": { "fields": { - "_id": { "field": "_id" }, - "articleType": { "field": "articleType" }, - "brand": { "field": "brand" }, - "code": { "field": "code" }, - "description": { "field": "description" }, - "embedding": { "field": "embedding" }, - "image": { "field": "image" }, - "name": { "field": "name" }, - "price": { "field": "price" }, - "primaryCategory": { "field": "primaryCategory" }, - "status": { "field": "status" }, - "subCategory": { "field": "subCategory" } + "_id": { + "field": "_id" + }, + "articleType": { + "field": "articleType" + }, + "brand": { + "field": "brand" + }, + "code": { + "field": "code" + }, + "description": { + "field": "description" + }, + "embedding": { + "field": "embedding" + }, + "image": { + "field": "image" + }, + "name": { + "field": "name" + }, + "price": { + "field": "price" + }, + "primaryCategory": { + "field": "primaryCategory" + }, + "status": { + "field": "status" + }, + "subCategory": { + "field": "subCategory" + } }, "collection": "products" } @@ -574,7 +689,14 @@ } }, "relations": {}, - "storage": { "fields": { "query": { "field": "query" } }, "collection": "events" } + "storage": { + "fields": { + "query": { + "field": "query" + } + }, + "collection": "events" + } }, "User": { "fields": { @@ -590,7 +712,10 @@ "nullable": true, "type": { "kind": "valueObject", - "name": "Address" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Address" }, "many": false }, @@ -637,10 +762,18 @@ }, "storage": { "fields": { - "_id": { "field": "_id" }, - "address": { "field": "address" }, - "email": { "field": "email" }, - "name": { "field": "name" } + "_id": { + "field": "_id" + }, + "address": { + "field": "address" + }, + "email": { + "field": "email" + }, + "name": { + "field": "name" + } }, "collection": "users" } @@ -687,242 +820,20 @@ "relations": {}, "storage": { "fields": { - "brand": { "field": "brand" }, - "exitMethod": { "field": "exitMethod" }, - "productId": { "field": "productId" }, - "subCategory": { "field": "subCategory" } - }, - "collection": "events" - } - } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" + "brand": { + "field": "brand" }, - "many": false - }, - "country": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - }, - "postalCode": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - }, - "streetAndNumber": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - } - } - }, - "CartItem": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - }, - "many": false - }, - "brand": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" + "exitMethod": { + "field": "exitMethod" }, - "many": false - }, - "image": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Image" + "productId": { + "field": "productId" }, - "many": false - }, - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - }, - "price": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Price" - }, - "many": false - }, - "productId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - } - } - }, - "Image": { - "fields": { - "url": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - } - } - }, - "InvoiceLineItem": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - }, - "many": false - }, - "lineTotal": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - }, - "many": false - }, - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - }, - "unitPrice": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - }, - "many": false - } - } - }, - "OrderLineItem": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - }, - "many": false - }, - "brand": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - }, - "image": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Image" - }, - "many": false - }, - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - }, - "price": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Price" - }, - "many": false - }, - "productId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - } - } - }, - "Price": { - "fields": { - "amount": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - }, - "many": false - }, - "currency": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false - } - } - }, - "StatusEntry": { - "fields": { - "status": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "many": false + "subCategory": { + "field": "subCategory" + } }, - "timestamp": { - "nullable": false, - "type": { - "codecId": "mongo/date@1", - "kind": "scalar" - }, - "many": false - } + "collection": "events" } } } @@ -1624,13 +1535,255 @@ "kind": "valueSet", "values": ["active", "discontinued", "out-of-stock"] } + }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "country": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "postalCode": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "streetAndNumber": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + } + } + }, + "CartItem": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + }, + "many": false + }, + "brand": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "image": { + "nullable": false, + "type": { + "kind": "valueObject", + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Image" + }, + "many": false + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "price": { + "nullable": false, + "type": { + "kind": "valueObject", + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Price" + }, + "many": false + }, + "productId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + } + } + }, + "Image": { + "fields": { + "url": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + } + } + }, + "InvoiceLineItem": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + }, + "many": false + }, + "lineTotal": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + }, + "many": false + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "unitPrice": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + }, + "many": false + } + } + }, + "OrderLineItem": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + }, + "many": false + }, + "brand": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "image": { + "nullable": false, + "type": { + "kind": "valueObject", + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Image" + }, + "many": false + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "price": { + "nullable": false, + "type": { + "kind": "valueObject", + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Price" + }, + "many": false + }, + "productId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + } + } + }, + "Price": { + "fields": { + "amount": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + }, + "many": false + }, + "currency": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + } + } + }, + "StatusEntry": { + "fields": { + "status": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "many": false + }, + "timestamp": { + "nullable": false, + "type": { + "codecId": "mongo/date@1", + "kind": "scalar" + }, + "many": false + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "bd938b4f8a10c688bd32dc61ec1dd808dcf34e725f08505b39ce365a39c97e1b" + "storageHash": "efad057995b73e1a0303e93726cc60cb6f7186542712902dd615689ad7971758" }, "target": "mongo", "targetFamily": "mongo" diff --git a/packages/2-mongo-family/9-family/test/fixtures/orm-contract.d.ts b/packages/2-mongo-family/9-family/test/fixtures/orm-contract.d.ts index 7657e10fc0b4..3c15fbda8e75 100644 --- a/packages/2-mongo-family/9-family/test/fixtures/orm-contract.d.ts +++ b/packages/2-mongo-family/9-family/test/fixtures/orm-contract.d.ts @@ -1,7 +1,7 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma contract emit'. // To regenerate, run: prisma contract emit -import type { CodecTypes as MongoCodecTypes } from '@internal/target-mongo/codec-types'; +import type { CodecTypes as MongoCodecTypes } from '../../../../3-targets/mongo/1-target/src/exports/codec-types'; import type { MongoCollection, @@ -12,143 +12,79 @@ import type { import type { Contract as ContractType, ExecutionHashBase, + FieldTypesFromDomain, NamespaceId, ProfileHashBase, StorageHashBase, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; -export type StorageHash = StorageHashBase<'orm-storage'>; +export type StorageHash = StorageHashBase<"orm-storage">; export type ExecutionHash = ExecutionHashBase; -export type ProfileHash = ProfileHashBase<'orm-profile'>; +export type ProfileHash = ProfileHashBase<"orm-profile">; export type CodecTypes = MongoCodecTypes; -export type HomeAddressOutput = { - readonly city: CodecTypes['mongo/string@1']['output']; - readonly country: CodecTypes['mongo/string@1']['output']; -}; -export type HomeAddressInput = { - readonly city: CodecTypes['mongo/string@1']['input']; - readonly country: CodecTypes['mongo/string@1']['input']; -}; -export type FieldOutputTypes = { - readonly __unbound__: { - readonly Address: { - readonly street: CodecTypes['mongo/string@1']['output']; - readonly city: CodecTypes['mongo/string@1']['output']; - readonly zip: CodecTypes['mongo/string@1']['output']; - }; - readonly Bug: { readonly severity: CodecTypes['mongo/string@1']['output'] }; - readonly Comment: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly text: CodecTypes['mongo/string@1']['output']; - readonly createdAt: CodecTypes['mongo/date@1']['output']; - }; - readonly Feature: { - readonly priority: CodecTypes['mongo/string@1']['output']; - readonly targetRelease: CodecTypes['mongo/string@1']['output']; - }; - readonly Task: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly title: CodecTypes['mongo/string@1']['output']; - readonly type: CodecTypes['mongo/string@1']['output']; - readonly assigneeId: CodecTypes['mongo/objectId@1']['output']; - }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['output']; - readonly name: CodecTypes['mongo/string@1']['output']; - readonly email: CodecTypes['mongo/string@1']['output']; - readonly loginCount: CodecTypes['mongo/int32@1']['output']; - readonly tags: ReadonlyArray; - readonly homeAddress: HomeAddressOutput | null; - }; - }; -}; -export type FieldInputTypes = { - readonly __unbound__: { - readonly Address: { - readonly street: CodecTypes['mongo/string@1']['input']; - readonly city: CodecTypes['mongo/string@1']['input']; - readonly zip: CodecTypes['mongo/string@1']['input']; - }; - readonly Bug: { readonly severity: CodecTypes['mongo/string@1']['input'] }; - readonly Comment: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly text: CodecTypes['mongo/string@1']['input']; - readonly createdAt: CodecTypes['mongo/date@1']['input']; - }; - readonly Feature: { - readonly priority: CodecTypes['mongo/string@1']['input']; - readonly targetRelease: CodecTypes['mongo/string@1']['input']; - }; - readonly Task: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly title: CodecTypes['mongo/string@1']['input']; - readonly type: CodecTypes['mongo/string@1']['input']; - readonly assigneeId: CodecTypes['mongo/objectId@1']['input']; - }; - readonly User: { - readonly _id: CodecTypes['mongo/objectId@1']['input']; - readonly name: CodecTypes['mongo/string@1']['input']; - readonly email: CodecTypes['mongo/string@1']['input']; - readonly loginCount: CodecTypes['mongo/int32@1']['input']; - readonly tags: ReadonlyArray; - readonly homeAddress: HomeAddressInput | null; - }; - }; -}; +type ParameterizedOutputTypeFunctions = never; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage; +type ValueObjectInputTypes = ValueObjectTypesFromStorage; +export type HomeAddressOutput = ValueObjectOutputTypes["__unbound__"]["HomeAddress"]; +export type HomeAddressInput = ValueObjectInputTypes["__unbound__"]["HomeAddress"]; +type FieldTypeOverrides = { }; +export type FieldOutputTypes = FieldTypesFromDomain; +export type FieldInputTypes = FieldTypesFromDomain; export namespace Models { export type unbound_Task = { - _id: CodecTypes['mongo/objectId@1']['output']; - title: CodecTypes['mongo/string@1']['output']; - type: 'bug' | 'feature'; - assigneeId: CodecTypes['mongo/objectId@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Task"]["_id"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + type: "bug" | "feature"; + assigneeId: FieldOutputTypes["__unbound__"]["Task"]["assigneeId"]; comments: unbound_Comment[]; assignee: unbound_User; - readonly [RelationKeys]?: 'assignee'; + readonly [RelationKeys]?: "assignee"; }; export type unbound_Bug = { - _id: CodecTypes['mongo/objectId@1']['output']; - title: CodecTypes['mongo/string@1']['output']; - type: 'bug'; - assigneeId: CodecTypes['mongo/objectId@1']['output']; - severity: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Task"]["_id"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + type: "bug"; + assigneeId: FieldOutputTypes["__unbound__"]["Task"]["assigneeId"]; + severity: FieldOutputTypes["__unbound__"]["Bug"]["severity"]; comments: unbound_Comment[]; assignee: unbound_User; - readonly [RelationKeys]?: 'assignee'; + readonly [RelationKeys]?: "assignee"; }; export type unbound_Feature = { - _id: CodecTypes['mongo/objectId@1']['output']; - title: CodecTypes['mongo/string@1']['output']; - type: 'feature'; - assigneeId: CodecTypes['mongo/objectId@1']['output']; - priority: CodecTypes['mongo/string@1']['output']; - targetRelease: CodecTypes['mongo/string@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Task"]["_id"]; + title: FieldOutputTypes["__unbound__"]["Task"]["title"]; + type: "feature"; + assigneeId: FieldOutputTypes["__unbound__"]["Task"]["assigneeId"]; + priority: FieldOutputTypes["__unbound__"]["Feature"]["priority"]; + targetRelease: FieldOutputTypes["__unbound__"]["Feature"]["targetRelease"]; comments: unbound_Comment[]; assignee: unbound_User; - readonly [RelationKeys]?: 'assignee'; + readonly [RelationKeys]?: "assignee"; }; export type unbound_User = { - _id: CodecTypes['mongo/objectId@1']['output']; - name: CodecTypes['mongo/string@1']['output']; - email: CodecTypes['mongo/string@1']['output']; - loginCount: CodecTypes['mongo/int32@1']['output']; - tags: ReadonlyArray; - homeAddress: HomeAddressOutput | null; + _id: FieldOutputTypes["__unbound__"]["User"]["_id"]; + name: FieldOutputTypes["__unbound__"]["User"]["name"]; + email: FieldOutputTypes["__unbound__"]["User"]["email"]; + loginCount: FieldOutputTypes["__unbound__"]["User"]["loginCount"]; + tags: FieldOutputTypes["__unbound__"]["User"]["tags"]; + homeAddress: FieldOutputTypes["__unbound__"]["User"]["homeAddress"]; addresses: unbound_Address[]; tasks: unbound_AnyTask[]; - readonly [RelationKeys]?: 'tasks'; + readonly [RelationKeys]?: "tasks"; }; export type unbound_Address = { - street: CodecTypes['mongo/string@1']['output']; - city: CodecTypes['mongo/string@1']['output']; - zip: CodecTypes['mongo/string@1']['output']; + street: FieldOutputTypes["__unbound__"]["Address"]["street"]; + city: FieldOutputTypes["__unbound__"]["Address"]["city"]; + zip: FieldOutputTypes["__unbound__"]["Address"]["zip"]; }; export type unbound_Comment = { - _id: CodecTypes['mongo/objectId@1']['output']; - text: CodecTypes['mongo/string@1']['output']; - createdAt: CodecTypes['mongo/date@1']['output']; + _id: FieldOutputTypes["__unbound__"]["Comment"]["_id"]; + text: FieldOutputTypes["__unbound__"]["Comment"]["text"]; + createdAt: FieldOutputTypes["__unbound__"]["Comment"]["createdAt"]; }; export type unbound_AnyTask = unbound_Bug | unbound_Feature; } @@ -168,238 +104,22 @@ export declare const models: { export type TypeMaps = MongoTypeMaps; type ContractBase = Omit< - ContractType<{ - readonly namespaces: { - readonly __unbound__: { - readonly id: '__unbound__'; - readonly kind: 'mongo-namespace'; - readonly entries: { - readonly collection: { readonly tasks: MongoCollection; readonly users: MongoCollection }; - }; - }; - }; - readonly storageHash: StorageHash; - }>, + ContractType<{ readonly namespaces: { readonly __unbound__: { readonly id: "__unbound__"; readonly kind: "mongo-namespace"; readonly entries: { readonly collection: { readonly tasks: MongoCollection; readonly users: MongoCollection }; readonly valueObject: { readonly HomeAddress: { readonly fields: { readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly country: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } } } } } } }; readonly storageHash: StorageHash }>, 'roots' | 'domain' > & { - readonly target: 'mongo'; - readonly targetFamily: 'mongo'; - readonly roots: { - readonly tasks: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Task' }; - readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; - }; + readonly target: "mongo"; + readonly targetFamily: "mongo"; + readonly roots: { readonly tasks: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" }; readonly users: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" } }; readonly domain: { readonly namespaces: { readonly __unbound__: { - readonly models: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly fields: { readonly street: { readonly field: 'street' }; readonly city: { readonly field: 'city' }; readonly zip: { readonly field: 'zip' } } }; - readonly owner: 'User'; - }; - readonly Bug: { - readonly fields: { - readonly severity: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'tasks'; readonly fields: { readonly severity: { readonly field: 'severity' } } }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Task'; - }; - }; - readonly Comment: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly createdAt: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly fields: { readonly _id: { readonly field: '_id' }; readonly text: { readonly field: 'text' }; readonly createdAt: { readonly field: 'createdAt' } } }; - readonly owner: 'Task'; - }; - readonly Feature: { - readonly fields: { - readonly priority: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly targetRelease: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - readonly relations: Record; - readonly storage: { readonly collection: 'tasks'; readonly fields: { readonly priority: { readonly field: 'priority' }; readonly targetRelease: { readonly field: 'targetRelease' } } }; - readonly base: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Task'; - }; - }; - readonly Task: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly title: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly type: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly assigneeId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - }; - readonly relations: { - readonly assignee: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'User'; - }; - readonly cardinality: 'N:1'; - readonly nullable: false; - readonly on: { - readonly localFields: readonly ['assigneeId']; - readonly targetFields: readonly ['_id']; - }; - }; - readonly comments: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Comment'; - }; - readonly cardinality: '1:N'; - }; - }; - readonly storage: { readonly collection: 'tasks'; readonly fields: { readonly _id: { readonly field: '_id' }; readonly title: { readonly field: 'title' }; readonly type: { readonly field: 'type' }; readonly assigneeId: { readonly field: 'assigneeId' } }; readonly relations: { readonly comments: { readonly field: 'comments' } } }; - readonly discriminator: { readonly field: 'type' }; - readonly variants: { - readonly bug: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Bug'; - }; - readonly feature: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Feature'; - }; - }; - }; - readonly User: { - readonly fields: { - readonly _id: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly email: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly loginCount: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - readonly tags: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly homeAddress: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'HomeAddress' }; - }; - }; - readonly relations: { - readonly addresses: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Address'; - }; - readonly cardinality: '1:N'; - }; - readonly tasks: { - readonly to: { - readonly namespace: '__unbound__' & NamespaceId; - readonly model: 'Task'; - }; - readonly cardinality: '1:N'; - readonly on: { - readonly localFields: readonly ['_id']; - readonly targetFields: readonly ['assigneeId']; - }; - }; - }; - readonly storage: { readonly collection: 'users'; readonly fields: { readonly _id: { readonly field: '_id' }; readonly name: { readonly field: 'name' }; readonly email: { readonly field: 'email' }; readonly loginCount: { readonly field: 'loginCount' }; readonly tags: { readonly field: 'tags' }; readonly homeAddress: { readonly field: 'homeAddress' } }; readonly relations: { readonly addresses: { readonly field: 'addresses' } } }; - }; - }; - readonly valueObjects: { - readonly HomeAddress: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; - }; - }; - }; - readonly capabilities: {}; - readonly extensions: {}; - readonly meta: {}; - readonly valueObjects: { - readonly HomeAddress: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly country: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; + readonly models: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly city: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly zip: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly fields: { readonly street: { readonly field: "street" }; readonly city: { readonly field: "city" }; readonly zip: { readonly field: "zip" } } }; readonly owner: "User" }; readonly Bug: { readonly fields: { readonly severity: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly severity: { readonly field: "severity" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" } }; readonly Comment: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly text: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly createdAt: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/date@1" } } }; readonly relations: Record; readonly storage: { readonly fields: { readonly _id: { readonly field: "_id" }; readonly text: { readonly field: "text" }; readonly createdAt: { readonly field: "createdAt" } } }; readonly owner: "Task" }; readonly Feature: { readonly fields: { readonly priority: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly targetRelease: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } } }; readonly relations: Record; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly priority: { readonly field: "priority" }; readonly targetRelease: { readonly field: "targetRelease" } } }; readonly base: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" } }; readonly Task: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly title: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly type: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly assigneeId: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } } }; readonly relations: { readonly assignee: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "User" }; readonly cardinality: "N:1"; readonly nullable: false; readonly on: { readonly localFields: readonly ["assigneeId"]; readonly targetFields: readonly ["_id"] } }; readonly comments: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Comment" }; readonly cardinality: "1:N" } }; readonly storage: { readonly collection: "tasks"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly title: { readonly field: "title" }; readonly type: { readonly field: "type" }; readonly assigneeId: { readonly field: "assigneeId" } }; readonly relations: { readonly comments: { readonly field: "comments" } } }; readonly discriminator: { readonly field: "type" }; readonly variants: { readonly bug: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Bug" }; readonly feature: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Feature" } } }; readonly User: { readonly fields: { readonly _id: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/objectId@1" } }; readonly name: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly email: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" } }; readonly loginCount: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/int32@1" } }; readonly tags: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "mongo/string@1" }; readonly many: { readonly elementNullable: false } }; readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "HomeAddress" } } }; readonly relations: { readonly addresses: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Address" }; readonly cardinality: "1:N" }; readonly tasks: { readonly to: { readonly namespace: "__unbound__" & NamespaceId; readonly model: "Task" }; readonly cardinality: "1:N"; readonly on: { readonly localFields: readonly ["_id"]; readonly targetFields: readonly ["assigneeId"] } } }; readonly storage: { readonly collection: "users"; readonly fields: { readonly _id: { readonly field: "_id" }; readonly name: { readonly field: "name" }; readonly email: { readonly field: "email" }; readonly loginCount: { readonly field: "loginCount" }; readonly tags: { readonly field: "tags" }; readonly homeAddress: { readonly field: "homeAddress" } }; readonly relations: { readonly addresses: { readonly field: "addresses" } } } } }; }; }; }; + readonly capabilities: { }; + readonly extensions: { }; + readonly meta: { }; readonly profileHash: ProfileHash; }; diff --git a/packages/2-mongo-family/9-family/test/fixtures/orm-contract.json b/packages/2-mongo-family/9-family/test/fixtures/orm-contract.json index 9aee4ab42e9e..a95f3db83085 100644 --- a/packages/2-mongo-family/9-family/test/fixtures/orm-contract.json +++ b/packages/2-mongo-family/9-family/test/fixtures/orm-contract.json @@ -19,6 +19,28 @@ "collection": { "tasks": {}, "users": {} + }, + "valueObject": { + "HomeAddress": { + "fields": { + "city": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + }, + "country": { + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false, + "many": false + } + } + } } } } @@ -31,13 +53,21 @@ "models": { "Task": { "storage": { + "collection": "tasks", "fields": { - "_id": { "field": "_id" }, - "title": { "field": "title" }, - "type": { "field": "type" }, - "assigneeId": { "field": "assigneeId" } + "_id": { + "field": "_id" + }, + "title": { + "field": "title" + }, + "type": { + "field": "type" + }, + "assigneeId": { + "field": "assigneeId" + } }, - "collection": "tasks", "relations": { "comments": { "field": "comments" @@ -114,7 +144,14 @@ } }, "Bug": { - "storage": { "fields": { "severity": { "field": "severity" } }, "collection": "tasks" }, + "storage": { + "collection": "tasks", + "fields": { + "severity": { + "field": "severity" + } + } + }, "fields": { "severity": { "type": { @@ -133,11 +170,15 @@ }, "Feature": { "storage": { + "collection": "tasks", "fields": { - "priority": { "field": "priority" }, - "targetRelease": { "field": "targetRelease" } - }, - "collection": "tasks" + "priority": { + "field": "priority" + }, + "targetRelease": { + "field": "targetRelease" + } + } }, "fields": { "priority": { @@ -165,15 +206,27 @@ }, "User": { "storage": { + "collection": "users", "fields": { - "_id": { "field": "_id" }, - "name": { "field": "name" }, - "email": { "field": "email" }, - "loginCount": { "field": "loginCount" }, - "tags": { "field": "tags" }, - "homeAddress": { "field": "homeAddress" } + "_id": { + "field": "_id" + }, + "name": { + "field": "name" + }, + "email": { + "field": "email" + }, + "loginCount": { + "field": "loginCount" + }, + "tags": { + "field": "tags" + }, + "homeAddress": { + "field": "homeAddress" + } }, - "collection": "users", "relations": { "addresses": { "field": "addresses" @@ -226,7 +279,10 @@ "homeAddress": { "type": { "kind": "valueObject", - "name": "HomeAddress" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "HomeAddress" }, "nullable": true, "many": false @@ -256,9 +312,15 @@ "Address": { "storage": { "fields": { - "street": { "field": "street" }, - "city": { "field": "city" }, - "zip": { "field": "zip" } + "street": { + "field": "street" + }, + "city": { + "field": "city" + }, + "zip": { + "field": "zip" + } } }, "fields": { @@ -293,9 +355,15 @@ "Comment": { "storage": { "fields": { - "_id": { "field": "_id" }, - "text": { "field": "text" }, - "createdAt": { "field": "createdAt" } + "_id": { + "field": "_id" + }, + "text": { + "field": "text" + }, + "createdAt": { + "field": "createdAt" + } } }, "fields": { @@ -327,28 +395,6 @@ "relations": {}, "owner": "Task" } - }, - "valueObjects": { - "HomeAddress": { - "fields": { - "city": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - }, - "country": { - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false, - "many": false - } - } - } } } } diff --git a/packages/2-mongo-family/9-family/test/mongo-contract-json-fixture.ts b/packages/2-mongo-family/9-family/test/mongo-contract-json-fixture.ts index 25c2ef4172f5..adefc783d5f8 100644 --- a/packages/2-mongo-family/9-family/test/mongo-contract-json-fixture.ts +++ b/packages/2-mongo-family/9-family/test/mongo-contract-json-fixture.ts @@ -20,6 +20,7 @@ function normalizeModels( export function mongoContractJson(params: { readonly models?: Record; readonly storageCollections?: Record; + readonly storageValueObjects?: Record; readonly roots?: Record>; }) { const models = normalizeModels( @@ -47,7 +48,12 @@ export function mongoContractJson(params: { namespaces: { [UNBOUND_NAMESPACE_ID]: { id: UNBOUND_NAMESPACE_ID, - entries: { collection: collections }, + entries: { + collection: collections, + ...(params.storageValueObjects === undefined + ? {} + : { valueObject: params.storageValueObjects }), + }, }, }, }, diff --git a/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts b/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts index e8a68943e51a..ee6ff1d7e416 100644 --- a/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts +++ b/packages/2-mongo-family/9-family/test/mongo-contract-serializer-base.test.ts @@ -76,33 +76,83 @@ describe('MongoContractSerializerBase', () => { expect(() => serializer.deserializeContract(json)).toThrow(/missing_collection/); }); - it('rejects when a field references a value object that does not exist', () => { - const serializer = new RecordingSerializer(); - const json = mongoContractJson({ - models: { - Item: { - fields: { - _id: { - type: { kind: 'scalar', codecId: 'mongo/objectId@1' }, - nullable: false, - many: false, + describe('given a model field that holds a value object', () => { + function contractJsonWith(storageValueObjects: Record) { + return mongoContractJson({ + models: { + Item: { + fields: { + _id: { + type: { kind: 'scalar', codecId: 'mongo/objectId@1' }, + nullable: false, + many: false, + }, + data: { + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, + nullable: false, + many: false, + }, }, - data: { - type: { kind: 'valueObject', name: 'Missing' }, + storage: { + fields: { _id: { field: '_id' }, data: { field: 'data' } }, + collection: 'items', + }, + relations: {}, + }, + }, + storageValueObjects, + }); + } + + const address = { + fields: { + city: { type: { kind: 'scalar', codecId: 'mongo/string@1' }, nullable: false }, + }, + }; + + it('accepts it when storage has the value object', () => { + const result = new RecordingSerializer().deserializeContract( + contractJsonWith({ Address: address }), + ); + expect(result.contract.storage.namespaces['__unbound__']?.entries['valueObject']).toEqual({ + Address: { + fields: { + city: { + type: { kind: 'scalar', codecId: 'mongo/string@1' }, nullable: false, many: false, }, }, - storage: { - fields: { _id: { field: '_id' }, data: { field: 'data' } }, - collection: 'items', - }, - relations: {}, }, - }, + }); }); - expect(() => serializer.deserializeContract(json)).toThrow(); + it('refuses it when storage does not have the value object, naming the field and the coordinate', () => { + expect(() => new RecordingSerializer().deserializeContract(contractJsonWith({}))).toThrow( + 'Model "__unbound__:Item" field "data" refers to value object "Address" in storage namespace "__unbound__", which storage.namespaces.__unbound__.entries.valueObject does not have', + ); + }); + + it('refuses a contract that stores value objects in the domain plane, and says to re-emit', () => { + const json = contractJsonWith({ Address: address }); + const old = { + ...json, + domain: { + namespaces: { + __unbound__: { ...json.domain.namespaces['__unbound__'], valueObjects: {} }, + }, + }, + }; + expect(() => new RecordingSerializer().deserializeContract(old)).toThrow( + 'Contract structural validation failed: domain.namespaces.__unbound__.valueObjects: contracts no longer store value objects in the domain plane; they are storage entities under storage.namespaces..entries.valueObject, so re-emit the contract from its source, or rewrite a stored contract such as a migration snapshot with value-objects-in-storage.ts, which ships in the value-objects-in-storage scripts of the Prisma 8 upgrade guide', + ); + }); }); it('rejects an unknown entries kind at hydration naming the kind and the namespace id', () => { diff --git a/packages/2-sql-family/1-core/contract/package.json b/packages/2-sql-family/1-core/contract/package.json index c5d072598251..3e2c3ea936d2 100644 --- a/packages/2-sql-family/1-core/contract/package.json +++ b/packages/2-sql-family/1-core/contract/package.json @@ -45,6 +45,8 @@ "exports": { "./authored-check-naming": "./dist/authored-check-naming.mjs", "./canonicalization-hooks": "./dist/canonicalization-hooks.mjs", + "./check-expressions": "./dist/check-expressions.mjs", + "./codec-json-type": "./dist/codec-json-type.mjs", "./contract-view": "./dist/contract-view.mjs", "./data-type": "./dist/data-type.mjs", "./data-type-support": "./dist/data-type-support.mjs", diff --git a/packages/2-sql-family/1-core/contract/src/check-expressions.ts b/packages/2-sql-family/1-core/contract/src/check-expressions.ts new file mode 100644 index 000000000000..b2411eddd7fa --- /dev/null +++ b/packages/2-sql-family/1-core/contract/src/check-expressions.ts @@ -0,0 +1,240 @@ +import type { + ContractValueObject, + ContractValueObjectField, + EntityCoordinate, + StorageBase, +} from '@internal/contract/types'; +import { valueObjectAt } from '@internal/contract/types'; +import type { CodecLookupWithDescriptors } from '@internal/framework-components/codec'; +import type { CheckKind } from '@internal/sql-schema-ir/naming'; +import { invariant } from '@internal/utils/assertions'; +import { declaredJsonType, type JsonType } from './codec-json-type'; +import { contractError } from './contract-errors'; +import { isStorageValueSet } from './ir/storage-value-set'; + +type CodecDescriptors = Pick; + +/** A value object with each nested value object inlined, as the target's check renderer reads it. */ +export interface ResolvedValueObject { + readonly fields: Readonly>; +} + +export interface ResolvedValueObjectField { + readonly nullable: boolean; + readonly many: false | { readonly elementNullable: boolean }; + readonly type: ResolvedValueObjectFieldType; +} + +/** + * `jsonType` is the JSON type the member's codec declares. `values` holds the stored values of the value set an enum member refers to. `anyObject` stands where a value object that contains itself refers back into itself: any object is accepted there, as the MongoDB validator does. + */ +export type ResolvedValueObjectFieldType = + | { + readonly kind: 'scalar'; + readonly jsonType: JsonType; + readonly values: readonly (string | number)[] | undefined; + } + | { readonly kind: 'valueObject'; readonly valueObject: ResolvedValueObject } + | { readonly kind: 'anyObject' }; + +/** One column's shape, as the contract builder hands it to a target's check renderer. */ +export interface CheckExpressionInput { + readonly tableName: string; + readonly columnName: string; + readonly many: false | { readonly elementNullable: boolean }; + /** The values a column typed by a domain enum, or a discriminator column, may hold. */ + readonly memberValues: readonly (string | number)[] | undefined; + /** The value object the column holds, or `undefined` when it holds none. */ + readonly valueObject: ResolvedColumnValueObject | undefined; +} + +/** The value object a column holds, with the column's nullability, and whether the column's JSON value is an array of them. */ +export interface ResolvedColumnValueObject { + readonly nullable: boolean; + readonly many: false | { readonly elementNullable: boolean }; + readonly valueObject: ResolvedValueObject; +} + +/** A check the target wants written: what it enforces, the column it constrains, and the predicate body without the surrounding `CHECK (…)`. The family names it. */ +export interface CheckExpressionCandidate { + readonly kind: CheckKind; + readonly columnName: string; + readonly expression: string; +} + +export type CheckExpressionRenderer = ( + input: CheckExpressionInput, +) => ReadonlyArray; + +/** + * What the check of a column that holds a value object reads. The contract build and `contract print` both derive it here, so print recognises exactly the check the build writes. + */ +export function resolveColumnValueObject( + storage: Pick, + column: { + readonly reference: EntityCoordinate<'storage', 'valueObject'>; + readonly nullable: boolean; + readonly many: false | { readonly elementNullable: boolean }; + }, + codecs: CodecDescriptors, +): ResolvedColumnValueObject { + return { + nullable: column.nullable, + many: column.many, + valueObject: resolveValueObject(storage, column.reference, codecs), + }; +} + +/** + * The value set a discriminator column names (ADR 309): keyed `.`, and holding the variants' values sorted, so the order variants are declared in does not change the storage hash. The contract build writes it and `contract print` compares a contract's value set with it. + */ +export function discriminatorValueSet( + baseModelName: string, + discriminatorField: string, + variantValues: readonly string[], +): { readonly name: string; readonly values: readonly string[] } { + return { name: `${baseModelName}.${discriminatorField}`, values: [...variantValues].sort() }; +} + +/** + * The value object a reference names, with nested value objects inlined, enum members' values read from their storage value sets, and each scalar member's JSON type read from its codec. + */ +export function resolveValueObject( + storage: Pick, + reference: EntityCoordinate<'storage', 'valueObject'>, + codecs: CodecDescriptors, +): ResolvedValueObject { + return resolveEntered(storage, reference, [referenceKey(reference)], codecs); +} + +function resolveEntered( + storage: Pick, + reference: EntityCoordinate<'storage', 'valueObject'>, + path: readonly string[], + codecs: CodecDescriptors, +): ResolvedValueObject { + const valueObject = requiredValueObject(storage, reference); + return { + fields: Object.fromEntries( + Object.entries(valueObject.fields).map(([name, field]) => [ + name, + { + nullable: field.nullable, + many: field.many ?? false, + type: resolveFieldType(storage, { reference, name, field }, path, codecs), + }, + ]), + ), + }; +} + +interface ValueObjectMember { + readonly reference: EntityCoordinate<'storage', 'valueObject'>; + readonly name: string; + readonly field: ContractValueObjectField; +} + +function resolveFieldType( + storage: Pick, + member: ValueObjectMember, + path: readonly string[], + codecs: CodecDescriptors, +): ResolvedValueObjectFieldType { + const { field } = member; + const { type } = field; + invariant( + type.kind !== 'union' && field.dict !== true, + 'a SQL value object member is never a union or a dictionary', + ); + if (type.kind === 'scalar') { + return { + kind: 'scalar', + jsonType: memberJsonType(member, type.codecId, codecs), + values: field.valueSet === undefined ? undefined : valueSetValues(storage, field.valueSet), + }; + } + const key = referenceKey(type); + if (reachesAny(storage, type, new Set(path))) return { kind: 'anyObject' }; + return { + kind: 'valueObject', + valueObject: resolveEntered(storage, type, [...path, key], codecs), + }; +} + +function memberJsonType( + member: ValueObjectMember, + codecId: string, + codecs: CodecDescriptors, +): JsonType { + const descriptor = codecs.descriptorFor(codecId); + invariant( + descriptor !== undefined, + `value object member codec "${codecId}" is not a codec of the stack; the contract validators refuse a codec the stack does not register`, + ); + const jsonType = declaredJsonType(descriptor); + if (jsonType !== undefined) return jsonType; + const valueObject = `${member.reference.namespaceId}:${member.reference.entityName}`; + throw contractError( + 'CONTRACT.CODEC_JSON_TYPE_MISSING', + `The codec "${codecId}" of value object "${valueObject}" member "${member.name}" declares no jsonType, so the check of the value object cannot be derived.`, + { + why: 'The check of a value object column requires each member to be of the JSON type its codec declares, and this codec declares none.', + fix: `The author of the codec "${codecId}" adds a jsonType to its descriptor, the JSON type its value is written as inside a JSON document.`, + meta: { codecId, valueObject, member: member.name, property: 'jsonType' }, + }, + ); +} + +/** + * Whether `from` refers, through its own fields or nested ones, to a value object on the path being resolved. Each value object on the path refers to `from`, so this holds exactly when `from` is in a group of value objects that contain each other which the path has already entered. + */ +function reachesAny( + storage: Pick, + from: EntityCoordinate<'storage', 'valueObject'>, + targets: ReadonlySet, +): boolean { + const seen = new Set(); + const pending: EntityCoordinate<'storage', 'valueObject'>[] = [from]; + for (let next = pending.pop(); next !== undefined; next = pending.pop()) { + const key = referenceKey(next); + if (targets.has(key)) return true; + if (seen.has(key)) continue; + seen.add(key); + for (const field of Object.values(requiredValueObject(storage, next).fields)) { + if (field.type.kind === 'valueObject') pending.push(field.type); + } + } + return false; +} + +function requiredValueObject( + storage: Pick, + reference: EntityCoordinate<'storage', 'valueObject'>, +): ContractValueObject { + const valueObject = valueObjectAt(storage, reference); + invariant( + valueObject !== undefined, + `value object "${reference.entityName}" in namespace "${reference.namespaceId}" is not in storage; the contract validators refuse a reference to a missing value object`, + ); + return valueObject; +} + +function valueSetValues( + storage: Pick, + reference: EntityCoordinate<'storage', 'valueSet'>, +): readonly (string | number)[] { + const valueSet = Object.hasOwn(storage.namespaces, reference.namespaceId) + ? storage.namespaces[reference.namespaceId]?.entries?.['valueSet']?.[reference.entityName] + : undefined; + invariant( + isStorageValueSet(valueSet), + `value set "${reference.entityName}" in namespace "${reference.namespaceId}" is not in storage; the contract validators refuse a value object member whose value set is missing`, + ); + return valueSet.values.filter( + (value): value is string | number => typeof value === 'string' || typeof value === 'number', + ); +} + +function referenceKey(reference: EntityCoordinate<'storage', 'valueObject'>): string { + return JSON.stringify([reference.namespaceId, reference.entityName]); +} diff --git a/packages/2-sql-family/1-core/contract/src/codec-json-type.ts b/packages/2-sql-family/1-core/contract/src/codec-json-type.ts new file mode 100644 index 000000000000..1386142c9f1f --- /dev/null +++ b/packages/2-sql-family/1-core/contract/src/codec-json-type.ts @@ -0,0 +1,16 @@ +export const JSON_TYPES = ['string', 'number', 'boolean', 'object', 'array', 'any'] as const; + +/** + * The JSON type a codec's value is written as inside a JSON document. `'any'` means the value can be any JSON value, including `null`, as for a JSON codec. + */ +export type JsonType = (typeof JSON_TYPES)[number]; + +export function isJsonType(value: unknown): value is JsonType { + return JSON_TYPES.some((jsonType) => jsonType === value); +} + +/** The JSON type a codec descriptor declares, or `undefined` when it declares none, as a descriptor compiled before codecs declared one does. */ +export function declaredJsonType(descriptor: object): JsonType | undefined { + const jsonType = Reflect.get(descriptor, 'jsonType'); + return isJsonType(jsonType) ? jsonType : undefined; +} diff --git a/packages/2-sql-family/1-core/contract/src/contract-errors.ts b/packages/2-sql-family/1-core/contract/src/contract-errors.ts index 333cbe1802c7..565f20612377 100644 --- a/packages/2-sql-family/1-core/contract/src/contract-errors.ts +++ b/packages/2-sql-family/1-core/contract/src/contract-errors.ts @@ -6,6 +6,7 @@ type ContractCode = `CONTRACT.${ContractSubcode}`; type ContractSubcode = | 'ARGUMENT_INVALID' | 'CODEC_DESCRIPTOR_MISSING' + | 'CODEC_JSON_TYPE_MISSING' | 'DATA_TYPE_UNREGISTERED' | 'INDEX_INVALID' | 'PACK_CONTRIBUTION_INVALID' diff --git a/packages/2-sql-family/1-core/contract/src/contract-view.ts b/packages/2-sql-family/1-core/contract/src/contract-view.ts index 1f6ff8de375c..5d506ffa0431 100644 --- a/packages/2-sql-family/1-core/contract/src/contract-view.ts +++ b/packages/2-sql-family/1-core/contract/src/contract-view.ts @@ -13,7 +13,7 @@ import type { SqlStorage } from './ir/sql-storage'; * `valueSet` are promoted to top-level view accessors; pack-contributed kinds * (e.g. `policy`) stay under `.entries`. */ -export const SQL_BUILTIN_KINDS = ['table', 'valueSet'] as const; +export const SQL_BUILTIN_KINDS = ['table', 'valueSet', 'valueObject'] as const; export type SqlBuiltinKind = (typeof SQL_BUILTIN_KINDS)[number]; type SqlEntries> = DefaultNamespaceEntries< diff --git a/packages/2-sql-family/1-core/contract/src/entity-kinds.ts b/packages/2-sql-family/1-core/contract/src/entity-kinds.ts index 56c94fde6a8b..d9e656d5d8a5 100644 --- a/packages/2-sql-family/1-core/contract/src/entity-kinds.ts +++ b/packages/2-sql-family/1-core/contract/src/entity-kinds.ts @@ -6,8 +6,13 @@ import { ifDefined } from '@internal/utils/defined'; import { contractError } from './contract-errors'; import { CheckConstraint } from './ir/check-constraint'; import { Index } from './ir/sql-index'; -import { StorageTableSchema, StorageValueSetSchema } from './ir/storage-entry-schemas'; +import { + SqlStorageValueObjectSchema, + StorageTableSchema, + StorageValueSetSchema, +} from './ir/storage-entry-schemas'; import { StorageTable, type StorageTableInput } from './ir/storage-table'; +import { StorageValueObject, type StorageValueObjectInput } from './ir/storage-value-object'; import { StorageValueSet, type StorageValueSetInput } from './ir/storage-value-set'; import { checkConstraintInputFromSerialized, @@ -47,9 +52,18 @@ export const valueSetEntityKind: EntityKindDescriptor new StorageValueSet(input), }; +export const valueObjectEntityKind: EntityKindDescriptor< + StorageValueObjectInput, + StorageValueObject +> = { + kind: 'valueObject', + schema: SqlStorageValueObjectSchema, + construct: (input) => new StorageValueObject(input), +}; + /** * Assembles the `kind → descriptor` registry for SQL namespaces: the built-in - * `table` and `valueSet` kinds plus any target `packKinds`. This builds the + * `table`, `valueSet` and `valueObject` kinds plus any target `packKinds`. This builds the * lookup table — it does not touch contract data. `hydrateNamespaceEntities` * later consumes this registry to turn a namespace's raw entries into IR * instances, and `createSqlContractSchema` derives validation from the same @@ -61,6 +75,7 @@ export function composeSqlEntityKinds( const kinds = new Map([ ['table', tableEntityKind], ['valueSet', valueSetEntityKind], + ['valueObject', valueObjectEntityKind], ]); for (const descriptor of packKinds) { if (kinds.has(descriptor.kind)) { diff --git a/packages/2-sql-family/1-core/contract/src/exports/check-expressions.ts b/packages/2-sql-family/1-core/contract/src/exports/check-expressions.ts new file mode 100644 index 000000000000..7b09c7de9980 --- /dev/null +++ b/packages/2-sql-family/1-core/contract/src/exports/check-expressions.ts @@ -0,0 +1,12 @@ +export { + type CheckExpressionCandidate, + type CheckExpressionInput, + type CheckExpressionRenderer, + discriminatorValueSet, + type ResolvedColumnValueObject, + type ResolvedValueObject, + type ResolvedValueObjectField, + type ResolvedValueObjectFieldType, + resolveColumnValueObject, + resolveValueObject, +} from '../check-expressions'; diff --git a/packages/2-sql-family/1-core/contract/src/exports/codec-json-type.ts b/packages/2-sql-family/1-core/contract/src/exports/codec-json-type.ts new file mode 100644 index 000000000000..1618f4fe8193 --- /dev/null +++ b/packages/2-sql-family/1-core/contract/src/exports/codec-json-type.ts @@ -0,0 +1,6 @@ +export { + declaredJsonType, + isJsonType, + JSON_TYPES, + type JsonType, +} from '../codec-json-type'; diff --git a/packages/2-sql-family/1-core/contract/src/exports/entity-kinds.ts b/packages/2-sql-family/1-core/contract/src/exports/entity-kinds.ts index f20c4c0bae42..77726715bfde 100644 --- a/packages/2-sql-family/1-core/contract/src/exports/entity-kinds.ts +++ b/packages/2-sql-family/1-core/contract/src/exports/entity-kinds.ts @@ -1,5 +1,6 @@ export { composeSqlEntityKinds, tableEntityKind, + valueObjectEntityKind, valueSetEntityKind, } from '../entity-kinds'; diff --git a/packages/2-sql-family/1-core/contract/src/exports/types.ts b/packages/2-sql-family/1-core/contract/src/exports/types.ts index bd1fad6ec571..3d48e3864a75 100644 --- a/packages/2-sql-family/1-core/contract/src/exports/types.ts +++ b/packages/2-sql-family/1-core/contract/src/exports/types.ts @@ -67,6 +67,7 @@ export type { StorageTableInput, StorageTypeInstance, StorageTypeInstanceInput, + StorageValueObjectInput, StorageValueSetInput, TypeMaps, TypeMapsPhantomKey, @@ -94,6 +95,7 @@ export { SqlStorage, StorageColumn, StorageTable, + StorageValueObject, StorageValueSet, toStorageTypeInstance, UniqueConstraint, diff --git a/packages/2-sql-family/1-core/contract/src/ir/sql-storage.ts b/packages/2-sql-family/1-core/contract/src/ir/sql-storage.ts index d7343eed0012..e1cfbc266715 100644 --- a/packages/2-sql-family/1-core/contract/src/ir/sql-storage.ts +++ b/packages/2-sql-family/1-core/contract/src/ir/sql-storage.ts @@ -16,6 +16,7 @@ import { type StorageTypeInstanceInput, toStorageTypeInstance, } from './storage-type-instance'; +import type { StorageValueObject } from './storage-value-object'; import type { StorageValueSet } from './storage-value-set'; /** @@ -91,13 +92,14 @@ export interface SqlStorageInput { */ /** * The typed `entries` shape for SQL family namespaces. The open dictionary - * is intersected with optional known-kind maps so that `ns.entries.table` - * and `ns.entries.valueSet` resolve without a cast, while unknown pack- + * is intersected with optional known-kind maps so that `ns.entries.table`, + * `ns.entries.valueSet` and `ns.entries.valueObject` resolve without a cast, while unknown pack- * contributed kinds remain valid (the `Record` part allows any string key). */ export type SqlNamespaceEntries = Readonly>>> & { readonly table?: Readonly>; readonly valueSet?: Readonly>; + readonly valueObject?: Readonly>; }; /** diff --git a/packages/2-sql-family/1-core/contract/src/ir/storage-entry-schemas.ts b/packages/2-sql-family/1-core/contract/src/ir/storage-entry-schemas.ts index e23eab700984..aad349696c7b 100644 --- a/packages/2-sql-family/1-core/contract/src/ir/storage-entry-schemas.ts +++ b/packages/2-sql-family/1-core/contract/src/ir/storage-entry-schemas.ts @@ -1,3 +1,4 @@ +import { ContractFieldTypeSchema, ContractValueObjectFieldSchema } from '@internal/contract/types'; import { DATA_TYPE_ID_PATTERN } from '@internal/framework-components/codec'; import { type Type, type } from 'arktype'; import type { ForeignKeyIndex, ForeignKeyInput, ReferentialAction } from './foreign-key'; @@ -35,6 +36,18 @@ const StorageValueSetRefSchema = type({ 'spaceId?': 'string', }); +/** + * The framework's value object schema, without what SQL does not store: no member is a union or a dictionary, since the derived check cannot enforce them. + */ +export const SqlStorageValueObjectSchema = type({ + '+': 'reject', + fields: type({ + '[string]': ContractValueObjectFieldSchema.omit('dict') + .merge({ type: ContractFieldTypeSchema.exclude({ kind: "'union'" }) }) + .onUndeclaredKey('reject'), + }), +}); + const StorageColumnSchema = type({ '+': 'reject', dataType: DATA_TYPE_ID_PATTERN, diff --git a/packages/2-sql-family/1-core/contract/src/ir/storage-value-object.ts b/packages/2-sql-family/1-core/contract/src/ir/storage-value-object.ts new file mode 100644 index 000000000000..33c80c2f36e1 --- /dev/null +++ b/packages/2-sql-family/1-core/contract/src/ir/storage-value-object.ts @@ -0,0 +1,32 @@ +import type { ContractValueObjectField } from '@internal/contract/types'; +import { freezeNode } from '@internal/framework-components/ir'; +import { SqlNode } from './sql-node'; + +/** Hydration / construction input shape for {@link StorageValueObject}; mirrors the stored JSON. */ +export interface StorageValueObjectInput { + readonly fields: Readonly>; +} + +/** + * SQL Contract IR node for a value object in a namespace's `valueObject` map (`SqlNamespace.entries.valueObject`): the keys every row holds in a column that stores it (ADR 309). A domain field refers to it by its storage coordinate. + * + * A field that leaves out `many` holds `many: false`, as the authoring builders write it, so a contract read from JSON equals the one built from its source. + * + * The entry's name is not on the class: value objects are keyed by name in the parent namespace's `valueObject` map. + */ +export class StorageValueObject extends SqlNode { + readonly fields: Readonly>; + + constructor(input: StorageValueObjectInput) { + super(); + this.fields = Object.freeze( + Object.fromEntries( + Object.entries(input.fields).map(([name, field]) => [ + name, + Object.freeze({ ...field, many: field.many ?? false }), + ]), + ), + ); + freezeNode(this); + } +} diff --git a/packages/2-sql-family/1-core/contract/src/types.ts b/packages/2-sql-family/1-core/contract/src/types.ts index 69325e6e305c..c87765fe33a2 100644 --- a/packages/2-sql-family/1-core/contract/src/types.ts +++ b/packages/2-sql-family/1-core/contract/src/types.ts @@ -48,6 +48,7 @@ export { type StorageTypeInstanceInput, toStorageTypeInstance, } from './ir/storage-type-instance'; +export { StorageValueObject, type StorageValueObjectInput } from './ir/storage-value-object'; export { isStorageValueSet, StorageValueSet, diff --git a/packages/2-sql-family/1-core/contract/src/validators.ts b/packages/2-sql-family/1-core/contract/src/validators.ts index eb6e916b4c1e..087f7187268a 100644 --- a/packages/2-sql-family/1-core/contract/src/validators.ts +++ b/packages/2-sql-family/1-core/contract/src/validators.ts @@ -2,10 +2,15 @@ import { ContractValidationError } from '@internal/contract/contract-validation- import { type Contract, ContractExecutionSectionSchema, + ContractFieldTypeSchema, type ContractModel, CrossReferenceSchema, } from '@internal/contract/types'; -import { validateContractDomain } from '@internal/contract/validate-domain'; +import { + removedDomainKeyProblems, + validateContractDomain, +} from '@internal/contract/validate-domain'; +import { validateStorageValueObjects } from '@internal/contract/validate-storage'; import { createDataTypeLookup, DATA_TYPE_ID_PATTERN, @@ -285,6 +290,36 @@ function removedKeyProblems(storage: unknown): readonly string[] { return []; } +/** + * A SQL value object member that is a union or a dictionary, named by its value object and member. The storage schema refuses it too, but at the path of its namespace, so this names it first. + */ +function unenforceableValueObjectMemberProblems(storage: unknown): readonly string[] { + return entriesOf(fieldOf(storage, 'namespaces')).flatMap(([namespaceId, namespace]) => + entriesOf(fieldOf(fieldOf(namespace, 'entries'), 'valueObject')).flatMap( + ([valueObjectName, valueObject]) => + entriesOf(fieldOf(valueObject, 'fields')).flatMap(([memberName, member]) => { + const kind = + fieldOf(fieldOf(member, 'type'), 'kind') === 'union' + ? 'union' + : fieldOf(member, 'dict') === true + ? 'dictionary' + : undefined; + return kind === undefined + ? [] + : [ + `value object "${namespaceId}:${valueObjectName}" member "${memberName}" is a ${kind}; SQL value objects hold no unions or dictionaries, since their check cannot enforce them`, + ]; + }), + ), + ); +} + +/** The problems a stored contract has before its structure is validated: removed keys first, then value object members SQL cannot enforce. */ +function preStructuralStorageProblems(storage: unknown): readonly string[] { + const removedKeys = removedKeyProblems(storage); + return removedKeys.length > 0 ? removedKeys : unenforceableValueObjectMemberProblems(storage); +} + type NamespacedStorageWalk = { readonly namespaces: Readonly< Record< @@ -419,30 +454,6 @@ function rejectDuplicateWireNamedCheckContent( } } -function isContractFieldType(value: unknown): boolean { - if (!isPlainRecord(value)) return false; - const kind = value['kind']; - if (kind === 'scalar') { - if (typeof value['codecId'] !== 'string') return false; - const typeParams = value['typeParams']; - if (typeParams !== undefined && !isPlainRecord(typeParams)) return false; - return true; - } - if (kind === 'valueObject') { - return typeof value['name'] === 'string'; - } - if (kind === 'union') { - const members = value['members']; - if (!Array.isArray(members)) return false; - return members.every((m) => isContractFieldType(m)); - } - return false; -} - -const ContractFieldTypeSchema = type('unknown').narrow((value, ctx) => - isContractFieldType(value) ? true : ctx.mustBe('scalar, valueObject, or union field type'), -); - const ModelFieldSchema = type({ '+': 'reject', nullable: 'boolean', @@ -552,9 +563,6 @@ export function createSqlContractSchema( namespaces: type({ '[string]': type({ models: type({ '[string]': ModelSchema }), - 'valueObjects?': type({ - '[string]': type({ fields: type({ '[string]': ModelFieldSchema }) }), - }), 'enum?': type({ '[string]': ContractEnumSchema }), }), }), @@ -576,11 +584,11 @@ const SqlContractSchema = createSqlContractSchema(DEFAULT_SQL_KINDS); * @throws Error if the storage structure is invalid */ export function validateStorage(value: unknown): void { - const removedKeys = removedKeyProblems(value); + const preStructuralProblems = preStructuralStorageProblems(value); const result = StorageSchema(value); const errors = - removedKeys.length > 0 - ? [...removedKeys] + preStructuralProblems.length > 0 + ? [...preStructuralProblems] : result instanceof type.errors ? result.map((p: { message: string }) => p.message) : []; @@ -632,10 +640,13 @@ function validateSqlContractStructure>( throw new ContractValidationError(`Unsupported target family: ${targetFamily}`, 'structural'); } - const removedKeys = removedKeyProblems(fieldOf(value, 'storage')); - if (removedKeys.length > 0) { + const preStructuralProblems = [ + ...removedDomainKeyProblems(value), + ...preStructuralStorageProblems(fieldOf(value, 'storage')), + ]; + if (preStructuralProblems.length > 0) { throw new ContractValidationError( - `Contract structural validation failed: ${removedKeys.join('; ')}`, + `Contract structural validation failed: ${preStructuralProblems.join('; ')}`, 'structural', ); } @@ -1002,6 +1013,7 @@ export function validateExecutionDefaultsTargetMappedColumns(contract: Contract< * counts match their referenced columns. Throws on the first mismatch. */ export function validateSqlStorageConsistency(contract: Contract): void { + validateStorageValueObjects(contract.storage); for (const { namespaceId, tableName, table: rawTable } of eachStorageTable(contract.storage)) { const table = blindCast( rawTable, @@ -1146,6 +1158,7 @@ export function validateSqlContractFully>( validateContractDomain({ roots: validated.roots, domain: validated.domain, + storage: validated.storage, }); validateSqlStorageConsistency(validated); const semanticErrors = validateStorageSemantics(validated.storage); diff --git a/packages/2-sql-family/1-core/contract/test/check-expressions.test.ts b/packages/2-sql-family/1-core/contract/test/check-expressions.test.ts new file mode 100644 index 000000000000..3a0874b404c0 --- /dev/null +++ b/packages/2-sql-family/1-core/contract/test/check-expressions.test.ts @@ -0,0 +1,219 @@ +import type { AnyCodecDescriptor } from '@internal/framework-components/codec'; +import { describe, expect, it } from 'vitest'; +import { + discriminatorValueSet, + resolveColumnValueObject, + resolveValueObject, +} from '../src/check-expressions'; + +function valueObjectRef(name: string) { + return { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: name, + } as const; +} + +const text = { nullable: false, many: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }; + +function nested(name: string, extra: Record = {}) { + return { + nullable: false, + many: false, + type: { kind: 'valueObject', ...valueObjectRef(name) }, + ...extra, + }; +} + +const codecs = { + descriptorFor: (codecId: string) => + (codecId === 'pg/text@1' ? { codecId, jsonType: 'string' } : { codecId }) as AnyCodecDescriptor, +}; + +function storageWith(entries: Record>) { + return { namespaces: { public: { id: 'public', entries } } }; +} + +describe('resolveValueObject', () => { + it('inlines nested value objects and reads enum members from their value sets', () => { + const storage = storageWith({ + valueObject: { + Address: { + fields: { + street: text, + kind: { + nullable: true, + many: false, + type: { kind: 'scalar', codecId: 'pg/text@1' }, + valueSet: { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueSet', + entityName: 'AddressKind', + }, + }, + geo: nested('Geo', { many: { elementNullable: true } }), + }, + }, + Geo: { fields: { lat: text } }, + }, + valueSet: { AddressKind: { kind: 'valueSet', values: ['home', 'work'] } }, + }); + + expect(resolveValueObject(storage, valueObjectRef('Address'), codecs)).toEqual({ + fields: { + street: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + kind: { + nullable: true, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: ['home', 'work'] }, + }, + geo: { + nullable: false, + many: { elementNullable: true }, + type: { + kind: 'valueObject', + valueObject: { + fields: { + lat: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + }, + }, + }, + }, + }, + }); + }); + + it('accepts any object where a value object that contains itself refers back to itself', () => { + const storage = storageWith({ + valueObject: { + NavItem: { + fields: { + label: text, + children: nested('NavItem', { many: { elementNullable: false } }), + }, + }, + }, + }); + + expect(resolveValueObject(storage, valueObjectRef('NavItem'), codecs)).toEqual({ + fields: { + label: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + children: { + nullable: false, + many: { elementNullable: false }, + type: { kind: 'anyObject' }, + }, + }, + }); + }); + + it('accepts any object at the first reference back into a group of value objects that contain each other', () => { + const storage = storageWith({ + valueObject: { + Folder: { fields: { name: text, entry: nested('Entry'), meta: nested('Meta') } }, + Entry: { fields: { parent: nested('Folder') } }, + Meta: { fields: { owner: text } }, + }, + }); + + expect(resolveValueObject(storage, valueObjectRef('Folder'), codecs)).toEqual({ + fields: { + name: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + entry: { nullable: false, many: false, type: { kind: 'anyObject' } }, + meta: { + nullable: false, + many: false, + type: { + kind: 'valueObject', + valueObject: { + fields: { + owner: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + }, + }, + }, + }, + }, + }); + }); + + it('refuses a member whose codec declares no JSON type, naming the codec, the member and what to add', () => { + const storage = storageWith({ + valueObject: { + Address: { + fields: { zip: { nullable: false, type: { kind: 'scalar', codecId: 'demo/zip@1' } } }, + }, + }, + }); + expect(() => resolveValueObject(storage, valueObjectRef('Address'), codecs)).toThrow( + expect.objectContaining({ + code: 'CONTRACT.CODEC_JSON_TYPE_MISSING', + message: + 'The codec "demo/zip@1" of value object "public:Address" member "zip" declares no jsonType, so the check of the value object cannot be derived.', + meta: { + codecId: 'demo/zip@1', + valueObject: 'public:Address', + member: 'zip', + property: 'jsonType', + }, + }), + ); + }); + + it('refuses a reference storage does not hold', () => { + expect(() => resolveValueObject(storageWith({}), valueObjectRef('Missing'), codecs)).toThrow( + /Missing/, + ); + }); +}); + +describe('resolveColumnValueObject', () => { + it('holds the column nullability and list shape with the resolved value object', () => { + const storage = storageWith({ valueObject: { Address: { fields: {} } } }); + expect( + resolveColumnValueObject( + storage, + { + reference: valueObjectRef('Address'), + nullable: true, + many: { elementNullable: false }, + }, + codecs, + ), + ).toEqual({ + nullable: true, + many: { elementNullable: false }, + valueObject: resolveValueObject(storage, valueObjectRef('Address'), codecs), + }); + }); +}); + +describe('discriminatorValueSet', () => { + it('is keyed by the base model and field, and holds the variant values sorted', () => { + expect(discriminatorValueSet('Task', 'type', ['feature', 'bug'])).toEqual({ + name: 'Task.type', + values: ['bug', 'feature'], + }); + }); +}); diff --git a/packages/2-sql-family/1-core/contract/test/storage-value-object.test.ts b/packages/2-sql-family/1-core/contract/test/storage-value-object.test.ts new file mode 100644 index 000000000000..38d2bb146a57 --- /dev/null +++ b/packages/2-sql-family/1-core/contract/test/storage-value-object.test.ts @@ -0,0 +1,254 @@ +import type { ContractModel } from '@internal/contract/types'; +import { hydrateNamespaceEntities, UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; +import { blindCast } from '@internal/utils/casts'; +import { createContract } from '@repo/test-utils'; +import { type } from 'arktype'; +import { describe, expect, it } from 'vitest'; +import { composeSqlEntityKinds } from '../src/entity-kinds'; +import { col, model, table } from '../src/factories'; +import { StorageValueObject } from '../src/ir/storage-value-object'; +import type { SqlStorage } from '../src/types'; +import { createSqlStorageSchema, validateSqlContractFully } from '../src/validators'; + +const text = { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } } as const; + +function valueObjectType(name: string, namespaceId: string = UNBOUND_NAMESPACE_ID) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName: name, + } as const; +} + +const address = { fields: { street: text, city: text } }; + +function storageWith(valueObject: Record) { + return { + namespaces: { + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'test-sql-namespace', + entries: { + table: { + user: table({ + id: col('pg/int4', 'pg/int4@1'), + address: col('pg/jsonb', 'pg/jsonb@1'), + }), + }, + valueObject, + }, + }, + }, + }; +} + +function contractWith( + addressType: unknown, + valueObject: Record = { Address: address }, +) { + return createContract({ + storage: blindCast( + storageWith(valueObject), + ), + models: { + User: blindCast({ + ...model('user', { id: { column: 'id' }, address: { column: 'address' } }), + fields: { + id: { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, + address: { nullable: false, type: addressType }, + }, + }), + }, + }); +} + +const addressWithMany = { + fields: { street: { ...text, many: false }, city: { ...text, many: false } }, +}; + +describe('StorageValueObject', () => { + it('holds the fields it is given, with many false where a field leaves it out, frozen', () => { + const valueObject = new StorageValueObject(address); + expect({ + fields: valueObject.fields, + frozen: Object.isFrozen(valueObject), + fieldFrozen: Object.isFrozen(valueObject.fields['street']), + }).toEqual({ fields: addressWithMany.fields, frozen: true, fieldFrozen: true }); + }); + + it('keeps the many a field gives', () => { + const tags = { ...text, many: { elementNullable: true } }; + expect(new StorageValueObject({ fields: { tags } }).fields).toEqual({ tags }); + }); + + it('serializes to its fields only', () => { + expect(JSON.parse(JSON.stringify(new StorageValueObject(address)))).toEqual(addressWithMany); + }); + + it('is hydrated from the valueObject slot', () => { + const hydrated = hydrateNamespaceEntities( + { valueObject: { Address: address } }, + composeSqlEntityKinds(), + 'fail', + ); + expect(hydrated['valueObject']?.['Address']).toBeInstanceOf(StorageValueObject); + }); +}); + +describe('the storage valueObject slot schema', () => { + const schema = createSqlStorageSchema(composeSqlEntityKinds()); + + function validate(valueObject: Record) { + return schema({ storageHash: 'test', ...storageWith(valueObject) }); + } + + it('accepts scalar, list, nested and self-referencing members', () => { + expect( + validate({ + Address: address, + Profile: { + fields: { + home: { nullable: true, type: valueObjectType('Address') }, + tags: { ...text, many: { elementNullable: false } }, + parent: { nullable: true, type: valueObjectType('Profile') }, + }, + }, + }), + ).not.toBeInstanceOf(type.errors); + }); + + it.each([ + [ + 'a union', + { + nullable: false, + type: { kind: 'union', members: [text.type, valueObjectType('Address')] }, + }, + ], + ['a dictionary', { ...text, dict: true }], + ])('refuses a member that is %s', (_case, member) => { + expect(validate({ Address: address, Shape: { fields: { either: member } } })).toBeInstanceOf( + type.errors, + ); + }); + + it('refuses a member key it does not know', () => { + expect(validate({ Address: { fields: { city: { ...text, column: 'city' } } } })).toBeInstanceOf( + type.errors, + ); + }); + + it('refuses a member value set that points into the domain plane', () => { + expect( + validate({ + Address: { + fields: { + kind: { + ...text, + valueSet: { + plane: 'domain', + namespaceId: UNBOUND_NAMESPACE_ID, + entityKind: 'enum', + entityName: 'Kind', + }, + }, + }, + }, + }), + ).toBeInstanceOf(type.errors); + }); + + it('refuses a member reference that is not a storage value object coordinate', () => { + expect( + validate({ + Wrapper: { + fields: { inner: { nullable: false, type: { kind: 'valueObject', name: 'Address' } } }, + }, + }), + ).toBeInstanceOf(type.errors); + }); +}); + +describe('validateSqlContractFully with a value object in storage', () => { + it('accepts a field that refers to the value object by its storage coordinate', () => { + expect(() => validateSqlContractFully(contractWith(valueObjectType('Address')))).not.toThrow(); + }); + + it('refuses a field that refers to a value object storage does not have', () => { + expect(() => validateSqlContractFully(contractWith(valueObjectType('Missing')))).toThrow( + 'Model "__unbound__:User" field "address" refers to value object "Missing" in storage namespace "__unbound__", which storage.namespaces.__unbound__.entries.valueObject does not have', + ); + }); + + it('accepts a nested value object and one that refers to itself', () => { + const contract = contractWith(valueObjectType('Profile'), { + Address: address, + Profile: { + fields: { + home: { nullable: true, type: valueObjectType('Address') }, + parent: { nullable: true, type: valueObjectType('Profile') }, + }, + }, + }); + expect(() => validateSqlContractFully(contract)).not.toThrow(); + }); + + it('refuses a domain field whose type is a union inside a union, as the framework field type schema does', () => { + expect(() => + validateSqlContractFully( + contractWith({ + kind: 'union', + members: [text.type, { kind: 'union', members: [text.type] }], + }), + ), + ).toThrow(/Contract structural validation failed/); + }); + + it('refuses a field type that names its value object the old way', () => { + expect(() => + validateSqlContractFully(contractWith({ kind: 'valueObject', name: 'Address' })), + ).toThrow(/Contract structural validation failed/); + }); + + it.each([ + [ + 'a union', + { + nullable: false, + type: { kind: 'union', members: [text.type, valueObjectType('Address')] }, + }, + 'union', + ], + ['a dictionary', { ...text, dict: true }, 'dictionary'], + ])('refuses a member that is %s, naming the member', (_case, member, kind) => { + const contract = contractWith(valueObjectType('Shape'), { + Address: address, + Shape: { fields: { either: member } }, + }); + expect(() => validateSqlContractFully(contract)).toThrow( + expect.objectContaining({ + message: `Contract structural validation failed: value object "__unbound__:Shape" member "either" is a ${kind}; SQL value objects hold no unions or dictionaries, since their check cannot enforce them`, + }), + ); + }); + + it('refuses value objects in the domain plane, and says to re-emit', () => { + const contract = contractWith(valueObjectType('Address')); + const old = { + ...contract, + domain: { + namespaces: { + [UNBOUND_NAMESPACE_ID]: { + ...contract.domain.namespaces[UNBOUND_NAMESPACE_ID], + valueObjects: { Address: address }, + }, + }, + }, + }; + expect(() => validateSqlContractFully(old)).toThrow( + 'Contract structural validation failed: domain.namespaces.__unbound__.valueObjects: contracts no longer store value objects in the domain plane; they are storage entities under storage.namespaces..entries.valueObject, so re-emit the contract from its source, or rewrite a stored contract such as a migration snapshot with value-objects-in-storage.ts, which ships in the value-objects-in-storage scripts of the Prisma 8 upgrade guide', + ); + }); +}); diff --git a/packages/2-sql-family/1-core/contract/test/test-type-lookups.ts b/packages/2-sql-family/1-core/contract/test/test-type-lookups.ts index 045a628e720b..ad3616186bd4 100644 --- a/packages/2-sql-family/1-core/contract/test/test-type-lookups.ts +++ b/packages/2-sql-family/1-core/contract/test/test-type-lookups.ts @@ -4,7 +4,7 @@ * Every codec id represents the data type the real stack gives it (`CODEC_DATA_TYPES`), else the * codec id without its version. A data type is written as the name in `names` (by codec id), * `CODEC_TYPE_NAMES` or `DATA_TYPE_NAMES`, else its name part. Each codec stores its values as - * authored and takes any parameters. + * authored and takes any parameters, and declares the JSON type in `CODEC_JSON_TYPES`, else `any`. */ import type { JsonValue } from '@internal/contract/types'; @@ -77,6 +77,14 @@ const DATA_TYPE_NAMES: Readonly> = { 'sqlite/character-varying': 'character varying', }; +const CODEC_JSON_TYPES: Readonly> = { + 'pg/text@1': 'string', + 'pg/int4@1': 'number', + 'pg/bool@1': 'boolean', + 'sql/text@1': 'string', + 'sql/int@1': 'number', +}; + const ENUM_CODEC_IDS: ReadonlySet = new Set(['pg/enum@1']); const ENUM_DATA_TYPE_IDS: ReadonlySet = new Set(['pg/enum']); @@ -202,10 +210,11 @@ export function testSqlTypeLookups( if (own.dataType !== undefined) return own; return { ...own, dataType: dataTypeOfCodec(codecId).id }; } - return { + const descriptor = { codecId, + jsonType: CODEC_JSON_TYPES[codecId] ?? 'any', dataType: dataTypeOfCodec(codecId).id, - traits: ['equality'], + traits: ['equality'] as const, paramsSchema: acceptAnything, isParameterized: true, factory: () => () => @@ -214,6 +223,7 @@ export function testSqlTypeLookups( 'a test codec lookup may have no codec for this id, as a real one may not' >(codecOf(codecId)), }; + return descriptor; }; return { diff --git a/packages/2-sql-family/1-core/contract/test/validators.test.ts b/packages/2-sql-family/1-core/contract/test/validators.test.ts index fed3b1e05afb..c92f5a31d756 100644 --- a/packages/2-sql-family/1-core/contract/test/validators.test.ts +++ b/packages/2-sql-family/1-core/contract/test/validators.test.ts @@ -84,41 +84,38 @@ describe('SQL contract validators', () => { }); it.each([ - { metadata: {}, valid: true, many: false }, - { metadata: { many: false }, valid: true, many: false }, - { - metadata: { many: { elementNullable: false } }, - valid: true, - many: { elementNullable: false }, - }, - { metadata: { many: { elementNullable: true } }, valid: true, many: { elementNullable: true } }, - { metadata: { many: true }, valid: false, many: undefined }, - { metadata: { many: null }, valid: false, many: undefined }, - { metadata: { many: {} }, valid: false, many: undefined }, - { metadata: { many: { elementNullable: 'false' } }, valid: false, many: undefined }, - ])('validates and normalizes value-object cardinality $metadata', ({ metadata, valid, many }) => { + { metadata: {}, valid: true }, + { metadata: { many: false }, valid: true }, + { metadata: { many: { elementNullable: false } }, valid: true }, + { metadata: { many: { elementNullable: true } }, valid: true }, + { metadata: { many: true }, valid: false }, + { metadata: { many: null }, valid: false }, + { metadata: { many: {} }, valid: false }, + { metadata: { many: { elementNullable: 'false' } }, valid: false }, + ])('validates storage value-object cardinality $metadata', ({ metadata, valid }) => { const field = { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false, ...metadata }; const schema = createSqlContractSchema(composeSqlEntityKinds()); const result = schema({ target: 'postgres', targetFamily: 'sql', profileHash: 'test', - domain: { + domain: { namespaces: { [UNBOUND_NAMESPACE_ID]: { models: {} } } }, + storage: { + storageHash: 'test', namespaces: { [UNBOUND_NAMESPACE_ID]: { - models: {}, - valueObjects: { Address: { fields: { city: field } } }, + id: UNBOUND_NAMESPACE_ID, + entries: { valueObject: { Address: { fields: { city: field } } } }, }, }, }, - storage: { storageHash: 'test', namespaces: {} }, }); if (valid) { expect(result).toMatchObject({ - domain: { + storage: { namespaces: { [UNBOUND_NAMESPACE_ID]: { - valueObjects: { Address: { fields: { city: { ...field, many } } } }, + entries: { valueObject: { Address: { fields: { city: field } } } }, }, }, }, @@ -1021,23 +1018,47 @@ describe('SQL contract validators', () => { it('leaves the storage type of a value-object column to the stack', () => { const c = createContract({ - storage: unboundTables({ - user: table({ id: col('pg/int4', 'pg/int4@1'), address: col('pg/text', 'pg/text@1') }), + storage: blindCast({ + namespaces: { + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + kind: 'test-sql-namespace', + entries: { + table: { + user: table({ + id: col('pg/int4', 'pg/int4@1'), + address: col('pg/text', 'pg/text@1'), + }), + }, + valueObject: { + Address: { + fields: { + street: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }, + }, + }, + }, + }, + }, + }, }), models: { User: blindCast({ ...contractModel('user', { id: { column: 'id' }, address: { column: 'address' } }), fields: { id: { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, - address: { nullable: false, type: { kind: 'valueObject', name: 'Address' } }, + address: { + nullable: false, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: UNBOUND_NAMESPACE_ID, + entityKind: 'valueObject', + entityName: 'Address', + }, + }, }, }), }, - valueObjects: { - Address: { - fields: { street: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } } }, - }, - }, }); expect(() => validateSqlContractFully(c)).not.toThrow(); }); diff --git a/packages/2-sql-family/1-core/contract/test/value-type-maps.display.test.ts b/packages/2-sql-family/1-core/contract/test/value-type-maps.display.test.ts index 286203b1d074..ac547244a107 100644 --- a/packages/2-sql-family/1-core/contract/test/value-type-maps.display.test.ts +++ b/packages/2-sql-family/1-core/contract/test/value-type-maps.display.test.ts @@ -50,7 +50,7 @@ type Storage = { }; }; -type Fields = FieldTypesFromDomain; +type Fields = FieldTypesFromDomain; type Columns = ColumnTypesFromStorage; declare const fields: Fields; diff --git a/packages/2-sql-family/1-core/contract/tsdown.config.ts b/packages/2-sql-family/1-core/contract/tsdown.config.ts index 525f5528e2c9..a0cd7472e252 100644 --- a/packages/2-sql-family/1-core/contract/tsdown.config.ts +++ b/packages/2-sql-family/1-core/contract/tsdown.config.ts @@ -3,6 +3,8 @@ import { defineConfig } from '@repo/tsdown'; export default defineConfig({ entry: [ 'src/exports/authored-check-naming.ts', + 'src/exports/check-expressions.ts', + 'src/exports/codec-json-type.ts', 'src/exports/contract-view.ts', 'src/exports/data-type.ts', 'src/exports/data-type-support.ts', diff --git a/packages/2-sql-family/1-core/schema-ir/src/exports/naming.ts b/packages/2-sql-family/1-core/schema-ir/src/exports/naming.ts index df5d615cad41..0a9871c6cfd5 100644 --- a/packages/2-sql-family/1-core/schema-ir/src/exports/naming.ts +++ b/packages/2-sql-family/1-core/schema-ir/src/exports/naming.ts @@ -1,5 +1,6 @@ export { assertWireNamePrefixLength, + CHECK_KINDS, type CheckKind, composeCheckWirePrefix, computeCheckContentHash, diff --git a/packages/2-sql-family/1-core/schema-ir/src/naming.ts b/packages/2-sql-family/1-core/schema-ir/src/naming.ts index d16bfe124a44..9d92c0b643bd 100644 --- a/packages/2-sql-family/1-core/schema-ir/src/naming.ts +++ b/packages/2-sql-family/1-core/schema-ir/src/naming.ts @@ -158,12 +158,13 @@ export function computeCheckContentHash(expression: string): string { * The canonical spelling of this union — every layer that can depend on this * module imports it rather than re-spelling the literals. */ -export type CheckKind = 'membership' | 'elementNotNull'; +export type CheckKind = 'membership' | 'elementNotNull' | 'shape'; /** The trailing segment a check's wire-name prefix carries, per kind. */ const CHECK_KIND_SUFFIX = { membership: 'check', elementNotNull: 'elem_not_null', + shape: 'shape', } as const; /** @@ -185,7 +186,7 @@ export function composeCheckWirePrefix( * Every {@link CheckKind} value, read off {@link CHECK_KIND_SUFFIX}'s own keys * so the enumeration can never drift out of step with it. */ -const CHECK_KINDS: readonly CheckKind[] = Object.keys(CHECK_KIND_SUFFIX).filter( +export const CHECK_KINDS: readonly CheckKind[] = Object.keys(CHECK_KIND_SUFFIX).filter( (kind): kind is CheckKind => kind in CHECK_KIND_SUFFIX, ); diff --git a/packages/2-sql-family/1-core/schema-ir/test/naming.test.ts b/packages/2-sql-family/1-core/schema-ir/test/naming.test.ts index 564de288daeb..ccb154d0d570 100644 --- a/packages/2-sql-family/1-core/schema-ir/test/naming.test.ts +++ b/packages/2-sql-family/1-core/schema-ir/test/naming.test.ts @@ -92,13 +92,15 @@ describe('the naming union', () => { }); describe('composeCheckWirePrefix', () => { - it('spells the membership and element-not-null kinds distinctly', () => { + it('spells each kind distinctly', () => { expect({ membership: composeCheckWirePrefix('User', 'role', 'membership'), elementNotNull: composeCheckWirePrefix('User', 'tags', 'elementNotNull'), + shape: composeCheckWirePrefix('User', 'address', 'shape'), }).toEqual({ membership: 'User_role_check', elementNotNull: 'User_tags_elem_not_null', + shape: 'User_address_shape', }); }); @@ -314,8 +316,10 @@ describe('derivedCheckPrefixes', () => { new Set([ composeCheckWirePrefix('User', 'role', 'membership'), composeCheckWirePrefix('User', 'role', 'elementNotNull'), + composeCheckWirePrefix('User', 'role', 'shape'), composeCheckWirePrefix('User', 'tags', 'membership'), composeCheckWirePrefix('User', 'tags', 'elementNotNull'), + composeCheckWirePrefix('User', 'tags', 'shape'), ]), ); }); @@ -339,6 +343,7 @@ describe('derivedCheckPrefixes', () => { new Set([ 'custom_oauth_providers_acceptable_client_ids_check', 'custom_oauth_providers_acceptable_client_ids_elem_not_', + 'custom_oauth_providers_acceptable_client_ids_shape', ]), ); }); diff --git a/packages/2-sql-family/2-authoring/contract-psl/src/interpreter.ts b/packages/2-sql-family/2-authoring/contract-psl/src/interpreter.ts index 01bd0e5dd862..b3f4d5a7d869 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/src/interpreter.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/src/interpreter.ts @@ -5,6 +5,7 @@ import type { } from '@internal/config/config-types'; import type { Contract, + ContractDiscriminator, ContractModel, ControlPolicy, CrossReference, @@ -1776,31 +1777,35 @@ function modelLabel(namespaceId: string, modelName: string, qualified: boolean): return qualified ? `${namespaceId}.${modelName}` : modelName; } -function resolvePolymorphism( - models: Record, - physicalNames: ReadonlyMap, +/** What `@@discriminator` and `@@base` declare, by model key, as the build writes it into the domain models and the storage. */ +type ResolvedPolymorphism = { + readonly discriminators: ReadonlyMap< + string, + { + readonly discriminator: ContractDiscriminator; + readonly variants: Record; + } + >; + readonly bases: ReadonlyMap; +}; + +/** + * Pairs each `@@discriminator` with the variants that declare `@@base` on it, before the build, so the build writes the discriminator's value set before it computes the storage hash (ADR 309). Reports a model with both attributes, a discriminator no variant names, a duplicate value, and a base with no discriminator. + */ +function resolveVariants( + modelKeys: ReadonlySet, discriminatorDeclarations: Map, baseDeclarations: Map, reported: ReportedPolymorphism, - syntheticPkFieldsByVariant: ReadonlyMap, - stiBaseFieldsByBase: ReadonlyMap, diagnostics: PslDiagnosticCollector, -): Record { - let patched = models; - - // STI variant columns were materialised onto the base storage table so the - // variants' `storage.fields` resolve. They are storage-only on the base — the - // domain field belongs to the variant — so strip them from the base model's - // domain + storage field maps (the table column, built upstream, stays). - for (const [baseKey, fieldNames] of stiBaseFieldsByBase) { - const baseModel = patched[baseKey]; - if (!baseModel || fieldNames.length === 0) continue; - patched = { - ...patched, - [baseKey]: stripStorageOnlyDomainFields(baseModel, fieldNames), - }; - } - +): ResolvedPolymorphism { + const discriminators = new Map< + string, + { + readonly discriminator: ContractDiscriminator; + readonly variants: Record; + } + >(); for (const [modelKey, decl] of discriminatorDeclarations) { const modelName = decl.model.name; if (baseDeclarations.has(modelKey)) { @@ -1811,16 +1816,12 @@ function resolvePolymorphism( }); continue; } - - const model = patched[modelKey]; - if (!model) continue; + if (!modelKeys.has(modelKey)) continue; const variants: Record = {}; - for (const baseDecl of baseDeclarations.values()) { if (baseDecl.base.key !== modelKey || baseDecl.value === undefined) continue; const variantName = baseDecl.model.name; - const existingVariant = variants[baseDecl.value]; if (existingVariant) { const qualified = @@ -1848,20 +1849,16 @@ function resolvePolymorphism( }); continue; } - - patched = { - ...patched, - [modelKey]: { ...model, discriminator: { field: decl.fieldName }, variants }, - }; + discriminators.set(modelKey, { discriminator: { field: decl.fieldName }, variants }); } + const bases = new Map(); for (const [variantKey, baseDecl] of baseDeclarations) { - const baseName = baseDecl.base.model.name; if (!discriminatorDeclarations.has(baseDecl.base.key)) { if (reported.discriminators.has(baseDecl.base.key)) continue; const qualified = baseDecl.namespaceId !== baseDecl.base.namespaceId; const variantLabel = modelLabel(baseDecl.namespaceId, baseDecl.model.name, qualified); - const baseLabel = modelLabel(baseDecl.base.namespaceId, baseName, qualified); + const baseLabel = modelLabel(baseDecl.base.namespaceId, baseDecl.base.model.name, qualified); diagnostics.push({ code: 'PSL_ORPHANED_BASE', message: `Model "${variantLabel}" declares @@base(${baseLabel}, ...) but "${baseLabel}" has no @@discriminator`, @@ -1869,13 +1866,56 @@ function resolvePolymorphism( }); continue; } + if (discriminatorDeclarations.has(variantKey) || !modelKeys.has(variantKey)) continue; + bases.set(variantKey, crossRef(baseDecl.base.model.name, baseDecl.base.namespaceId)); + } + return { discriminators, bases }; +} - if (discriminatorDeclarations.has(variantKey)) { - continue; - } +/** The model nodes with the discriminator, variants and base the build writes. */ +function withPolymorphism( + modelNodes: readonly ModelNode[], + polymorphism: ResolvedPolymorphism, + defaultNamespaceId: string, +): ModelNode[] { + return modelNodes.map((node) => { + const key = modelCoordinateKey(node.namespaceId ?? defaultNamespaceId, node.modelName); + const discriminator = polymorphism.discriminators.get(key); + const base = polymorphism.bases.get(key); + return { + ...node, + ...(discriminator ?? {}), + ...ifDefined('base', base), + }; + }); +} +/** + * Points each variant without `@@map` at its base table, and strips from the domain the columns a variant's or a base's model only stores: STI columns on the base, and the link column of an MTI variant. The discriminator, the variants and the base are written by the build. + */ +function resolveVariantStorage( + models: Record, + physicalNames: ReadonlyMap, + baseDeclarations: Map, + polymorphism: ResolvedPolymorphism, + syntheticPkFieldsByVariant: ReadonlyMap, + stiBaseFieldsByBase: ReadonlyMap, +): Record { + let patched = models; + + for (const [baseKey, fieldNames] of stiBaseFieldsByBase) { + const baseModel = patched[baseKey]; + if (!baseModel || fieldNames.length === 0) continue; + patched = { + ...patched, + [baseKey]: stripStorageOnlyDomainFields(baseModel, fieldNames), + }; + } + + for (const variantKey of polymorphism.bases.keys()) { + const baseDecl = baseDeclarations.get(variantKey); const variantModel = patched[variantKey]; - if (!variantModel) continue; + if (!baseDecl || !variantModel) continue; const hasExplicitMap = getAttribute(baseDecl.model.attributes, 'map') !== undefined; const resolvedTable = storageName( @@ -1885,7 +1925,6 @@ function resolvePolymorphism( const patchedVariant: ContractModel = { ...variantModel, - base: crossRef(baseName, baseDecl.base.namespaceId), ...(resolvedTable ? { storage: { ...variantModel.storage, table: resolvedTable } } : {}), }; @@ -2194,13 +2233,26 @@ export function interpretPslDocumentToSqlContract( const models: ModelSymbol[] = []; const modelEntries: ModelNamespaceEntry[] = []; const compositeTypes: CompositeTypeSymbol[] = []; + const compositeTypeNamespaces = new Map(); for (const namespace of namespaceSymbols) { for (const model of Object.values(namespace.models)) { models.push(model); modelEntries.push({ model, namespaceId: namespace.id }); } - compositeTypes.push(...Object.values(namespace.compositeTypes)); + for (const compositeType of Object.values(namespace.compositeTypes)) { + const firstNamespaceId = compositeTypeNamespaces.get(compositeType.name); + if (firstNamespaceId !== undefined) { + diagnostics.push({ + code: 'PSL_DUPLICATE_DECLARATION', + message: `Composite type "${compositeType.name}" is declared in namespace "${firstNamespaceId}" and in namespace "${namespace.id}". Every composite type is stored in the default storage namespace, so composite type names must differ across namespaces. Rename one of them.`, + ...diagnosticSource(input.sources, compositeType.node.syntax).at(), + }); + continue; + } + compositeTypeNamespaces.set(compositeType.name, namespace.id); + compositeTypes.push(compositeType); + } } const physicalNames = new Map(); for (const model of models) { @@ -2705,6 +2757,25 @@ export function interpretPslDocumentToSqlContract( }); } + const polyDiagnostics = createPslDiagnosticCollector(input.sources); + const polymorphism = resolveVariants( + new Set( + stiColumnModelNodes.map((node) => + modelCoordinateKey(node.namespaceId ?? defaultNamespaceId, node.modelName), + ), + ), + discriminatorDeclarations, + baseDeclarations, + reported, + polyDiagnostics, + ); + if (polyDiagnostics.length > 0) { + return notOk({ + summary: 'PSL to SQL contract interpretation failed', + diagnostics: polyDiagnostics.toExternal(), + }); + } + // Merge lowered extension-block entities into each namespace's entries. // `valueSet` is unioned rather than overwritten: a namespace may derive // value-sets from both a PSL `enum` block and an extension entity, and both @@ -2771,7 +2842,7 @@ export function interpretPslDocumentToSqlContract( : {}), createNamespace: createNamespaceWithExtensions, ...ifDefined('valueObjects', valueObjects.length > 0 ? valueObjects : undefined), - models: stiColumnModelNodes, + models: withPolymorphism(stiColumnModelNodes, polymorphism, defaultNamespaceId), }, input.codecLookup, input.dataTypes.lookup, @@ -2804,25 +2875,15 @@ export function interpretPslDocumentToSqlContract( } } - const polyDiagnostics = createPslDiagnosticCollector(input.sources); - const polymorphicModels = resolvePolymorphism( + const polymorphicModels = resolveVariantStorage( modelsByCoordinate, physicalNames, - discriminatorDeclarations, baseDeclarations, - reported, + polymorphism, syntheticPkFieldsByVariant, stiBaseFieldsByBase, - polyDiagnostics, ); - if (polyDiagnostics.length > 0) { - return notOk({ - summary: 'PSL to SQL contract interpretation failed', - diagnostics: polyDiagnostics.toExternal(), - }); - } - const filteredRoots = Object.fromEntries( Object.entries(contract.roots).filter( ([, crossReference]) => @@ -2845,7 +2906,6 @@ export function interpretPslDocumentToSqlContract( ]), ), ...ifDefined('enum', namespaceSlice.enum), - ...ifDefined('valueObjects', namespaceSlice.valueObjects), }, ]), ), diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/fixtures.ts b/packages/2-sql-family/2-authoring/contract-psl/test/fixtures.ts index 1abc34d3f52b..15eeea1e8cb2 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/test/fixtures.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/test/fixtures.ts @@ -4,10 +4,7 @@ import type { ContractSourceDiagnostics, } from '@internal/config/config-types'; import type { Contract, JsonValue } from '@internal/contract/types'; -import { - domainModelsAtDefaultNamespace, - domainValueObjectsAtDefaultNamespace, -} from '@internal/contract/types'; +import { type ContractValueObject, domainModelsAtDefaultNamespace } from '@internal/contract/types'; import { type AuthoringContributions, type AuthoringEntityContext, @@ -37,6 +34,7 @@ import { withSeedDiagnostics } from '@internal/psl-parser/interpret'; import type { DocumentAst, PslSources, SourceFile } from '@internal/psl-parser/syntax'; import { parse } from '@internal/psl-parser/syntax'; import { bindPslSchema } from '@internal/psl-parser/test'; +import type { CheckExpressionInput } from '@internal/sql-contract/check-expressions'; import type { SqlNamespaceBase, SqlNamespaceInput } from '@internal/sql-contract/types'; import { type EnumTypeHandle, enumType } from '@internal/sql-contract-ts/contract-builder'; import type { Result } from '@internal/utils/result'; @@ -130,13 +128,7 @@ export const sqliteEnumInferenceCodecs = { * fixture without one emits no checks at all and every assertion about checks * would hold vacuously — the PSL/TS parity test in particular. */ -export function testRenderCheckExpressions(input: { - readonly tableName: string; - readonly columnName: string; - readonly many: boolean; - readonly elementNullable: boolean; - readonly memberValues: readonly (string | number)[] | undefined; -}): ReadonlyArray<{ +export function testRenderCheckExpressions(input: CheckExpressionInput): ReadonlyArray<{ readonly kind: 'membership' | 'elementNotNull'; readonly columnName: string; readonly expression: string; @@ -162,7 +154,7 @@ export function testRenderCheckExpressions(input: { : `${column} IN (${members})`, }); } - if (input.many && !input.elementNullable) { + if (input.many !== false && !input.many.elementNullable) { candidates.push({ kind: 'elementNotNull', columnName: input.columnName, @@ -594,8 +586,25 @@ export function modelsOf(contract: Contract) { return domainModelsAtDefaultNamespace(contract.domain); } -export function valueObjectsOf(contract: Contract) { - return domainValueObjectsAtDefaultNamespace(contract.domain); +/** A field type that refers to the storage value object `entityName` in `namespaceId`. */ +export function valueObjectRef(entityName: string, namespaceId = 'public') { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName, + } as const; +} + +/** The storage value objects of every namespace, as plain JSON; `undefined` when the contract has none. */ +export function valueObjectsOf( + contract: Contract, +): Record | undefined { + const slots = Object.values(contract.storage.namespaces).flatMap((namespace) => + namespace.entries['valueObject'] === undefined ? [] : [namespace.entries['valueObject']], + ); + return slots.length === 0 ? undefined : JSON.parse(JSON.stringify(Object.assign({}, ...slots))); } export function documentScopedTypes(contract: { readonly storage?: unknown }) { diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.alias-expansion.test.ts b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.alias-expansion.test.ts index 1ac21d474f07..d21ec455765c 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.alias-expansion.test.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.alias-expansion.test.ts @@ -6,6 +6,7 @@ import { postgresNativeScalarTypeDescriptors, testEnumEntityContributions, testEnumPslBlockDescriptor, + valueObjectsOf, } from './fixtures'; import { sqlStorageFromSuccessfulSqlInterpretation } from './interpret-sql-contract-storage'; import { @@ -221,7 +222,7 @@ model Note { @@base(Post, "note") }`); expect({ - member: contract.domain.namespaces['public']?.valueObjects?.['Label']?.fields['code'], + member: valueObjectsOf(contract)?.['Label']?.fields['code'], discriminator: unboundTables(sqlStorageFromSuccessfulSqlInterpretation(contract))['Post'] ?.columns['kind'], types: documentScopedTypes(contract), @@ -237,6 +238,12 @@ model Note { nullable: false, many: false, typeParams: { length: 8 }, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'public', + entityName: 'Post.kind', + }, }, types: undefined, }); diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.list-field-equals-single-field.test.ts b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.list-field-equals-single-field.test.ts index 6c27de2b5cb5..77c1597a95de 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.list-field-equals-single-field.test.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.list-field-equals-single-field.test.ts @@ -13,6 +13,7 @@ import { postgresTarget, testEnumEntityContributions, testEnumPslBlockDescriptor, + valueObjectsOf, } from './fixtures'; const numeric = { @@ -142,7 +143,7 @@ model Product { expect(result.ok).toBe(true); if (!result.ok) return; - expect(result.value.domain.namespaces['public']?.valueObjects?.['Price']?.fields).toEqual({ + expect(valueObjectsOf(result.value)?.['Price']?.fields).toEqual({ one: { nullable: false, type: numeric, many: false }, many: { nullable: false, type: numeric, many: { elementNullable: false } }, }); diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.nullable-list-shapes.test.ts b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.nullable-list-shapes.test.ts index 5318a50a71e8..80b2c7537d91 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.nullable-list-shapes.test.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.nullable-list-shapes.test.ts @@ -11,6 +11,7 @@ import { postgresScalarAuthoringTypes, postgresScalarTypeDescriptors, postgresTarget, + valueObjectRef, valueObjectsOf, } from './fixtures'; @@ -221,7 +222,7 @@ model User { expect(model?.fields['addresses']).toEqual({ nullable: false, - type: { kind: 'valueObject', name: 'Address' }, + type: valueObjectRef('Address'), many: { elementNullable: true }, }); expect(addressesColumn).toEqual({ diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.storage-plane.test.ts b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.storage-plane.test.ts new file mode 100644 index 000000000000..bb08dba6ca48 --- /dev/null +++ b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.storage-plane.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, it } from 'vitest'; +import { createTestSqlNamespace } from '../../../1-core/contract/test/test-support'; +import { fixtureInterpreterTypes } from './fixture-codec-descriptors'; +import { fixtureDataTypeSupport } from './fixture-data-types'; +import { + createBuiltinLikeControlMutationDefaults, + interpretSqlContract, + postgresScalarAuthoringTypes, + postgresScalarTypeDescriptors, + postgresTarget, + testEnumEntityContributions, + testEnumPslBlockDescriptor, +} from './fixtures'; + +function interpretPostgres(schema: string) { + const result = interpretSqlContract(schema, { + target: postgresTarget, + scalarColumnDescriptors: postgresScalarTypeDescriptors, + authoringContributions: { + type: postgresScalarAuthoringTypes, + entityTypes: testEnumEntityContributions, + pslBlockDescriptors: { enum: testEnumPslBlockDescriptor }, + dataTypes: fixtureDataTypeSupport.entries, + valueObjectStorageType: 'Jsonb', + }, + ...fixtureInterpreterTypes, + composedExtensionContracts: new Map(), + createNamespace: createTestSqlNamespace, + capabilities: { sql: { scalarList: true } }, + controlMutationDefaults: createBuiltinLikeControlMutationDefaults(), + }); + if (!result.ok) throw new Error(JSON.stringify(result.failure.diagnostics)); + return JSON.parse(JSON.stringify(result.value)); +} + +const text = { nullable: false, many: false, type: { kind: 'scalar', codecId: 'pg/text@1' } }; + +function valueObjectRef(name: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: name, + }; +} + +const countryEnum = `enum Country { + @@type("pg/text@1") + DE = "DE" + FR = "FR" +} +`; + +const addressSchema = (street: string) => `${countryEnum} +type Geo { + lat Float +} + +type Address { + ${street} String + tags String[] + country Country + geo Geo? +} + +model User { + id Int @id + home Address +}`; + +describe('SQL PSL value objects in the storage plane', () => { + it('writes a composite type to storage, and the field refers to it by coordinate', () => { + const contract = interpretPostgres(addressSchema('street')); + expect({ + field: contract.domain.namespaces.public.models.User.fields.home, + valueObjects: contract.storage.namespaces.public.entries.valueObject, + domainKeys: Object.keys(contract.domain.namespaces.public).sort(), + }).toEqual({ + field: { nullable: false, many: false, type: valueObjectRef('Address') }, + valueObjects: { + Address: { + fields: { + street: text, + tags: { ...text, many: { elementNullable: false } }, + country: { + ...text, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'public', + entityName: 'Country', + }, + }, + geo: { nullable: true, many: false, type: valueObjectRef('Geo') }, + }, + }, + Geo: { + fields: { + lat: { nullable: false, many: false, type: { kind: 'scalar', codecId: 'pg/float8@1' } }, + }, + }, + }, + domainKeys: ['enum', 'models'], + }); + }); + + it('changes the storage hash when a member changes', () => { + expect(interpretPostgres(addressSchema('street')).storage.storageHash).not.toBe( + interpretPostgres(addressSchema('line1')).storage.storageHash, + ); + }); +}); + +const taskSchema = (variants: string) => `model Task { + id Int @id + type String + + @@discriminator(type) +} + +${variants}`; + +const bug = `model Bug { + severity Int + @@base(Task, "bug") +} +`; +const feature = `model Feature { + priority Int + @@base(Task, "feature") +} +`; + +describe('SQL PSL discriminator value sets', () => { + it('writes a . value set of the variant values, and the column refers to it', () => { + const contract = interpretPostgres(taskSchema(`${bug}\n${feature}`)); + const namespace = contract.storage.namespaces.public; + expect({ + valueSet: namespace.entries.valueSet?.['Task.type'], + column: namespace.entries.table.Task.columns.type.valueSet, + discriminator: contract.domain.namespaces.public.models.Task.discriminator, + }).toEqual({ + valueSet: { kind: 'valueSet', values: ['bug', 'feature'] }, + column: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'public', + entityName: 'Task.type', + }, + discriminator: { field: 'type' }, + }); + }); + + it("changes the storage hash when a variant's value changes", () => { + expect(interpretPostgres(taskSchema(bug)).storage.storageHash).not.toBe( + interpretPostgres(taskSchema(bug.replace('"bug"', '"defect"'))).storage.storageHash, + ); + }); + + it('keeps the enum value set of an enum-typed discriminator and writes no second one', () => { + const contract = interpretPostgres(`enum Kind { + @@type("pg/text@1") + Bug = "bug" + Feature = "feature" +} + +model Task { + id Int @id + type Kind + + @@discriminator(type) +} + +${bug}`); + const namespace = contract.storage.namespaces.public; + expect({ + valueSets: Object.keys(namespace.entries.valueSet), + column: namespace.entries.table.Task.columns.type.valueSet.entityName, + }).toEqual({ valueSets: ['Kind'], column: 'Kind' }); + }); +}); diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-domain.test.ts b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-domain.test.ts index f1dfdb0a1190..77a9099b23bc 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-domain.test.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-domain.test.ts @@ -10,6 +10,7 @@ import { postgresTarget, testEnumEntityContributions, testEnumPslBlockDescriptor, + valueObjectsOf, } from './fixtures'; const pslBlockDescriptors = { enum: testEnumPslBlockDescriptor }; @@ -40,8 +41,8 @@ const countryEnum = `enum Country { } `; -describe('interpretPslDocumentToSqlContract value objects in the domain', () => { - it('gives an enum-typed composite member the domain valueSet a model field of that enum has, single and list', () => { +describe('interpretPslDocumentToSqlContract value objects in the storage plane', () => { + it('gives an enum-typed composite member the storage value set of that enum, single and list', () => { const result = interpretPostgres(`${countryEnum} type Address { country Country @@ -69,9 +70,18 @@ model User { entityName: 'Country', }, }); - expect(namespace?.valueObjects?.['Address']?.fields).toEqual({ - country: modelField, - countries: { ...modelField, many: { elementNullable: false } }, + const member = { + ...modelField, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'public', + entityName: 'Country', + }, + }; + expect(valueObjectsOf(result.value)?.['Address']?.fields).toEqual({ + country: member, + countries: { ...member, many: { elementNullable: false } }, }); }); @@ -110,7 +120,7 @@ model User { inline: fields?.['inline'], email: fields?.['email'], }).toEqual({ code: short, inline: short, email }); - expect(namespace?.valueObjects?.['Label']?.fields).toEqual({ + expect(valueObjectsOf(result.value)?.['Label']?.fields).toEqual({ code: short, codes: { ...short, many: { elementNullable: false } }, email, @@ -137,7 +147,7 @@ model Order { expect(result.ok).toBe(true); if (!result.ok) return; const text = { kind: 'scalar', codecId: 'pg/text@1' }; - expect(result.value.domain.namespaces['public']?.valueObjects).toEqual({ + expect(valueObjectsOf(result.value)).toEqual({ Address: { fields: { street: { nullable: false, type: text, many: false }, @@ -147,7 +157,17 @@ model Order { }, ShippingInfo: { fields: { - address: { nullable: false, type: { kind: 'valueObject', name: 'Address' }, many: false }, + address: { + nullable: false, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, + many: false, + }, notes: { nullable: false, type: text, many: false }, }, }, @@ -232,13 +252,13 @@ model User { ]); }); - it('omits valueObjects from the contract when no composite types exist', () => { + it('writes no valueObject slot when no composite types exist', () => { const result = interpretPostgres(`model User { id Int @id }`); expect(result.ok).toBe(true); if (!result.ok) return; - expect(result.value.domain.namespaces['public']?.valueObjects).toBeUndefined(); + expect(valueObjectsOf(result.value)).toBeUndefined(); }); }); diff --git a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-storage.test.ts b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-storage.test.ts index 70f71751c4cf..6f11acf0bb68 100644 --- a/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-storage.test.ts +++ b/packages/2-sql-family/2-authoring/contract-psl/test/interpreter.value-object-storage.test.ts @@ -13,6 +13,7 @@ import { sqliteScalarAuthoringTypes, sqliteScalarColumnDescriptors, sqliteTarget, + valueObjectRef, } from './fixtures'; function userFieldsAndColumns(contract: Contract) { @@ -58,14 +59,16 @@ model User { const idField = { nullable: false, type: { kind: 'scalar', codecId: 'pg/int4@1' }, many: false }; const idColumn = { many: false, dataType: 'pg/int4', codecId: 'pg/int4@1', nullable: false }; -const addressFields = { - home: { nullable: true, type: { kind: 'valueObject', name: 'Address' }, many: false }, - addresses: { - nullable: false, - type: { kind: 'valueObject', name: 'Address' }, - many: { elementNullable: false }, - }, -}; +function addressFields(namespaceId = 'public') { + return { + home: { nullable: true, type: valueObjectRef('Address', namespaceId), many: false }, + addresses: { + nullable: false, + type: valueObjectRef('Address', namespaceId), + many: { elementNullable: false }, + }, + }; +} describe('interpretPslDocumentToSqlContract value-object storage', () => { describe('value-object fields keep the target-declared storage column', () => { @@ -75,7 +78,7 @@ describe('interpretPslDocumentToSqlContract value-object storage', () => { expect(result.ok).toBe(true); if (!result.ok) return; expect(userFieldsAndColumns(result.value)).toEqual({ - fields: { id: idField, ...addressFields }, + fields: { id: idField, ...addressFields() }, columns: { id: idColumn, home: { many: false, dataType: 'pg/jsonb', codecId: 'pg/jsonb@1', nullable: true }, @@ -108,7 +111,7 @@ describe('interpretPslDocumentToSqlContract value-object storage', () => { type: { kind: 'scalar', codecId: 'sqlite/integer@1' }, many: false, }, - ...addressFields, + ...addressFields('__unbound__'), }, columns: { id: { @@ -143,7 +146,7 @@ model User { expect(userFieldsAndColumns(result.value)).toEqual({ fields: { id: idField, - home: { nullable: false, type: { kind: 'valueObject', name: 'Address' }, many: false }, + home: { nullable: false, type: valueObjectRef('Address'), many: false }, }, columns: { id: idColumn, @@ -286,3 +289,44 @@ model Child { ]); }); }); + +describe('composite types with the same name in two namespaces', () => { + it('are refused, since every value object is stored in the default storage namespace', () => { + const result = interpretPostgres(`namespace public { + type Address { + street String + } + model User { + id Int @id + home Address + } +} + +namespace auth { + type Address { + zip Int + } + model Account { + id Int @id + home Address + } +} +`); + expect( + result.ok + ? [] + : result.failure.diagnostics.map(({ code, message, span }) => ({ + code, + message, + line: span?.start.line, + })), + ).toEqual([ + { + code: 'PSL_DUPLICATE_DECLARATION', + message: + 'Composite type "Address" is declared in namespace "public" and in namespace "auth". Every composite type is stored in the default storage namespace, so composite type names must differ across namespaces. Rename one of them.', + line: 12, + }, + ]); + }); +}); diff --git a/packages/2-sql-family/2-authoring/contract-ts/schemas/data-contract-sql-v1.json b/packages/2-sql-family/2-authoring/contract-ts/schemas/data-contract-sql-v1.json index 7b151f2e5396..0309a4adadd8 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/schemas/data-contract-sql-v1.json +++ b/packages/2-sql-family/2-authoring/contract-ts/schemas/data-contract-sql-v1.json @@ -81,7 +81,117 @@ "nullable": { "type": "boolean" }, - "type": {}, + "type": { + "anyOf": [ + { + "type": "object", + "properties": { + "codecId": { + "type": "string" + }, + "kind": { + "const": "scalar" + }, + "typeParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": ["codecId", "kind"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "entityKind": { + "const": "valueObject" + }, + "entityName": { + "type": "string" + }, + "kind": { + "const": "valueObject" + }, + "namespaceId": { + "type": "string" + }, + "plane": { + "const": "storage" + } + }, + "required": [ + "entityKind", + "entityName", + "kind", + "namespaceId", + "plane" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "kind": { + "const": "union" + }, + "members": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "codecId": { + "type": "string" + }, + "kind": { + "const": "scalar" + }, + "typeParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": ["codecId", "kind"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "entityKind": { + "const": "valueObject" + }, + "entityName": { + "type": "string" + }, + "kind": { + "const": "valueObject" + }, + "namespaceId": { + "type": "string" + }, + "plane": { + "const": "storage" + } + }, + "required": [ + "entityKind", + "entityName", + "kind", + "namespaceId", + "plane" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": ["kind", "members"], + "additionalProperties": false + } + ] + }, "many": { "anyOf": [ { @@ -410,71 +520,6 @@ "required": ["codecId", "members"], "additionalProperties": false } - }, - "valueObjects": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "fields": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "nullable": { - "type": "boolean" - }, - "type": {}, - "many": { - "anyOf": [ - { - "type": "object", - "properties": { - "elementNullable": { - "type": "boolean" - } - }, - "required": ["elementNullable"], - "additionalProperties": false - }, - { - "const": false - } - ], - "default": false - }, - "dict": { - "const": true - }, - "valueSet": { - "type": "object", - "properties": { - "entityKind": { - "const": "enum" - }, - "entityName": { - "type": "string" - }, - "namespaceId": { - "type": "string" - }, - "plane": { - "const": "domain" - }, - "spaceId": { - "type": "string" - } - }, - "required": ["entityKind", "entityName", "namespaceId", "plane"] - } - }, - "required": ["nullable", "type"], - "additionalProperties": false - } - } - }, - "required": ["fields"] - } } }, "required": ["models"] @@ -814,6 +859,117 @@ "additionalProperties": false } }, + "valueObject": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "fields": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "nullable": { + "type": "boolean" + }, + "type": { + "anyOf": [ + { + "type": "object", + "properties": { + "codecId": { + "type": "string" + }, + "kind": { + "const": "scalar" + }, + "typeParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": ["codecId", "kind"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "entityKind": { + "const": "valueObject" + }, + "entityName": { + "type": "string" + }, + "kind": { + "const": "valueObject" + }, + "namespaceId": { + "type": "string" + }, + "plane": { + "const": "storage" + } + }, + "required": [ + "entityKind", + "entityName", + "kind", + "namespaceId", + "plane" + ], + "additionalProperties": false + } + ] + }, + "many": { + "anyOf": [ + { + "type": "object", + "properties": { + "elementNullable": { + "type": "boolean" + } + }, + "required": ["elementNullable"], + "additionalProperties": false + }, + { + "const": false + } + ] + }, + "valueSet": { + "type": "object", + "properties": { + "entityKind": { + "const": "valueSet" + }, + "entityName": { + "type": "string" + }, + "namespaceId": { + "type": "string" + }, + "plane": { + "const": "storage" + }, + "spaceId": { + "type": "string" + } + }, + "required": ["entityKind", "entityName", "namespaceId", "plane"], + "additionalProperties": false + } + }, + "required": ["nullable", "type"], + "additionalProperties": false + } + } + }, + "required": ["fields"], + "additionalProperties": false + } + }, "valueSet": { "type": "object", "additionalProperties": { diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/build-contract.ts b/packages/2-sql-family/2-authoring/contract-ts/src/build-contract.ts index 556b5edeb419..4b2a0ffd71d2 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/build-contract.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/build-contract.ts @@ -12,6 +12,8 @@ import { coreHash, crossRef, type ExecutionMutationDefault, + effectiveControlPolicy, + type StorageBase, type StorageHashBase, } from '@internal/contract/types'; import { encodeEnumTypeMembers } from '@internal/contract-authoring'; @@ -27,10 +29,15 @@ import type { import { mergeCapabilityMatrices } from '@internal/framework-components/components'; import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { sqlContractCanonicalizationHooks } from '@internal/sql-contract/canonicalization-hooks'; +import { + type CheckExpressionRenderer, + discriminatorValueSet, +} from '@internal/sql-contract/check-expressions'; import { sqlDataTypeOfCodec } from '@internal/sql-contract/data-type'; import { validateIndexTypes } from '@internal/sql-contract/index-type-validation'; import { type IndexTypeRegistry, indexTypeRegistryOf } from '@internal/sql-contract/index-types'; import { + CheckConstraint, type SqlNamespaceInput, SqlStorage, type SqlStorageInput, @@ -46,8 +53,9 @@ import type { ContractDefinition } from './contract-definition'; import { contractError } from './contract-errors'; import { describeModel, type ModelDescription } from './describe-model'; import { describeTableNode } from './describe-table-node'; -import { buildDomainField } from './domain-fields'; -import { lowerTable, type TableLoweringContext } from './lower-table'; +import { buildValueObjectMember } from './domain-fields'; +import { lowerRenderedChecks } from './lower-column'; +import { inCanonicalOrder, lowerTable, type TableLoweringContext } from './lower-table'; import { mergeTables } from './merge-tables'; import { modelLookupsOf, modelNamespaceId } from './model-references'; import { namespaceIdOrDefault } from './namespace-id'; @@ -152,6 +160,137 @@ function enabledCapabilities( return enabled; } +/** The value objects of a definition as storage entities, all in the default namespace; `undefined` when it has none. */ +function valueObjectsFromDefinition( + definition: ContractDefinition, + defaultNamespaceId: string, +): Record | undefined { + if (definition.valueObjects === undefined || definition.valueObjects.length === 0) { + return undefined; + } + return Object.fromEntries( + definition.valueObjects.map((valueObject) => [ + valueObject.name, + { + fields: Object.fromEntries( + valueObject.fields.map((field) => [ + field.fieldName, + buildValueObjectMember(field, defaultNamespaceId), + ]), + ), + }, + ]), + ); +} + +/** The value objects and value sets storage will hold, in the shape storage readers take. */ +function storageOf( + defaultNamespaceId: string, + valueObjects: Record | undefined, + storageValueSetsByNs: Readonly>>, +): Pick { + const namespaceIds = new Set([defaultNamespaceId, ...Object.keys(storageValueSetsByNs)]); + return { + namespaces: Object.fromEntries( + [...namespaceIds].map((id) => [ + id, + { + id, + entries: { + valueSet: storageValueSetsByNs[id] ?? {}, + ...(id === defaultNamespaceId ? { valueObject: valueObjects ?? {} } : {}), + }, + }, + ]), + ), + }; +} + +/** + * Writes the value set of each discriminator, keyed `.` in the base table's namespace and holding the variants' values as the column stores them, sorted, and points the discriminator column at it (ADR 309). A column an enum types keeps the enum's value set. An enum with the same name in the same namespace is refused, since the two value sets would share one entry. + */ +function writeDiscriminatorValueSets( + definition: ContractDefinition, + tablesByNamespace: Record>, + storageValueSetsByNs: Record>, + checkRendererFor: (table: StorageTableInput) => CheckExpressionRenderer | undefined, +): void { + const defaultNamespaceId = definition.target.defaultNamespaceId; + for (const model of definition.models) { + const { discriminator, variants } = model; + if (discriminator === undefined || variants === undefined) continue; + const namespaceId = modelNamespaceId(model, defaultNamespaceId); + const table = tablesByNamespace[namespaceId]?.[model.tableName]; + const columnName = model.fields.find( + (field) => field.fieldName === discriminator.field, + )?.columnName; + const column = columnName === undefined ? undefined : table?.columns[columnName]; + if (table === undefined || columnName === undefined || column === undefined) continue; + if (column.valueSet !== undefined) continue; + const { name: valueSetName, values } = discriminatorValueSet( + model.modelName, + discriminator.field, + Object.keys(variants), + ); + const namespaceValueSets = storageValueSetsByNs[namespaceId] ?? {}; + if (Object.hasOwn(namespaceValueSets, valueSetName)) { + throw contractError( + 'CONTRACT.ENUM_INVALID', + `enumType("${valueSetName}"): has the name of the value set that the discriminator field "${discriminator.field}" of model "${model.modelName}" writes. Rename the enum.`, + { + meta: { + enumName: valueSetName, + model: model.modelName, + field: discriminator.field, + reason: 'discriminator-value-set-name', + }, + }, + ); + } + namespaceValueSets[valueSetName] = { kind: 'valueSet', values }; + storageValueSetsByNs[namespaceId] = namespaceValueSets; + const render = checkRendererFor(table); + const derivedChecks = + render === undefined + ? [] + : lowerRenderedChecks( + model.tableName, + render({ + tableName: model.tableName, + columnName, + many: false, + memberValues: values, + valueObject: undefined, + }), + ); + const namespaceTables = tablesByNamespace[namespaceId] ?? {}; + namespaceTables[model.tableName] = { + ...table, + ...(derivedChecks.length > 0 + ? { + checks: inCanonicalOrder([ + ...(table.checks ?? []).map((check) => CheckConstraint.from(check)), + ...derivedChecks, + ]), + } + : {}), + columns: { + ...table.columns, + [columnName]: { + ...column, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId, + entityName: valueSetName, + }, + }, + }, + }; + tablesByNamespace[namespaceId] = namespaceTables; + } +} + export function buildSqlContractFromDefinition( definition: ContractDefinition, codecLookup: CodecLookupWithDescriptors, @@ -227,11 +366,14 @@ export function buildSqlContractFromDefinition( // producer's) and flush once, threshold-batched by code. const authoringWarnings: AuthoringWarning[] = [...(definition.warnings ?? [])]; const collectedColumnEntities: CollectedColumnEntities = {}; + const valueObjects = valueObjectsFromDefinition(definition, defaultNamespaceId); + const renderCheckExpressions = resolveCheckExpressionRenderer(definition.target); const tableLoweringContext: TableLoweringContext = { lookups, defaultNamespaceId, qualifyColumnType, - renderCheckExpressions: resolveCheckExpressionRenderer(definition.target), + renderCheckExpressions, + storage: storageOf(defaultNamespaceId, valueObjects, storageValueSetsByNs), collectedColumnEntities, defaultControlPolicy: definition.defaultControlPolicy, indexTypeRegistry, @@ -246,6 +388,12 @@ export function buildSqlContractFromDefinition( namespaceTables[table.tableName] = lowerTable(table, tableLoweringContext); tablesByNamespace[table.namespaceId] = namespaceTables; } + writeDiscriminatorValueSets(definition, tablesByNamespace, storageValueSetsByNs, (table) => + renderCheckExpressions !== undefined && + effectiveControlPolicy(table.control, definition.defaultControlPolicy) === 'managed' + ? renderCheckExpressions + : undefined, + ); const executionDefaults: ExecutionMutationDefault[] = components.flatMap( (c) => c.domain.executionDefaults, @@ -301,6 +449,9 @@ export function buildSqlContractFromDefinition( ...(valueSetEntries !== undefined && Object.keys(valueSetEntries).length > 0 ? { valueSet: valueSetEntries } : {}), + ...(id === defaultNamespaceId && valueObjects !== undefined + ? { valueObject: valueObjects } + : {}), }, }; return [id, createNamespace(nsInput)]; @@ -362,27 +513,10 @@ export function buildSqlContractFromDefinition( defaults: executionDefaults, }); - const valueObjects: Record | undefined = - definition.valueObjects && definition.valueObjects.length > 0 - ? Object.fromEntries( - definition.valueObjects.map((vo) => [ - vo.name, - { - fields: Object.fromEntries( - vo.fields.map((f) => [f.fieldName, buildDomainField(f, defaultNamespaceId)]), - ), - }, - ]), - ) - : undefined; - const domainNamespaceIds = new Set(Object.keys(modelsByNamespace)); if (domainNamespaceIds.size === 0) { domainNamespaceIds.add(defaultNamespaceId); } - if (valueObjects !== undefined) { - domainNamespaceIds.add(defaultNamespaceId); - } for (const nsId of Object.keys(domainEnumsByNs)) { domainNamespaceIds.add(nsId); } @@ -392,9 +526,6 @@ export function buildSqlContractFromDefinition( const enumsInNs = domainEnumsByNs[namespaceId]; const namespaceSlice = { models: modelsInNs, - ...(namespaceId === defaultNamespaceId && valueObjects !== undefined - ? { valueObjects } - : {}), ...(enumsInNs !== undefined && Object.keys(enumsInNs).length > 0 ? { enum: enumsInNs } : {}), diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/contract-definition.ts b/packages/2-sql-family/2-authoring/contract-ts/src/contract-definition.ts index f90fc94de104..98fccc244c98 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/contract-definition.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/contract-definition.ts @@ -1,6 +1,8 @@ import type { ColumnDefault, + ContractDiscriminator, ControlPolicy, + CrossReference, ExecutionMutationDefaultPhases, } from '@internal/contract/types'; import type { ForeignKeyDefaultsState } from '@internal/contract-authoring'; @@ -305,6 +307,12 @@ export interface ModelNode extends TableProperties { * table and a root for this model — the base owns both. */ readonly sharesBaseTable?: boolean; + /** The field that names a row's variant, on a model that has variants. Its value set is written into storage before the storage hash (ADR 309). */ + readonly discriminator?: ContractDiscriminator; + /** The variants of this model, keyed by the discriminator value as the column stores it. */ + readonly variants?: Readonly>; + /** The model this one is a variant of. */ + readonly base?: CrossReference; } /** diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/contract-types.ts b/packages/2-sql-family/2-authoring/contract-ts/src/contract-types.ts index 8fe8d352cf92..30cead1907e6 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/contract-types.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/contract-types.ts @@ -1618,7 +1618,8 @@ export type SqlContractResult = ContractWithTypeMaps< CodecTypesFromDefinition, ParameterizedOutputTypesFromDefinition, FieldTypeOverrides, - 'output' + 'output', + BuiltStorage['namespaces'] > >; readonly fieldInputTypes: ResolvedTypeMap< @@ -1627,7 +1628,8 @@ export type SqlContractResult = ContractWithTypeMaps< CodecTypesFromDefinition, ParameterizedOutputTypesFromDefinition, FieldTypeOverrides, - 'input' + 'input', + BuiltStorage['namespaces'] > >; readonly storageColumnTypes: ResolvedTypeMap< diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/describe-column.ts b/packages/2-sql-family/2-authoring/contract-ts/src/describe-column.ts index ec2567faacf9..7c430a02558b 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/describe-column.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/describe-column.ts @@ -11,6 +11,7 @@ export function describeColumn(column: ColumnNode, site: ColumnSite): ColumnDesc many: column.many === true ? { elementNullable: column.elementNullable === true } : false, default: column.default, enumTypeHandle: column.enumTypeHandle, + valueObject: undefined, site, }; } diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/describe-model.ts b/packages/2-sql-family/2-authoring/contract-ts/src/describe-model.ts index 8ebe5087e5f4..14348b001770 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/describe-model.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/describe-model.ts @@ -13,7 +13,7 @@ import { } from './contract-definition'; import { contractError } from './contract-errors'; import { describeColumn } from './describe-column'; -import { buildDomainField } from './domain-fields'; +import { buildDomainField, valueObjectReference } from './domain-fields'; import { modelNamespaceId } from './model-references'; import { lowerRelations } from './model-relations'; import { type ForeignKeyResolutionContext, resolveForeignKey } from './resolve-foreign-key'; @@ -89,7 +89,7 @@ export function describeModel( context, ), ); - const storage = describeModelStorage(model, namespaceId, foreignKeys); + const storage = describeModelStorage(model, namespaceId, foreignKeys, context.defaultNamespaceId); const storageFields: Record = {}; for (const [fieldName, columnName] of Object.entries(fieldToColumn)) { @@ -106,6 +106,9 @@ export function describeModel( storage: { table: tableName, namespaceId, fields: storageFields }, fields: domainFields, relations: lowerRelations(model, columnToField, context), + ...ifDefined('discriminator', model.discriminator), + ...ifDefined('variants', model.variants), + ...ifDefined('base', model.base), }, executionDefaults, }, @@ -119,9 +122,12 @@ function describeModelStorage( model: ModelNode, namespaceId: string, foreignKeys: readonly ForeignKeyAuthoringInput[], + defaultNamespaceId: string, ): ModelStorage { const { tableName } = model; - const columns = model.fields.map((field) => describeFieldColumn(model.modelName, field)); + const columns = model.fields.map((field) => + describeFieldColumn(model.modelName, field, defaultNamespaceId), + ); if (model.sharesBaseTable) { if (model.checks && model.checks.length > 0) { throw contractError( @@ -149,18 +155,28 @@ function describeModelStorage( }; } -function describeFieldColumn(modelName: string, field: ModelField): ColumnDescription { +function describeFieldColumn( + modelName: string, + field: ModelField, + defaultNamespaceId: string, +): ColumnDescription { const site = { kind: 'field', modelName, fieldName: field.fieldName } as const; if (isValueObjectMember(field)) { - return describeColumn( - { - columnName: field.columnName, - descriptor: field.descriptor, - nullable: field.nullable, - ...ifDefined('default', field.default), + return { + ...describeColumn( + { + columnName: field.columnName, + descriptor: field.descriptor, + nullable: field.nullable, + ...ifDefined('default', field.default), + }, + site, + ), + valueObject: { + reference: valueObjectReference(field.valueObjectName, defaultNamespaceId), + many: field.many === true ? { elementNullable: field.elementNullable === true } : false, }, - site, - ); + }; } return describeColumn(field, site); } diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/domain-fields.ts b/packages/2-sql-family/2-authoring/contract-ts/src/domain-fields.ts index 8f5f9072e826..1943c82a2f00 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/domain-fields.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/domain-fields.ts @@ -1,4 +1,8 @@ -import type { ContractField } from '@internal/contract/types'; +import type { + ContractField, + ContractValueObjectField, + EntityCoordinate, +} from '@internal/contract/types'; import { nonEmptyTypeParams } from '@internal/sql-contract/types'; import { ifDefined } from '@internal/utils/defined'; import { @@ -6,20 +10,38 @@ import { type ScalarMemberNode, type ValueObjectMemberNode, } from './contract-definition'; -import { enumValueSetRefs } from './enum-members'; +import { type EnumValueSetRefs, enumValueSetRefs } from './enum-members'; -export function buildDomainField( - field: ScalarMemberNode | ValueObjectMemberNode, +type Member = ScalarMemberNode | ValueObjectMemberNode; + +/** The storage coordinate of a value object. Value objects are all in the default namespace. */ +export function valueObjectReference( + valueObjectName: string, defaultNamespaceId: string, -): ContractField { +): EntityCoordinate<'storage', 'valueObject'> { + return { + plane: 'storage', + namespaceId: defaultNamespaceId, + entityKind: 'valueObject', + entityName: valueObjectName, + }; +} + +function fieldWithoutValueSet( + field: Member, + defaultNamespaceId: string, +): Omit { + const many = field.many ? { elementNullable: field.elementNullable === true } : false; if (isValueObjectMember(field)) { return { - type: { kind: 'valueObject', name: field.valueObjectName }, + type: { + kind: 'valueObject', + ...valueObjectReference(field.valueObjectName, defaultNamespaceId), + }, nullable: field.nullable, - many: field.many ? { elementNullable: field.elementNullable === true } : false, + many, }; } - return { type: { kind: 'scalar', @@ -27,7 +49,31 @@ export function buildDomainField( ...ifDefined('typeParams', nonEmptyTypeParams(field.descriptor.typeParams)), }, nullable: field.nullable, - many: field.many ? { elementNullable: field.elementNullable === true } : false, - ...ifDefined('valueSet', enumValueSetRefs(field.enumTypeHandle, defaultNamespaceId)?.domain), + many, + }; +} + +function enumRefsOf(field: Member, defaultNamespaceId: string): EnumValueSetRefs | undefined { + return isValueObjectMember(field) + ? undefined + : enumValueSetRefs(field.enumTypeHandle, defaultNamespaceId); +} + +/** A model field in the domain plane. A value object is referred to by its storage coordinate, an enum by the domain enum. */ +export function buildDomainField(field: Member, defaultNamespaceId: string): ContractField { + return { + ...fieldWithoutValueSet(field, defaultNamespaceId), + ...ifDefined('valueSet', enumRefsOf(field, defaultNamespaceId)?.domain), + }; +} + +/** A member of a storage value object. Storage never refers to the domain plane, so an enum member refers to the storage value set. */ +export function buildValueObjectMember( + field: Member, + defaultNamespaceId: string, +): ContractValueObjectField { + return { + ...fieldWithoutValueSet(field, defaultNamespaceId), + ...ifDefined('valueSet', enumRefsOf(field, defaultNamespaceId)?.storage), }; } diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/enum-members.ts b/packages/2-sql-family/2-authoring/contract-ts/src/enum-members.ts index c33299c517ca..70143381d5ef 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/enum-members.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/enum-members.ts @@ -1,4 +1,4 @@ -import type { ValueSetRef } from '@internal/contract/types'; +import type { EntityCoordinate } from '@internal/contract/types'; import { type EnumTypeHandle, encodeEnumTypeMembers } from '@internal/contract-authoring'; import type { CodecLookupWithDescriptors } from '@internal/framework-components/codec'; import { contractError } from './contract-errors'; @@ -29,8 +29,8 @@ export function checkMemberValues( } export interface EnumValueSetRefs { - readonly domain: ValueSetRef; - readonly storage: ValueSetRef; + readonly domain: EntityCoordinate<'domain', 'enum'>; + readonly storage: EntityCoordinate<'storage', 'valueSet'>; } /** diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/lower-column.ts b/packages/2-sql-family/2-authoring/contract-ts/src/lower-column.ts index 9799134aa642..21c0616e74ef 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/lower-column.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/lower-column.ts @@ -1,5 +1,11 @@ +import type { StorageBase } from '@internal/contract/types'; import type { CodecLookupWithDescriptors, TypeLookups } from '@internal/framework-components/codec'; import { dataTypeParams } from '@internal/framework-components/codec'; +import { + type CheckExpressionCandidate, + type CheckExpressionRenderer, + resolveColumnValueObject, +} from '@internal/sql-contract/check-expressions'; import { type SqlDataType, sqlDataTypeOfCodec, @@ -20,11 +26,7 @@ import { type ColumnSite, columnSiteMeta, columnSiteSubject } from './declaratio import { checkMemberValues, enumValueSetRefs } from './enum-members'; import { type CollectedColumnEntities, collectEntityFromColumn } from './pack-entities'; import type { ColumnDescription } from './storage-description'; -import { - type CheckExpressionRenderer, - type ColumnTypeQualifier, - resolveColumnDescriptor, -} from './target-authoring-hooks'; +import { type ColumnTypeQualifier, resolveColumnDescriptor } from './target-authoring-hooks'; /** * Names the target's rendered checks and lowers them into contract entities. @@ -37,13 +39,9 @@ import { * rather than having to assume something about SQL text it declares itself * unable to read. */ -function lowerRenderedChecks( +export function lowerRenderedChecks( tableName: string, - candidates: ReadonlyArray<{ - readonly kind: 'membership' | 'elementNotNull'; - readonly columnName: string; - readonly expression: string; - }>, + candidates: ReadonlyArray, ): CheckConstraint[] { return candidates.map( (candidate) => @@ -80,6 +78,8 @@ export interface ColumnLoweringContext { readonly defaultNamespaceId: string; readonly qualifyColumnType: ColumnTypeQualifier | undefined; readonly renderCheckExpressions: CheckExpressionRenderer | undefined; + /** The value objects and value sets storage will hold, which a value object column's check is derived from. */ + readonly storage: Pick; /** Receives the pack entity a column's type constructor refers to. */ readonly collectedColumnEntities: CollectedColumnEntities; } @@ -162,10 +162,21 @@ export function lowerColumn( const candidates = render({ tableName: placement.tableName, columnName: description.columnName, - many: many !== false, - elementNullable, + many, memberValues: enumTypeHandle !== undefined ? checkMemberValues(enumTypeHandle, codecLookup) : undefined, + valueObject: + description.valueObject !== undefined + ? resolveColumnValueObject( + context.storage, + { + reference: description.valueObject.reference, + nullable: description.nullable, + many: description.valueObject.many, + }, + codecLookup, + ) + : undefined, }); return { column, derivedChecks: lowerRenderedChecks(placement.tableName, candidates) }; } diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/lower-table.ts b/packages/2-sql-family/2-authoring/contract-ts/src/lower-table.ts index 68bcc328d874..eeb4b01b6881 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/lower-table.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/lower-table.ts @@ -12,7 +12,7 @@ import { type StorageTableInput, } from '@internal/sql-contract/types'; import { - type CheckKind, + CHECK_KINDS, composeCheckWirePrefix, derivedCheckPrefixes, } from '@internal/sql-schema-ir/naming'; @@ -23,8 +23,6 @@ import { contractError } from './contract-errors'; import { type ColumnLoweringContext, lowerColumn } from './lower-column'; import type { TableDescription } from './storage-description'; -const DERIVABLE_CHECK_KINDS: readonly CheckKind[] = ['membership', 'elementNotNull']; - /** * Which of `columnNames` could have produced `prefix` for some * {@link CheckKind} — the reverse of {@link derivedCheckPrefixes}, used only @@ -38,9 +36,7 @@ function columnsProducingCheckPrefix( prefix: string, ): readonly string[] { return columnNames.filter((columnName) => - DERIVABLE_CHECK_KINDS.some( - (kind) => composeCheckWirePrefix(tableName, columnName, kind) === prefix, - ), + CHECK_KINDS.some((kind) => composeCheckWirePrefix(tableName, columnName, kind) === prefix), ); } @@ -117,7 +113,7 @@ export function lowerTable( /** * The checks sorted by name in UTF-16 code-unit order, the order contract canonicalization gives them. The table's storage is then the same whichever declaration each column came from and in whatever order the columns were listed. */ -function inCanonicalOrder(checks: readonly CheckConstraint[]): readonly CheckConstraint[] { +export function inCanonicalOrder(checks: readonly CheckConstraint[]): readonly CheckConstraint[] { return [...checks].sort((a, b) => compareCodeUnits(a.name, b.name)); } diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/storage-description.ts b/packages/2-sql-family/2-authoring/contract-ts/src/storage-description.ts index 5b189dfac6db..5172ad78503c 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/storage-description.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/storage-description.ts @@ -1,4 +1,4 @@ -import type { ControlPolicy } from '@internal/contract/types'; +import type { ControlPolicy, EntityCoordinate } from '@internal/contract/types'; import type { EnumTypeHandle } from '@internal/contract-authoring'; import type { ColumnTypeDescriptor } from '@internal/framework-components/codec'; import type { ForeignKeyAuthoringInput } from '@internal/sql-contract/foreign-key-materialization'; @@ -20,9 +20,17 @@ export interface ColumnDescription { readonly default: AuthoredColumnDefault | undefined; /** The `enumType()` handle that types the column, which gives it a storage value set and a membership check. */ readonly enumTypeHandle: EnumTypeHandle | undefined; + /** The value object the column holds, and whether the field is a list of them, stored as one JSON array. */ + readonly valueObject: ColumnValueObject | undefined; readonly site: ColumnSite; } +export interface ColumnValueObject { + readonly reference: EntityCoordinate<'storage', 'valueObject'>; + /** Whether the column's JSON value is an array of value objects. The column's own `many` is a native array column, which a value object column never is. */ + readonly many: false | { readonly elementNullable: boolean }; +} + /** One table, before lowering. It holds every table-level property a model or table node can state, and its foreign keys already name their target tables. */ export interface TableDescription { readonly namespaceId: string; diff --git a/packages/2-sql-family/2-authoring/contract-ts/src/target-authoring-hooks.ts b/packages/2-sql-family/2-authoring/contract-ts/src/target-authoring-hooks.ts index aa9bc6089f0d..b10b4d84fe6c 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/src/target-authoring-hooks.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/src/target-authoring-hooks.ts @@ -1,5 +1,6 @@ import type { AuthoringContributions } from '@internal/framework-components/authoring'; import type { ColumnTypeDescriptor } from '@internal/framework-components/codec'; +import type { CheckExpressionRenderer } from '@internal/sql-contract/check-expressions'; import { ifDefined } from '@internal/utils/defined'; import type { ContractDefinition } from './contract-definition'; @@ -61,31 +62,6 @@ function hasColumnTypeQualifier( return 'qualifyColumnType' in authoring && typeof authoring.qualifyColumnType === 'function'; } -/** - * A target's contract-construction-time check renderer, contributed through - * `target.authoring.renderCheckExpressions`. Given one column's shape it - * returns the checks that column needs, each as a kind, the column it - * constrains, and an opaque predicate body (no surrounding `CHECK (…)`). The - * target owns the SQL — quoting, escaping, predicate choice — and nothing - * else: naming is composed here, so the family never depends on a property of - * text it cannot read. `memberValues` is - * supplied only for a domain enum authored through an `enumType()` handle, so - * a target cannot accidentally write a membership check for a column whose - * native type already enforces its member set. Targets without the hook write - * no checks. - */ -export type CheckExpressionRenderer = (input: { - readonly tableName: string; - readonly columnName: string; - readonly many: boolean; - readonly elementNullable: boolean; - readonly memberValues: readonly (string | number)[] | undefined; -}) => ReadonlyArray<{ - readonly kind: 'membership' | 'elementNotNull'; - readonly columnName: string; - readonly expression: string; -}>; - /** * Structural check for a target that contributes a `renderCheckExpressions` * hook, duck-typed for the same reason {@link hasColumnTypeQualifier} is: the @@ -101,6 +77,9 @@ function hasCheckExpressionRenderer( ); } +/** + * The target's check renderer, contributed through `target.authoring.renderCheckExpressions`. The target owns the SQL of each check and the family names it, so the family never depends on a property of text it cannot read. `memberValues` is supplied only for a domain enum authored through an `enumType()` handle and for a discriminator column, so a target cannot write a membership check for a column whose native type already enforces its member set. Targets without the hook write no checks. + */ export function resolveCheckExpressionRenderer( target: ContractDefinition['target'], ): CheckExpressionRenderer | undefined { diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/check-constraint.authoring.test.ts b/packages/2-sql-family/2-authoring/contract-ts/test/check-constraint.authoring.test.ts index 902b77a88e05..268aa714018d 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/check-constraint.authoring.test.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/check-constraint.authoring.test.ts @@ -2,6 +2,7 @@ import { applySpecifierDefaultControlPolicy } from '@internal/contract/apply-spe import type { Contract, ControlPolicy } from '@internal/contract/types'; import { emptyCodecLookup } from '@internal/framework-components/codec'; import type { FamilyPackRef, TargetPackRef } from '@internal/framework-components/components'; +import type { CheckExpressionInput } from '@internal/sql-contract/check-expressions'; import { sql } from '@internal/sql-contract/sql-expression'; import { CheckConstraint, @@ -36,13 +37,7 @@ const sqlFamilyPack = { version: '0.0.1', } as const satisfies FamilyPackRef<'sql'>; -interface RenderInput { - readonly tableName: string; - readonly columnName: string; - readonly many: boolean; - readonly elementNullable: boolean; - readonly memberValues: readonly (string | number)[] | undefined; -} +type RenderInput = CheckExpressionInput; /** * Stands in for the Postgres pack's `renderCheckExpressions`, reproducing the @@ -79,7 +74,7 @@ function renderCheckExpressions(input: RenderInput): ReadonlyArray<{ : `${column} IN (${members})`, }); } - if (input.many && !input.elementNullable) { + if (input.many !== false && !input.many.elementNullable) { candidates.push({ kind: 'elementNotNull', columnName: input.columnName, @@ -315,8 +310,8 @@ describe('check emission — entity-ref-resolved value set (native enum shape)', tableName: 'User', columnName: 'role', many: false, - elementNullable: false, memberValues: undefined, + valueObject: undefined, }); }); }); @@ -526,9 +521,9 @@ describe('check emission — guards', () => { expect(hookCalls.at(-1)).toEqual({ tableName: 'User', columnName: 'special', - many, - elementNullable: false, + many: many ? { elementNullable: false } : false, memberValues: [1.5, 'NaN'], + valueObject: undefined, }); }, ); diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.derived-checks.test.ts b/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.derived-checks.test.ts new file mode 100644 index 000000000000..01749b5b8e32 --- /dev/null +++ b/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.derived-checks.test.ts @@ -0,0 +1,257 @@ +import { crossRef } from '@internal/contract/types'; +import type { CheckExpressionInput } from '@internal/sql-contract/check-expressions'; +import { composeCheckWirePrefix } from '@internal/sql-schema-ir/naming'; +import { describe, expect, it } from 'vitest'; +import { createTestSqlNamespace } from '../../../1-core/contract/test/test-support'; +import { testTypeLookups } from '../../../1-core/contract/test/test-type-lookups'; +import { buildSqlContractFromDefinition } from '../src/contract-builder'; +import type { ContractDefinition, ModelNode } from '../src/contract-definition'; +import { enumType, member } from '../src/enum-type'; + +const renderedInputs: CheckExpressionInput[] = []; + +function renderCheckExpressions(input: CheckExpressionInput) { + renderedInputs.push(input); + return [ + ...(input.memberValues === undefined + ? [] + : [ + { + kind: 'membership' as const, + columnName: input.columnName, + expression: `"${input.columnName}" IN (${input.memberValues.join(', ')})`, + }, + ]), + ...(input.valueObject === undefined + ? [] + : [ + { + kind: 'shape' as const, + columnName: input.columnName, + expression: `shape of "${input.columnName}"`, + }, + ]), + ]; +} + +const targetPack = { + kind: 'target', + id: 'postgres', + familyId: 'sql', + targetId: 'postgres', + version: '0.0.1', + defaultNamespaceId: 'public', + authoring: { type: {}, renderCheckExpressions }, +} as const; + +const int4 = { codecId: 'pg/int4@1' } as const; +const text = { codecId: 'pg/text@1' } as const; +const jsonb = { codecId: 'pg/jsonb@1' } as const; +const idField = { fieldName: 'id', columnName: 'id', descriptor: int4, nullable: false } as const; + +const Kind = enumType('Kind', text, member('Home', 'home'), member('Work', 'work')); + +function definition(models: readonly ModelNode[]): ContractDefinition { + return { + warnings: undefined, + target: targetPack, + createNamespace: createTestSqlNamespace, + enums: { Kind }, + models, + valueObjects: [ + { + name: 'Address', + fields: [ + { fieldName: 'street', descriptor: text, nullable: false }, + { fieldName: 'kind', descriptor: text, nullable: true, enumTypeHandle: Kind }, + { fieldName: 'geo', valueObjectName: 'Geo', nullable: false }, + ], + }, + { name: 'Geo', fields: [{ fieldName: 'lat', descriptor: text, nullable: false }] }, + ], + }; +} + +function userModel(control?: 'external'): ModelNode { + return { + modelName: 'User', + tableName: 'user', + fields: [ + idField, + { + fieldName: 'home', + columnName: 'home', + valueObjectName: 'Address', + descriptor: jsonb, + nullable: true, + }, + { + fieldName: 'stops', + columnName: 'stops', + valueObjectName: 'Address', + descriptor: jsonb, + nullable: false, + many: true, + elementNullable: true, + }, + ], + id: { columns: ['id'] }, + ...(control !== undefined ? { control } : {}), + }; +} + +function build(models: readonly ModelNode[]) { + renderedInputs.length = 0; + return buildSqlContractFromDefinition( + definition(models), + testTypeLookups.codecLookup, + testTypeLookups.dataTypeLookup, + ); +} + +function checkNames(contract: ReturnType, tableName: string) { + return (contract.storage.namespaces['public']?.entries.table?.[tableName]?.checks ?? []).map( + (check) => check.prefix, + ); +} + +const resolvedAddress = { + fields: { + street: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + kind: { + nullable: true, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: ['home', 'work'] }, + }, + geo: { + nullable: false, + many: false, + type: { + kind: 'valueObject', + valueObject: { + fields: { + lat: { + nullable: false, + many: false, + type: { kind: 'scalar', jsonType: 'string', values: undefined }, + }, + }, + }, + }, + }, + }, +} as const; + +describe('the derived check of a column that holds a value object', () => { + it('hands the renderer the value object resolved from storage, with the field nullability and list shape', () => { + build([userModel()]); + expect( + renderedInputs + .filter((input) => input.valueObject !== undefined) + .map(({ columnName, many, valueObject }) => ({ columnName, many, valueObject })), + ).toEqual([ + { + columnName: 'home', + many: false, + valueObject: { nullable: true, many: false, valueObject: resolvedAddress }, + }, + { + columnName: 'stops', + many: false, + valueObject: { + nullable: false, + many: { elementNullable: true }, + valueObject: resolvedAddress, + }, + }, + ]); + }); + + it('is named after the table, the column and the shape kind', () => { + expect(checkNames(build([userModel()]), 'user')).toEqual([ + composeCheckWirePrefix('user', 'home', 'shape'), + composeCheckWirePrefix('user', 'stops', 'shape'), + ]); + }); + + it('is not derived for a table that is not managed', () => { + expect(checkNames(build([userModel('external')]), 'user')).toEqual([]); + }); + + it('refuses an authored check whose name a value object check would use', () => { + const model = { + ...userModel(), + checks: [ + { + expression: 'true', + map: undefined, + name: composeCheckWirePrefix('user', 'home', 'shape'), + }, + ], + }; + expect(() => build([model])).toThrow(/derived enforcement check would use for column "home"/); + }); +}); + +function taskModels(options: { readonly typedByEnum?: boolean } = {}): readonly ModelNode[] { + return [ + { + modelName: 'Task', + tableName: 'task', + fields: [ + idField, + { + fieldName: 'type', + columnName: 'type', + descriptor: text, + nullable: false, + ...(options.typedByEnum === true ? { enumTypeHandle: Kind } : {}), + }, + ], + id: { columns: ['id'] }, + discriminator: { field: 'type' }, + variants: { work: crossRef('Bug', 'public'), home: crossRef('Feature', 'public') }, + }, + ...['Bug', 'Feature'].map((modelName) => ({ + modelName, + tableName: 'task', + fields: [], + sharesBaseTable: true, + base: crossRef('Task', 'public'), + })), + ]; +} + +describe('the derived check of a discriminator column', () => { + it('is a membership check holding the variant values, sorted', () => { + const contract = build(taskModels()); + expect(contract.storage.namespaces['public']?.entries.table?.['task']?.checks).toEqual([ + expect.objectContaining({ + prefix: composeCheckWirePrefix('task', 'type', 'membership'), + expression: '"type" IN (home, work)', + }), + ]); + }); + + it('is only the enum membership check when an enum types the discriminator', () => { + const contract = build(taskModels({ typedByEnum: true })); + expect({ + checks: checkNames(contract, 'task'), + memberValues: renderedInputs.map((input) => input.memberValues), + }).toEqual({ + checks: [composeCheckWirePrefix('task', 'type', 'membership')], + memberValues: [undefined, ['home', 'work']], + }); + }); + + it('is not derived for a table that is not managed', () => { + const [task, ...variants] = taskModels(); + expect( + checkNames(build([{ ...task, control: 'external' } as ModelNode, ...variants]), 'task'), + ).toEqual([]); + }); +}); diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.discriminator-value-set.test.ts b/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.discriminator-value-set.test.ts new file mode 100644 index 000000000000..86fa008c6141 --- /dev/null +++ b/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.discriminator-value-set.test.ts @@ -0,0 +1,154 @@ +import { crossRef } from '@internal/contract/types'; +import type { TargetPackRef } from '@internal/framework-components/components'; +import { describe, expect, it } from 'vitest'; +import { createTestSqlNamespace } from '../../../1-core/contract/test/test-support'; +import { testTypeLookups } from '../../../1-core/contract/test/test-type-lookups'; +import { buildSqlContractFromDefinition } from '../src/contract-builder'; +import type { ContractDefinition } from '../src/contract-definition'; +import { enumType, member } from '../src/enum-type'; + +const postgresTargetPack: TargetPackRef<'sql', 'postgres'> = { + kind: 'target', + id: 'postgres', + familyId: 'sql', + targetId: 'postgres', + version: '0.0.1', + defaultNamespaceId: 'public', +}; + +const int4 = { codecId: 'pg/int4@1' } as const; +const text = { codecId: 'pg/text@1' } as const; + +function definitionWith( + overrides: Partial> & { + readonly typeEnum?: ReturnType; + readonly variants?: Readonly>; + readonly namespaceId?: string; + } = {}, +): ContractDefinition { + const variants = overrides.variants ?? { bug: 'Bug', feature: 'Feature' }; + const namespaceId = overrides.namespaceId ?? 'public'; + const inNamespace = overrides.namespaceId !== undefined ? { namespaceId } : {}; + return { + warnings: undefined, + target: postgresTargetPack, + createNamespace: createTestSqlNamespace, + ...(overrides.enums !== undefined ? { enums: overrides.enums } : {}), + models: [ + { + modelName: 'Task', + tableName: 'task', + ...inNamespace, + fields: [ + { fieldName: 'id', columnName: 'id', descriptor: int4, nullable: false }, + { + fieldName: 'type', + columnName: 'type', + descriptor: text, + nullable: false, + ...(overrides.typeEnum !== undefined ? { enumTypeHandle: overrides.typeEnum } : {}), + }, + ], + id: { columns: ['id'] }, + discriminator: { field: 'type' }, + variants: Object.fromEntries( + Object.entries(variants).map(([value, model]) => [value, crossRef(model, namespaceId)]), + ), + }, + ...Object.values(variants).map((model) => ({ + modelName: model, + tableName: 'task', + ...inNamespace, + fields: [], + sharesBaseTable: true, + base: crossRef('Task', namespaceId), + })), + ], + }; +} + +function build(definition: ContractDefinition) { + return buildSqlContractFromDefinition( + definition, + testTypeLookups.codecLookup, + testTypeLookups.dataTypeLookup, + ); +} + +describe('the value set of a discriminator', () => { + it('is written as ., and the discriminator column refers to it', () => { + const contract = build(definitionWith()); + const namespace = contract.storage.namespaces['public']; + expect({ + valueSet: JSON.parse(JSON.stringify(namespace?.entries.valueSet?.['Task.type'])), + column: namespace?.entries.table?.['task']?.columns['type']?.valueSet, + task: { + discriminator: contract.domain.namespaces['public']?.models['Task']?.discriminator, + variants: contract.domain.namespaces['public']?.models['Task']?.variants, + }, + bugBase: contract.domain.namespaces['public']?.models['Bug']?.base, + }).toEqual({ + valueSet: { kind: 'valueSet', values: ['bug', 'feature'] }, + column: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'public', + entityName: 'Task.type', + }, + task: { + discriminator: { field: 'type' }, + variants: { bug: crossRef('Bug', 'public'), feature: crossRef('Feature', 'public') }, + }, + bugBase: crossRef('Task', 'public'), + }); + }); + + it('holds the values in sorted order, so the order the variants are declared in does not change the storage hash', () => { + const declaredFeatureFirst = build( + definitionWith({ variants: { feature: 'Feature', '10': 'Ten', bug: 'Bug', '2': 'Two' } }), + ); + const declaredBugFirst = build( + definitionWith({ variants: { bug: 'Bug', '2': 'Two', feature: 'Feature', '10': 'Ten' } }), + ); + expect({ + values: + declaredFeatureFirst.storage.namespaces['public']?.entries.valueSet?.['Task.type']?.values, + sameHash: declaredFeatureFirst.storage.storageHash === declaredBugFirst.storage.storageHash, + }).toEqual({ values: ['10', '2', 'bug', 'feature'], sameHash: true }); + }); + + it('changes the storage hash when a variant is added', () => { + expect(build(definitionWith({ variants: { bug: 'Bug' } })).storage.storageHash).not.toBe( + build(definitionWith()).storage.storageHash, + ); + }); + + it('is not written when an enum types the discriminator field, which keeps the enum value set', () => { + const Kind = enumType('Kind', text, member('Bug', 'bug'), member('Feature', 'feature')); + const namespace = build(definitionWith({ enums: { Kind }, typeEnum: Kind })).storage.namespaces[ + 'public' + ]; + expect({ + valueSets: Object.keys(namespace?.entries.valueSet ?? {}), + column: namespace?.entries.table?.['task']?.columns['type']?.valueSet?.entityName, + }).toEqual({ valueSets: ['Kind'], column: 'Kind' }); + }); + + it('refuses an enum whose name is the value set key of a discriminator, naming both', () => { + const clash = enumType('Task.type', text, member('Bug', 'bug')); + expect(() => build(definitionWith({ enums: { 'Task.type': clash } }))).toThrow( + 'enumType("Task.type"): has the name of the value set that the discriminator field "type" of model "Task" writes. Rename the enum.', + ); + }); + + it('is written beside an enum of the same name in another namespace, since the two value sets do not share an entry', () => { + const sameName = enumType('Task.type', text, member('Bug', 'bug')); + const contract = build( + definitionWith({ enums: { 'Task.type': sameName }, namespaceId: 'other' }), + ); + expect({ + public: contract.storage.namespaces['public']?.entries.valueSet?.['Task.type']?.values, + other: contract.storage.namespaces['other']?.entries.valueSet?.['Task.type']?.values, + }).toEqual({ public: ['bug'], other: ['bug', 'feature'] }); + }); +}); diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.value-object-domain.test.ts b/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.value-object-domain.test.ts index c647ce3cd36a..14216a77a1e2 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.value-object-domain.test.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/contract-builder.value-object-domain.test.ts @@ -6,6 +6,16 @@ import { buildSqlContractFromDefinition } from '../src/contract-builder'; import { enumType, member } from '../src/enum-type'; import { valueObjectsOf } from './contract-test-helpers'; +function valueObjectRef(entityName: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName, + } as const; +} + const postgresTargetPack: TargetPackRef<'sql', 'postgres'> = { kind: 'target', id: 'postgres', @@ -23,8 +33,8 @@ const numeric = { } as const; const idField = { fieldName: 'id', columnName: 'id', descriptor: int4, nullable: false } as const; -describe('value-object members and value-object fields in the domain', () => { - it('types a model field by its value object, optional and list', () => { +describe('value objects in the storage plane, and the fields that refer to them', () => { + it('types a model field by the storage coordinate of its value object, optional and list', () => { const jsonb = { codecId: 'pg/jsonb@1' } as const; const contract = buildSqlContractFromDefinition( { @@ -67,16 +77,16 @@ describe('value-object members and value-object fields in the domain', () => { const { id: _id, ...fields } = contract.domain.namespaces['public']?.models['User']?.fields ?? {}; expect(fields).toEqual({ - home: { type: { kind: 'valueObject', name: 'Address' }, nullable: true, many: false }, + home: { type: valueObjectRef('Address'), nullable: true, many: false }, addresses: { - type: { kind: 'valueObject', name: 'Address' }, + type: valueObjectRef('Address'), nullable: false, many: { elementNullable: false }, }, }); }); - it('builds value-object members like model fields, keeping many, type parameters and the domain valueSet of an enum', () => { + it('builds value-object members like model fields, keeping many and type parameters, with the storage value set of an enum', () => { const Country = enumType('Country', text, member('DE', 'DE'), member('FR', 'FR')); const scalarFields = [ { fieldName: 'amount', descriptor: numeric, nullable: false }, @@ -149,17 +159,11 @@ describe('value-object members and value-object fields in the domain', () => { testTypeLookups.dataTypeLookup, ); - const namespace = contract.domain.namespaces['public']; - const { - id: _id, - shipping: _shipping, - ...modelFields - } = namespace?.models['Order']?.fields ?? {}; - const { stops, ...memberFields } = namespace?.valueObjects?.['Shipping']?.fields ?? {}; - expect({ memberFields, stops }).toEqual({ - memberFields: modelFields, + const { stops, ...memberFields } = valueObjectsOf(contract)?.['Shipping']?.fields ?? {}; + expect({ stops, domainKeys: Object.keys(contract.domain.namespaces['public'] ?? {}) }).toEqual({ + domainKeys: ['models', 'enum'], stops: { - type: { kind: 'valueObject', name: 'Stop' }, + type: valueObjectRef('Stop'), nullable: false, many: { elementNullable: true }, }, @@ -180,8 +184,8 @@ describe('value-object members and value-object fields in the domain', () => { nullable: false, many: false, valueSet: { - plane: 'domain', - entityKind: 'enum', + plane: 'storage', + entityKind: 'valueSet', namespaceId: 'public', entityName: 'Country', }, @@ -191,8 +195,8 @@ describe('value-object members and value-object fields in the domain', () => { nullable: true, many: { elementNullable: false }, valueSet: { - plane: 'domain', - entityKind: 'enum', + plane: 'storage', + entityKind: 'valueSet', namespaceId: 'public', entityName: 'Country', }, @@ -242,7 +246,7 @@ describe('value-object members and value-object fields in the domain', () => { fields: { street: { type: { kind: 'scalar', codecId: 'pg/text@1' }, nullable: false, many: false }, location: { - type: { kind: 'valueObject', name: 'GeoLocation' }, + type: valueObjectRef('GeoLocation'), nullable: true, many: false, }, @@ -251,7 +255,7 @@ describe('value-object members and value-object fields in the domain', () => { }); }); - it('omits valueObjects from the contract when none are defined', () => { + it('writes no valueObject slot when none are defined', () => { const contract = buildSqlContractFromDefinition( { warnings: undefined, diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/contract-test-helpers.ts b/packages/2-sql-family/2-authoring/contract-ts/test/contract-test-helpers.ts index 279f6a45be19..5989db6e82bc 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/contract-test-helpers.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/contract-test-helpers.ts @@ -1,8 +1,5 @@ import type { Contract, ContractValueObjectDefinitions } from '@internal/contract/types'; -import { - domainModelsAtDefaultNamespace, - domainValueObjectsAtDefaultNamespace, -} from '@internal/contract/types'; +import { domainModelsAtDefaultNamespace } from '@internal/contract/types'; import { blindCast } from '@internal/utils/casts'; /** Models map for the contract's sole domain namespace, precise per-namespace. */ @@ -13,12 +10,18 @@ export function modelsOf(contract: T): SoleNamespaceModels; } +/** The `valueObject` slot of the contract's sole storage namespace, or `undefined` when it has none. */ export function valueObjectsOf( contract: T, ): ContractValueObjectDefinitions | undefined { - return domainValueObjectsAtDefaultNamespace(contract.domain) as - | ContractValueObjectDefinitions - | undefined; + const [namespace, ...others] = Object.values(contract.storage.namespaces); + if (namespace === undefined || others.length > 0) { + throw new Error('valueObjectsOf reads a contract with exactly one storage namespace'); + } + return blindCast< + ContractValueObjectDefinitions | undefined, + 'test helper reads the storage valueObject slot the build wrote' + >(namespace.entries['valueObject']); } /** Flat model map for runtime assertions when model types are widened by the test harness. */ diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/data-contract-json-schema.test.ts b/packages/2-sql-family/2-authoring/contract-ts/test/data-contract-json-schema.test.ts index bab6dae75e4f..cb0ba0454ce3 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/data-contract-json-schema.test.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/data-contract-json-schema.test.ts @@ -6,6 +6,8 @@ import { generateDataContractJsonSchema } from '../src/data-contract-json-schema import { validSqlContractJson } from './sql-contract-json-fixture'; import { storageWithNamespacedTables } from './storage-with-namespaced-tables'; +const text = { kind: 'scalar', codecId: 'pg/text@1' }; + const checkedInSchemaPath = join(import.meta.dirname, '..', 'schemas', 'data-contract-sql-v1.json'); function compileGeneratedSchema() { @@ -58,7 +60,7 @@ describe('data contract JSON schema', () => { models: { User: { storage: { namespaceId: '__unbound__', table: 'User', fields: {} }, - fields: { tags: { type: 'String', nullable: false, many } }, + fields: { tags: { type: text, nullable: false, many } }, }, }, } @@ -171,6 +173,32 @@ describe('data contract JSON schema', () => { expect(validate(contract)).toBe(false); }); + it.each([ + { kind: 'a scalar', member: { nullable: false, type: text }, valid: true }, + { + kind: 'a union', + member: { nullable: false, type: { kind: 'union', members: [text] } }, + valid: false, + }, + { kind: 'a dictionary', member: { nullable: false, type: text, dict: true }, valid: false }, + ])('validates a storage value object member that is $kind as $valid', ({ member, valid }) => { + const validate = new Ajv2020({ strict: false }).compile( + JSON.parse(readFileSync(checkedInSchemaPath, 'utf8')), + ); + const contract = validSqlContractJson({ + storage: { + storageHash: 'test', + namespaces: { + __unbound__: { + id: '__unbound__', + entries: { table: {}, valueObject: { Shape: { fields: { either: member } } } }, + }, + }, + }, + }); + expect(validate(contract)).toBe(valid); + }); + it('accepts pack-contributed entry kinds as generic entity maps', () => { const validate = compileGeneratedSchema(); const contract = validSqlContractJson({ diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/fixtures/managed-user-contract.ts b/packages/2-sql-family/2-authoring/contract-ts/test/fixtures/managed-user-contract.ts index c1e1c480ab34..5879acb902ad 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/fixtures/managed-user-contract.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/fixtures/managed-user-contract.ts @@ -1,4 +1,5 @@ import type { FamilyPackRef } from '@internal/framework-components/components'; +import type { CheckExpressionInput } from '@internal/sql-contract/check-expressions'; import { createTestSqlNamespace } from '../../../../1-core/contract/test/test-support'; import { testTypeLookups } from '../../../../1-core/contract/test/test-type-lookups'; import { defineContract } from '../../src/contract-builder'; @@ -11,13 +12,7 @@ const sqlFamilyPack = { version: '0.0.1', } as const satisfies FamilyPackRef<'sql'>; -export function renderCheckExpressions(input: { - readonly tableName: string; - readonly columnName: string; - readonly many: boolean; - readonly elementNullable: boolean; - readonly memberValues: readonly string[] | undefined; -}): ReadonlyArray<{ +export function renderCheckExpressions(input: CheckExpressionInput): ReadonlyArray<{ readonly kind: 'membership' | 'elementNotNull'; readonly columnName: string; readonly expression: string; @@ -39,7 +34,7 @@ export function renderCheckExpressions(input: { : `${column} IN (${members})`, }); } - if (input.many && !input.elementNullable) { + if (input.many !== false && !input.many.elementNullable) { candidates.push({ kind: 'elementNotNull', columnName: input.columnName, diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/merge-tables.test.ts b/packages/2-sql-family/2-authoring/contract-ts/test/merge-tables.test.ts index 30b54f25e6e7..9dfc89adea22 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/merge-tables.test.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/merge-tables.test.ts @@ -10,6 +10,7 @@ function column(modelName: string, columnName: string, nullable = true): ColumnD many: false, default: undefined, enumTypeHandle: undefined, + valueObject: undefined, site: { kind: 'field', modelName, fieldName: columnName }, }; } diff --git a/packages/2-sql-family/2-authoring/contract-ts/test/table-node-helpers.ts b/packages/2-sql-family/2-authoring/contract-ts/test/table-node-helpers.ts index ef8dcb1b41e2..257990ae1664 100644 --- a/packages/2-sql-family/2-authoring/contract-ts/test/table-node-helpers.ts +++ b/packages/2-sql-family/2-authoring/contract-ts/test/table-node-helpers.ts @@ -1,4 +1,5 @@ import type { Contract } from '@internal/contract/types'; +import type { CheckExpressionInput } from '@internal/sql-contract/check-expressions'; import type { SqlStorage } from '@internal/sql-contract/types'; import { invariant } from '@internal/utils/assertions'; import { createTestSqlNamespace } from '../../../1-core/contract/test/test-support'; @@ -14,13 +15,7 @@ import { type ValueObjectFieldNode, } from '../src/contract-definition'; -interface RenderInput { - readonly tableName: string; - readonly columnName: string; - readonly many: boolean; - readonly elementNullable: boolean; - readonly memberValues: readonly (string | number)[] | undefined; -} +type RenderInput = CheckExpressionInput; function renderCheckExpressions(input: RenderInput): ReadonlyArray<{ readonly kind: 'membership' | 'elementNotNull'; @@ -38,7 +33,7 @@ function renderCheckExpressions(input: RenderInput): ReadonlyArray<{ }, ] : []), - ...(input.many && !input.elementNullable + ...(input.many !== false && !input.many.elementNullable ? [ { kind: 'elementNotNull' as const, diff --git a/packages/2-sql-family/3-tooling/emitter/src/index.ts b/packages/2-sql-family/3-tooling/emitter/src/index.ts index ce6c8d53235c..1454a81da83d 100644 --- a/packages/2-sql-family/3-tooling/emitter/src/index.ts +++ b/packages/2-sql-family/3-tooling/emitter/src/index.ts @@ -5,6 +5,7 @@ import type { JsonValue, } from '@internal/contract/types'; import { + generateValueObjectsDescriptorType, renderCodecTypeOverride, renderTypeOverridesMap, renderValueSetOverride, @@ -524,6 +525,12 @@ function generateStorageNamespacesType(namespaces: SqlStorage['namespaces']): st if (valueSetEntries !== undefined && Object.keys(valueSetEntries).length > 0) { entriesParts.push(`readonly valueSet: ${generateValueSetBlockType(valueSetEntries)}`); } + const valueObjectEntries = ns.entries.valueObject; + if (valueObjectEntries !== undefined && Object.keys(valueObjectEntries).length > 0) { + entriesParts.push( + `readonly valueObject: ${generateValueObjectsDescriptorType(valueObjectEntries)}`, + ); + } const entriesType = `{ ${entriesParts.join('; ')} }`; parts.push( `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}${kindSuffix}; readonly entries: ${entriesType} }`, diff --git a/packages/2-sql-family/3-tooling/emitter/test/create-emitter-test-contract.ts b/packages/2-sql-family/3-tooling/emitter/test/create-emitter-test-contract.ts index 06613a9c0dbe..85145ff14c9e 100644 --- a/packages/2-sql-family/3-tooling/emitter/test/create-emitter-test-contract.ts +++ b/packages/2-sql-family/3-tooling/emitter/test/create-emitter-test-contract.ts @@ -5,9 +5,28 @@ import { profileHash, UNBOUND_DOMAIN_NAMESPACE_ID, } from '@internal/contract/types'; +import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { applicationDomainOf } from '@repo/test-utils'; import { normalizeRootSqlStorage } from './sql-storage-fixture'; +function withValueObjects( + storage: Contract['storage'], + valueObjects: Record, +): Contract['storage'] { + const namespace = storage.namespaces[UNBOUND_NAMESPACE_ID]; + return { + ...storage, + namespaces: { + ...storage.namespaces, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + ...namespace, + entries: { ...namespace?.entries, valueObject: valueObjects }, + }, + }, + }; +} + export function createEmitterTestContract( overrides: Partial> & { models?: Record | undefined; @@ -16,9 +35,13 @@ export function createEmitterTestContract( } = {}, ): Contract { const { models, domain, storage, valueObjects, ...rest } = overrides; - const resolvedStorage = Object.hasOwn(overrides, 'storage') + const normalizedStorage = Object.hasOwn(overrides, 'storage') ? normalizeRootSqlStorage(storage) : normalizeRootSqlStorage({ tables: {} }); + const resolvedStorage = + valueObjects === undefined || normalizedStorage === undefined + ? normalizedStorage + : withValueObjects(normalizedStorage, valueObjects); const contract: Contract = { targetFamily: 'sql', target: 'test-db', @@ -27,7 +50,6 @@ export function createEmitterTestContract( domain ?? applicationDomainOf({ models: models ?? {}, - ...(valueObjects !== undefined ? { valueObjects } : {}), namespaceId: UNBOUND_DOMAIN_NAMESPACE_ID, }), extensions: {}, diff --git a/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts b/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts index 2a346eea0e15..3dd0e664ffe3 100644 --- a/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts +++ b/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts @@ -10,6 +10,7 @@ import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { describe, expect, it } from 'vitest'; import { sqlEmission } from '../src/index'; import { createEmitterTestContract as createContract } from './create-emitter-test-contract'; +import { identityCodecLookup } from './value-set-codec-lookups'; type TestDescriptor = | ControlTargetDescriptor<'sql', string> @@ -1052,7 +1053,7 @@ describe('sql-target-family-hook', () => { ); }); - it('emits valueObjects descriptor on ContractBase', () => { + it('emits value objects in the storage namespace entries, and nowhere else', () => { const ir = createContract({ valueObjects: { Address: { @@ -1068,8 +1069,10 @@ describe('sql-target-family-hook', () => { storage: { tables: {} }, }); const types = generateContractDts(ir, sqlEmission, [], testHashes); - expect(types).toContain('readonly valueObjects:'); - expect(types).toContain('readonly Address: { readonly fields:'); + expect(types).toContain( + 'readonly entries: { readonly table: {}; readonly valueObject: { readonly Address: { readonly fields: { readonly street: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "pg/text@1" } } } } } }', + ); + expect(types).not.toContain('readonly valueObjects:'); }); it('emits model fields referencing value objects with valueObject kind', () => { @@ -1089,7 +1092,13 @@ describe('sql-target-family-hook', () => { homeAddress: { nullable: true, many: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, }, }, relations: {}, @@ -1123,7 +1132,7 @@ describe('sql-target-family-hook', () => { }); const types = generateContractDts(ir, sqlEmission, [], testHashes); expect(types).toContain( - 'readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly name: "Address" } }', + 'readonly homeAddress: { readonly nullable: true; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Address" } }', ); }); @@ -1143,7 +1152,13 @@ describe('sql-target-family-hook', () => { id: { nullable: false, many: false, type: { kind: 'scalar', codecId: 'pg/int4@1' } }, addresses: { nullable: false, - type: { kind: 'valueObject', name: 'Address' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'Address', + }, many: { elementNullable: false }, }, }, @@ -1178,7 +1193,7 @@ describe('sql-target-family-hook', () => { }); const types = generateContractDts(ir, sqlEmission, [], testHashes); expect(types).toContain( - 'readonly addresses: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly name: "Address" }; readonly many: { readonly elementNullable: false } }', + 'readonly addresses: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "Address" }; readonly many: { readonly elementNullable: false } }', ); }); @@ -1194,7 +1209,13 @@ describe('sql-target-family-hook', () => { }, children: { nullable: false, - type: { kind: 'valueObject', name: 'NavItem' }, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: '__unbound__', + entityKind: 'valueObject', + entityName: 'NavItem', + }, many: { elementNullable: false }, }, }, @@ -1206,16 +1227,60 @@ describe('sql-target-family-hook', () => { expect(types).toContain('export type NavItemOutput ='); expect(types).toContain('export type NavItemInput ='); expect(types).toContain( - 'readonly children: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly name: "NavItem" }; readonly many: { readonly elementNullable: false } }', + 'readonly children: { readonly nullable: false; readonly type: { readonly kind: "valueObject"; readonly plane: "storage"; readonly namespaceId: "__unbound__"; readonly entityKind: "valueObject"; readonly entityName: "NavItem" }; readonly many: { readonly elementNullable: false } }', ); }); - it('omits valueObjects when none exist', () => { + it('omits value objects when none exist', () => { const ir = createContract({ storage: { tables: {} }, }); const types = generateContractDts(ir, sqlEmission, [], testHashes); - expect(types).not.toContain('valueObjects'); + expect(types).not.toMatch(/valueObject/i); + }); + + it('types an enum member of a value object by the storage value set it names', () => { + const ir = createContract({ + valueObjects: { + Address: { + fields: { + kind: { + nullable: false, + many: false, + type: { kind: 'scalar', codecId: 'pg/text@1' }, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: '__unbound__', + entityName: 'Kind', + }, + }, + }, + }, + }, + storage: { + namespaces: { + __unbound__: { + id: '__unbound__', + entries: { + table: {}, + valueSet: { Kind: { kind: 'valueSet', values: ['home', 'work'] } }, + }, + }, + }, + }, + }); + const types = generateContractDts( + ir, + sqlEmission, + [], + testHashes, + undefined, + identityCodecLookup, + ); + expect(types).toContain( + 'type FieldTypeOverrides = { readonly __unbound__: { readonly valueObjects: { readonly Address: { readonly kind: { readonly output: "home" | "work"; readonly input: "home" | "work" } } } } };', + ); }); it('emits nullable value object type alias field', () => { diff --git a/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts b/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts index 6b89e81192fa..950db0a56187 100644 --- a/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts +++ b/packages/2-sql-family/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts @@ -816,7 +816,7 @@ describe('StorageColumnTypes', () => { const dts = generateContractDts(contract, sqlEmission, [], testHashes); expect(dts).toContain( - "export type FieldOutputTypes = FieldTypesFromDomain;", + "export type FieldOutputTypes = FieldTypesFromDomain;", ); expect(dts).toContain( 'readonly tags: { readonly nullable: false; readonly type: { readonly kind: "scalar"; readonly codecId: "pg/text@1" }; readonly many: { readonly elementNullable: false } }', diff --git a/packages/2-sql-family/4-lanes/relational-core/README.md b/packages/2-sql-family/4-lanes/relational-core/README.md index a113dc0631f5..fb416f7add6e 100644 --- a/packages/2-sql-family/4-lanes/relational-core/README.md +++ b/packages/2-sql-family/4-lanes/relational-core/README.md @@ -120,6 +120,7 @@ class PgTextCodec extends CodecImpl<'pg/text@1', readonly ['equality'], string, class PgTextDescriptor extends PostgresCodecDescriptor { override readonly dataType = pgText.id; + override readonly jsonType = 'string' as const; override readonly codecId = 'pg/text@1'; override readonly traits = ['equality'] as const; override readonly paramsSchema = undefined; diff --git a/packages/2-sql-family/4-lanes/relational-core/src/ast/sql-codecs.ts b/packages/2-sql-family/4-lanes/relational-core/src/ast/sql-codecs.ts index ba13b4b06537..36c7b4cdbaaa 100644 --- a/packages/2-sql-family/4-lanes/relational-core/src/ast/sql-codecs.ts +++ b/packages/2-sql-family/4-lanes/relational-core/src/ast/sql-codecs.ts @@ -75,6 +75,7 @@ export class SqlTextCodec extends CodecImpl< } export class SqlTextDescriptor extends CodecDescriptorTemplateImpl { + readonly jsonType = 'string' as const; override readonly codecId = SQL_TEXT_CODEC_ID; override readonly traits = ['equality', 'order', 'textual'] as const; override readonly paramsSchema = undefined; @@ -113,6 +114,7 @@ export class SqlIntCodec extends CodecImpl< } export class SqlIntDescriptor extends CodecDescriptorTemplateImpl { + readonly jsonType = 'number' as const; override readonly codecId = SQL_INT_CODEC_ID; override readonly traits = ['equality', 'order', 'numeric'] as const; override readonly paramsSchema = undefined; @@ -150,6 +152,7 @@ export class SqlFloatCodec extends CodecImpl< } export class SqlFloatDescriptor extends CodecDescriptorTemplateImpl { + readonly jsonType = 'number' as const; override readonly codecId = SQL_FLOAT_CODEC_ID; override readonly traits = ['equality', 'order', 'numeric'] as const; override readonly paramsSchema = undefined; @@ -187,6 +190,7 @@ export class SqlCharCodec extends CodecImpl< } export class SqlCharDescriptor extends CodecDescriptorTemplateImpl { + readonly jsonType = 'string' as const; override readonly codecId = SQL_CHAR_CODEC_ID; override readonly traits = ['equality', 'order', 'textual'] as const; override readonly paramsSchema = undefined; @@ -224,6 +228,7 @@ export class SqlVarcharCodec extends CodecImpl< } export class SqlVarcharDescriptor extends CodecDescriptorTemplateImpl { + readonly jsonType = 'string' as const; override readonly codecId = SQL_VARCHAR_CODEC_ID; override readonly traits = ['equality', 'order', 'textual'] as const; override readonly paramsSchema = undefined; @@ -285,6 +290,7 @@ export class SqlRawCodec extends CodecImpl { + readonly jsonType = 'any' as const; override readonly codecId = SQL_RAW_CODEC_ID; override readonly traits = [] as const; override readonly enumRefusal = diff --git a/packages/2-sql-family/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts b/packages/2-sql-family/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts index 7a2058a0a1fe..d3d2ec5974d6 100644 --- a/packages/2-sql-family/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts +++ b/packages/2-sql-family/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -773,7 +775,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts b/packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts index e5d56647cbe5..5096bff022f4 100644 --- a/packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts +++ b/packages/3-extensions/arktype-json/src/core/arktype-json-codec.ts @@ -179,6 +179,7 @@ const arktypeJsonParamsSchema = type({ }) satisfies StandardSchemaV1; export class ArktypeJsonDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; /** The document's JSON text, which `fromWire` parses as it parses a row; a document that is a string would otherwise reach it unquoted. */ protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); diff --git a/packages/3-extensions/paradedb/migrations/snapshots/0c0734babd6eeb868fee1f281ca96963022475611560e9f170f465daa35f8599/contract.d.ts b/packages/3-extensions/paradedb/migrations/snapshots/0c0734babd6eeb868fee1f281ca96963022475611560e9f170f465daa35f8599/contract.d.ts index 3f411499b2fb..c4748b39956e 100644 --- a/packages/3-extensions/paradedb/migrations/snapshots/0c0734babd6eeb868fee1f281ca96963022475611560e9f170f465daa35f8599/contract.d.ts +++ b/packages/3-extensions/paradedb/migrations/snapshots/0c0734babd6eeb868fee1f281ca96963022475611560e9f170f465daa35f8599/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -368,7 +370,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/paradedb/src/contract.d.ts b/packages/3-extensions/paradedb/src/contract.d.ts index 3f411499b2fb..c4748b39956e 100644 --- a/packages/3-extensions/paradedb/src/contract.d.ts +++ b/packages/3-extensions/paradedb/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -368,7 +370,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/pgvector/migrations/snapshots/4a96b488a4ce92b434e5f7d6607b6435c0955f0d36b0018077045787764240e6/contract.d.ts b/packages/3-extensions/pgvector/migrations/snapshots/4a96b488a4ce92b434e5f7d6607b6435c0955f0d36b0018077045787764240e6/contract.d.ts index 9f816cdefb5f..5d014b849ee5 100644 --- a/packages/3-extensions/pgvector/migrations/snapshots/4a96b488a4ce92b434e5f7d6607b6435c0955f0d36b0018077045787764240e6/contract.d.ts +++ b/packages/3-extensions/pgvector/migrations/snapshots/4a96b488a4ce92b434e5f7d6607b6435c0955f0d36b0018077045787764240e6/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -376,7 +378,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/pgvector/src/contract.d.ts b/packages/3-extensions/pgvector/src/contract.d.ts index 9f816cdefb5f..5d014b849ee5 100644 --- a/packages/3-extensions/pgvector/src/contract.d.ts +++ b/packages/3-extensions/pgvector/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -376,7 +378,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/pgvector/src/core/codecs.ts b/packages/3-extensions/pgvector/src/core/codecs.ts index fc741e0d07b3..f13c2de3e0e7 100644 --- a/packages/3-extensions/pgvector/src/core/codecs.ts +++ b/packages/3-extensions/pgvector/src/core/codecs.ts @@ -140,6 +140,7 @@ const vectorTextJsonProjection = (expression: ProjectionExpr): ProjectionExpr => CastExpr.as(expression, 'text'); export class PgVectorDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'array' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return vectorTextJsonProjection(expression); } diff --git a/packages/3-extensions/postgis/migrations/snapshots/2db551a8a5619d0d97710f29130eaea0eccbdd5238f015ee9d18408d438e0f8a/contract.d.ts b/packages/3-extensions/postgis/migrations/snapshots/2db551a8a5619d0d97710f29130eaea0eccbdd5238f015ee9d18408d438e0f8a/contract.d.ts index 0556e3ca8e35..68057e10f7d4 100644 --- a/packages/3-extensions/postgis/migrations/snapshots/2db551a8a5619d0d97710f29130eaea0eccbdd5238f015ee9d18408d438e0f8a/contract.d.ts +++ b/packages/3-extensions/postgis/migrations/snapshots/2db551a8a5619d0d97710f29130eaea0eccbdd5238f015ee9d18408d438e0f8a/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -376,7 +378,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/postgis/src/contract.d.ts b/packages/3-extensions/postgis/src/contract.d.ts index 0556e3ca8e35..68057e10f7d4 100644 --- a/packages/3-extensions/postgis/src/contract.d.ts +++ b/packages/3-extensions/postgis/src/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -376,7 +378,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/postgis/src/core/codecs.ts b/packages/3-extensions/postgis/src/core/codecs.ts index 8b589977f132..85622fdb3fa1 100644 --- a/packages/3-extensions/postgis/src/core/codecs.ts +++ b/packages/3-extensions/postgis/src/core/codecs.ts @@ -115,6 +115,7 @@ export class PostgisGeometryCodec extends CodecImpl< } export class PostgisGeometryDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'object' as const; /** The HEXEWKB text PostgreSQL prints for a geometry, which `fromWire` reads as it reads a row. A JSON constructor handed the geometry itself may write GeoJSON instead. */ protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); diff --git a/packages/3-extensions/sql-orm-client/package.json b/packages/3-extensions/sql-orm-client/package.json index 78a821f48183..e22378ddac7d 100644 --- a/packages/3-extensions/sql-orm-client/package.json +++ b/packages/3-extensions/sql-orm-client/package.json @@ -8,8 +8,8 @@ "description": "ORM client for Prisma 8 — fluent, type-safe model collections", "scripts": { "build": "tsdown", - "emit": "cd ../../../test/integration && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/polymorphism/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/polymorphism-self-relation/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/polymorphism-namespaces/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/soft-delete/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/fragment-namespace/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/scope-namespace/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/junction-namespaces/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/execution-defaulted-tags/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/mn-psl/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/self-relations/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/collection-methods/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/on-update-only/prisma.config.ts && cp test/sql-orm-client/fixtures/generated/contract.json test/sql-orm-client/fixtures/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/generated/ && cp test/sql-orm-client/fixtures/junction-namespaces/generated/contract.json test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/ && cp test/sql-orm-client/fixtures/polymorphism/generated/contract.json test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/ && cp test/sql-orm-client/fixtures/soft-delete/generated/contract.json test/sql-orm-client/fixtures/soft-delete/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/ && cp test/sql-orm-client/fixtures/fragment-namespace/generated/contract.json test/sql-orm-client/fixtures/fragment-namespace/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/fragment-namespace/generated/ && cp test/sql-orm-client/fixtures/scope-namespace/generated/contract.json test/sql-orm-client/fixtures/scope-namespace/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/scope-namespace/generated/ && cp test/sql-orm-client/fixtures/collection-methods/generated/contract.json test/sql-orm-client/fixtures/collection-methods/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/ && cp test/sql-orm-client/fixtures/on-update-only/generated/contract.json test/sql-orm-client/fixtures/on-update-only/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/on-update-only/generated/ && cd ../../packages/3-extensions/sql-orm-client && node scripts/strip-pgvector-fixture.mjs test/fixtures/generated/contract.d.ts", - "emit:check": "pnpm emit && git diff --exit-code test/fixtures/generated/ test/fixtures/junction-namespaces/generated/ test/fixtures/polymorphism/generated/ test/fixtures/soft-delete/generated/ test/fixtures/fragment-namespace/generated/ test/fixtures/scope-namespace/generated/ test/fixtures/collection-methods/generated/ test/fixtures/on-update-only/generated/", + "emit": "cd ../../../test/integration && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/polymorphism/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/polymorphism-self-relation/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/polymorphism-namespaces/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/soft-delete/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/value-objects/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/fragment-namespace/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/scope-namespace/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/junction-namespaces/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/execution-defaulted-tags/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/mn-psl/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/self-relations/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/collection-methods/prisma.config.ts && node ../../packages/1-framework/3-tooling/cli/dist/bin.mjs contract emit --config test/sql-orm-client/fixtures/on-update-only/prisma.config.ts && cp test/sql-orm-client/fixtures/generated/contract.json test/sql-orm-client/fixtures/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/generated/ && cp test/sql-orm-client/fixtures/junction-namespaces/generated/contract.json test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/ && cp test/sql-orm-client/fixtures/polymorphism/generated/contract.json test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/ && cp test/sql-orm-client/fixtures/soft-delete/generated/contract.json test/sql-orm-client/fixtures/soft-delete/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/ && cp test/sql-orm-client/fixtures/value-objects/generated/contract.json test/sql-orm-client/fixtures/value-objects/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/ && cp test/sql-orm-client/fixtures/fragment-namespace/generated/contract.json test/sql-orm-client/fixtures/fragment-namespace/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/fragment-namespace/generated/ && cp test/sql-orm-client/fixtures/scope-namespace/generated/contract.json test/sql-orm-client/fixtures/scope-namespace/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/scope-namespace/generated/ && cp test/sql-orm-client/fixtures/collection-methods/generated/contract.json test/sql-orm-client/fixtures/collection-methods/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/ && cp test/sql-orm-client/fixtures/on-update-only/generated/contract.json test/sql-orm-client/fixtures/on-update-only/generated/contract.d.ts ../../packages/3-extensions/sql-orm-client/test/fixtures/on-update-only/generated/ && cd ../../packages/3-extensions/sql-orm-client && node scripts/strip-pgvector-fixture.mjs test/fixtures/generated/contract.d.ts", + "emit:check": "pnpm emit && git diff --exit-code test/fixtures/generated/ test/fixtures/junction-namespaces/generated/ test/fixtures/polymorphism/generated/ test/fixtures/soft-delete/generated/ test/fixtures/value-objects/generated/ test/fixtures/fragment-namespace/generated/ test/fixtures/scope-namespace/generated/ test/fixtures/collection-methods/generated/ test/fixtures/on-update-only/generated/", "test": "vitest run", "typecheck": "tsc --noEmit", "lint": "biome check . --error-on-warnings", diff --git a/packages/3-extensions/sql-orm-client/test/codec-async.types.test-d.ts b/packages/3-extensions/sql-orm-client/test/codec-async.types.test-d.ts index f232e4595941..fcc48bc59888 100644 --- a/packages/3-extensions/sql-orm-client/test/codec-async.types.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/codec-async.types.test-d.ts @@ -11,6 +11,7 @@ */ import type { Expression, ScopeField } from '@internal/sql-relational-core/expression'; +import type { JsonValue } from '@internal/utils/json'; import { expectTypeOf, test } from 'vitest'; import type { Collection } from '../src/collection'; import type { @@ -22,8 +23,9 @@ import type { UniqueConstraintCriterion, } from '../src/types'; import type { Contract } from './fixtures/generated/contract'; +import type { Contract as ValueObjectContract } from './fixtures/value-objects/generated/contract'; -// `User.address` is jsonb-backed (a value object whose fields and the field itself flow through the `pg/jsonb@1` codec); `User.invitedById` is int4 and nullable. Both codecs are lifted to async dispatch at the codec boundary regardless of whether the author wrote sync or async functions, so these columns are representative of "async codec columns" at the runtime boundary. +// `User.address` is jsonb-backed: in the value-objects fixture it holds a value object, whose fields and the field itself flow through the `pg/jsonb@1` codec. `User.invitedById` is int4 and nullable. Both codecs are lifted to async dispatch at the codec boundary regardless of whether the author wrote sync or async functions, so these columns are representative of "async codec columns" at the runtime boundary. // // See `packages/1-framework/1-core/framework-components/src/shared/codec.ts` (`Codec` interface — `encode`/`decode` return `Promise<…>`) — every author function is wrapped at the class boundary, which means every column is an "async codec" at the runtime layer. @@ -36,6 +38,9 @@ type AddressShape = { type IsPromiseLike = T extends Promise ? true : false; type UserRow = DefaultModelRow; +type ValueObjectUserRow = DefaultModelRow; +type ValueObjectUserCreate = CreateInput; +type ValueObjectUserUpdate = MutationUpdateInput; type UserCreate = CreateInput; type UserUpdate = MutationUpdateInput; type UserUnique = UniqueConstraintCriterion; @@ -56,7 +61,7 @@ test('DefaultModelRow exposes plain `T | null` for nullable pg/int4@1 columns', }); test('DefaultModelRow exposes plain object value (no Promise) for jsonb-backed value object', () => { - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); }); test('InferRootRow for non-polymorphic model equals DefaultModelRow', () => { @@ -75,7 +80,7 @@ test('Collection.first() resolves to a plain row | null (no Promise on row fi type Row = Exclude; expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf(); }); @@ -84,7 +89,7 @@ test('Collection async iteration yields a plain row (no Promise on row fields type IteratedRow = UserCollectionRow; expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); expectTypeOf>().toEqualTypeOf(); }); @@ -104,11 +109,11 @@ test('CreateInput accepts plain `string` for pg/text@1 fields', () => { }); test('CreateInput accepts plain object for jsonb-backed value-object field', () => { - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); }); test('MutationUpdateInput accepts plain T for jsonb-backed value-object field', () => { - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); }); test('MutationUpdateInput accepts plain `string` for pg/text@1 fields', () => { diff --git a/packages/3-extensions/sql-orm-client/test/codec-field-parity.types.test-d.ts b/packages/3-extensions/sql-orm-client/test/codec-field-parity.types.test-d.ts index b11274c2606d..48d1186e8aa2 100644 --- a/packages/3-extensions/sql-orm-client/test/codec-field-parity.types.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/codec-field-parity.types.test-d.ts @@ -1,27 +1,33 @@ -import type { ExtractCodecTypes } from '@internal/sql-contract/types'; +import type { Contract as SqlContract } from '@internal/contract/types'; +import type { ExtractCodecTypes, SqlStorage } from '@internal/sql-contract/types'; import { describe, expectTypeOf, test } from 'vitest'; import type { CodecField, CollectionModelName, ModelAccessor } from '../src/types'; +import type { Contract as ValueObjectContract } from './fixtures/value-objects/generated/contract'; import type { TestContract } from './helpers'; +type AnyContract = SqlContract; + type Exactly = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false; -type ModelName = CollectionModelName; -type Accessor = ModelAccessor; +type ModelName = CollectionModelName; +type Accessor> = ModelAccessor; -type ScalarFieldName = { - [F in keyof Accessor & string]: Accessor[F] extends { readonly returnType: unknown } +type ScalarFieldName> = { + [F in keyof Accessor & string]: Accessor[F] extends { + readonly returnType: unknown; + } ? F : never; -}[keyof Accessor & string]; +}[keyof Accessor & string]; -type CodecFieldOf = Field extends { +type CodecFieldOf = Field extends { readonly returnType: { - readonly codecId: infer Id extends keyof ExtractCodecTypes & string; + readonly codecId: infer Id extends keyof ExtractCodecTypes & string; readonly nullable: infer Nullable extends boolean; }; } - ? CodecField + ? CodecField : never; type MethodName = Exclude; @@ -38,36 +44,52 @@ type MethodDifferences = { : `${Path}.${K}`; }[MethodName]; -type FieldDifferences = { - [F in ScalarFieldName]: [CodecFieldOf[F]>] extends [never] +type FieldDifferences> = { + [F in ScalarFieldName]: [CodecFieldOf[F]>] extends [never] ? never - : Exactly[F], keyof CodecFieldOf[F]>> extends true - ? MethodDifferences<`${M}.${F}`, Accessor[F], CodecFieldOf[F]>> + : Exactly[F], keyof CodecFieldOf[F]>> extends true + ? MethodDifferences<`${M}.${F}`, Accessor[F], CodecFieldOf[F]>> : `${M}.${F} methods`; -}[ScalarFieldName]; +}[ScalarFieldName]; + +type FieldsWithoutCodecField> = { + [F in ScalarFieldName]: [CodecFieldOf[F]>] extends [never] + ? `${M}.${F}` + : never; +}[ScalarFieldName]; -type FieldsWithoutCodecField = { - [F in ScalarFieldName]: [CodecFieldOf[F]>] extends [never] ? `${M}.${F}` : never; -}[ScalarFieldName]; +type Differences = { + [M in ModelName]: FieldDifferences; +}[ModelName]; -type Differences = { [M in ModelName]: FieldDifferences }[ModelName]; +type Unmatched = { + [M in ModelName]: FieldsWithoutCodecField; +}[ModelName]; type ValueMethod = 'eq' | 'neq' | 'in' | 'notIn'; -type RefinedValueMethods = `User.address.${ValueMethod}`; -type Unmatched = { [M in ModelName]: FieldsWithoutCodecField }[ModelName]; describe('CodecField against every scalar field of the test contract', () => { test('the comparison checks fields, not nothing', () => { - expectTypeOf>().toEqualTypeOf< + expectTypeOf>().toEqualTypeOf< 'id' | 'title' | 'userId' | 'views' | 'embedding' >(); }); test('every scalar field of the test contract has a CodecField', () => { - expectTypeOf().toBeNever(); + expectTypeOf>().toBeNever(); + }); + + test('every method takes the same parameters', () => { + expectTypeOf>().toBeNever(); + }); +}); + +describe('CodecField against a value object field', () => { + test('every scalar field has a CodecField', () => { + expectTypeOf>().toBeNever(); }); - test('every method takes the same parameters, except the value methods of a value-object field, whose input type refines its codec input', () => { - expectTypeOf().toEqualTypeOf(); + test('every method takes the same parameters, except the value methods of the value object field, whose input type refines its codec input', () => { + expectTypeOf>().toEqualTypeOf<`User.address.${ValueMethod}`>(); }); }); diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.d.ts index 3a9d55796e0e..8c339719f244 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.d.ts @@ -28,7 +28,7 @@ import type { } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'8973aa4e3654dc5083f7ff15a7c187e3e590f977b4171bb816cd6566fa4db93b'>; + StorageHashBase<'87d224586379cdc42fda508d70ac88410eb9795e164b6031ef5a9dd9a5a1b759'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -276,22 +276,34 @@ type DefaultLiteralValue = CodecId extends keyo type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type FieldTypeOverrides = {}; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Note: { readonly kind: { readonly output: 'memo'; readonly input: 'memo' } }; + }; + }; +}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; -type ColumnTypeOverrides = {}; +type ColumnTypeOverrides = { + readonly public: { + readonly notes: { readonly kind: { readonly output: 'memo'; readonly input: 'memo' } }; + }; +}; export type StorageColumnTypes = ColumnTypesFromStorage< ContractBase['storage'], CodecTypes, @@ -483,6 +495,9 @@ type ContractBase = Omit< foreignKeys: readonly []; }; }; + readonly valueSet: { + readonly 'Note.kind': { readonly kind: 'valueSet'; readonly values: readonly ['memo'] }; + }; }; }; }; @@ -687,7 +702,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.json b/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.json index a13f0e70eabe..281f66221c23 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.json +++ b/packages/3-extensions/sql-orm-client/test/fixtures/collection-methods/generated/contract.json @@ -211,6 +211,13 @@ "entries": { "table": { "notes": { + "checks": [ + { + "expression": "\"kind\" IN ('memo')", + "name": "notes_kind_check_97646eca", + "prefix": "notes_kind_check" + } + ], "columns": { "body": { "codecId": "pg/text@1", @@ -225,7 +232,13 @@ "kind": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Note.kind", + "namespaceId": "public", + "plane": "storage" + } }, "pinned": { "codecId": "pg/bool@1", @@ -339,13 +352,21 @@ }, "uniques": [] } + }, + "valueSet": { + "Note.kind": { + "kind": "valueSet", + "values": [ + "memo" + ] + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "8973aa4e3654dc5083f7ff15a7c187e3e590f977b4171bb816cd6566fa4db93b" + "storageHash": "87d224586379cdc42fda508d70ac88410eb9795e164b6031ef5a9dd9a5a1b759" }, "capabilities": { "postgres": { diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/fragment-namespace/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/fragment-namespace/generated/contract.d.ts index 4a0bc8f4834c..069381911c50 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/fragment-namespace/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/fragment-namespace/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -493,7 +495,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts index 616e21b8f485..50fe2a67136c 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts @@ -38,7 +38,6 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, } from '@internal/contract/types'; export type StorageHash = @@ -290,36 +289,23 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], - CodecTypes, - ParameterizedOutputTypeFunctions, - FieldTypeOverrides, - 'output' ->; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], - CodecTypes, - ParameterizedOutputTypeFunctions, - FieldTypeOverrides, - 'input' ->; -export type AddressOutput = ValueObjectOutputTypes['public']['Address']; -export type AddressInput = ValueObjectInputTypes['public']['Address']; + type FieldTypeOverrides = {}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -1182,7 +1168,7 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/jsonb@1' }; }; readonly email: { readonly nullable: false; @@ -1348,24 +1334,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; }; }; }; @@ -1459,24 +1427,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json index fe24243054be..2dc5dd47490e 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json +++ b/packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json @@ -505,8 +505,8 @@ "address": { "nullable": true, "type": { - "kind": "valueObject", - "name": "Address" + "codecId": "pg/jsonb@1", + "kind": "scalar" } }, "email": { @@ -778,33 +778,6 @@ "table": "user_tags" } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.d.ts index cf321d22b5ac..39b31348475c 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -795,7 +797,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/on-update-only/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/on-update-only/generated/contract.d.ts index 37b0bd85e647..88b672847ec3 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/on-update-only/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/on-update-only/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -460,7 +462,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.d.ts index 741846a4cec7..3db1e40b9a97 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.d.ts @@ -23,7 +23,7 @@ import type { } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'2ccba7c309ee13e4714b958e47eb88ca06415dac60cd475a51940499c3331f8a'>; + StorageHashBase<'00913b83286a9e6492e6621c3ba277633d43a309cdda39f9cb46f0a54752c1eb'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -269,22 +269,53 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type FieldTypeOverrides = {}; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Task: { + readonly type: { + readonly output: 'bug' | 'epic' | 'feature'; + readonly input: 'bug' | 'epic' | 'feature'; + }; + }; + readonly User: { + readonly kind: { + readonly output: 'admin' | 'regular'; + readonly input: 'admin' | 'regular'; + }; + }; + }; + }; +}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; -type ColumnTypeOverrides = {}; +type ColumnTypeOverrides = { + readonly public: { + readonly tasks: { + readonly type: { + readonly output: 'bug' | 'epic' | 'feature'; + readonly input: 'bug' | 'epic' | 'feature'; + }; + }; + readonly users: { + readonly kind: { readonly output: 'admin' | 'regular'; readonly input: 'admin' | 'regular' }; + }; + }; +}; export type StorageColumnTypes = ColumnTypesFromStorage< ContractBase['storage'], CodecTypes, @@ -884,6 +915,16 @@ type ContractBase = Omit< ]; }; }; + readonly valueSet: { + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'epic', 'feature']; + }; + readonly 'User.kind': { + readonly kind: 'valueSet'; + readonly values: readonly ['admin', 'regular']; + }; + }; }; }; }; @@ -1363,7 +1404,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.json b/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.json index c62e7950f392..fa8deeced427 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.json +++ b/packages/3-extensions/sql-orm-client/test/fixtures/polymorphism/generated/contract.json @@ -957,6 +957,13 @@ "uniques": [] }, "tasks": { + "checks": [ + { + "expression": "\"type\" IN ('bug', 'epic', 'feature')", + "name": "tasks_type_check_b3d5b43f", + "prefix": "tasks_type_check" + } + ], "columns": { "bug_assignee_person_id": { "codecId": "pg/int4@1", @@ -995,7 +1002,13 @@ "type": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Task.type", + "namespaceId": "public", + "plane": "storage" + } } }, "foreignKeys": [ @@ -1124,6 +1137,13 @@ "uniques": [] }, "users": { + "checks": [ + { + "expression": "\"kind\" IN ('admin', 'regular')", + "name": "users_kind_check_dc69a8d4", + "prefix": "users_kind_check" + } + ], "columns": { "account_id": { "codecId": "pg/int4@1", @@ -1147,7 +1167,13 @@ "kind": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "User.kind", + "namespaceId": "public", + "plane": "storage" + } }, "name": { "codecId": "pg/text@1", @@ -1203,13 +1229,30 @@ }, "uniques": [] } + }, + "valueSet": { + "Task.type": { + "kind": "valueSet", + "values": [ + "bug", + "epic", + "feature" + ] + }, + "User.kind": { + "kind": "valueSet", + "values": [ + "admin", + "regular" + ] + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "2ccba7c309ee13e4714b958e47eb88ca06415dac60cd475a51940499c3331f8a" + "storageHash": "00913b83286a9e6492e6621c3ba277633d43a309cdda39f9cb46f0a54752c1eb" }, "capabilities": { "postgres": { diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/scope-namespace/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/scope-namespace/generated/contract.d.ts index bc89d2cf7208..dd6df2b631f4 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/scope-namespace/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/scope-namespace/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -493,7 +495,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.d.ts index 62f3fe8bdef7..ac6b16a295f1 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.d.ts +++ b/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.d.ts @@ -20,11 +20,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'dfd3186b551a7868c3339bf4a5f80ca2a3ad4aa43a610cc64189db7c62a1e475'>; + StorageHashBase<'388580bf7258d1c37e6113016201f45f9054b3a56527588f9d8f6646473ea9ef'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -269,15 +269,15 @@ type DefaultLiteralValue = CodecId extends keyo : CodecTypes[CodecId]['json'] : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -291,14 +291,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -571,6 +573,16 @@ type ContractBase = Omit< foreignKeys: readonly []; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -706,7 +718,13 @@ type ContractBase = Omit< readonly fields: { readonly addresses: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly many: { readonly elementNullable: false }; }; readonly id: { @@ -772,16 +790,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; }; }; }; @@ -814,16 +822,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.json b/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.json index d595f985f0e7..e28ee18ce55c 100644 --- a/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.json +++ b/packages/3-extensions/sql-orm-client/test/fixtures/soft-delete/generated/contract.json @@ -204,8 +204,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "public", + "plane": "storage" } }, "id": { @@ -313,19 +316,6 @@ "table": "users" } } - }, - "valueObjects": { - "Address": { - "fields": { - "street": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } @@ -481,6 +471,11 @@ }, "tags": { "checks": [ + { + "expression": "jsonb_path_match(\"addresses\", 'strict $.type() == \"array\" && !exists($[*] ? (!((@.type() == \"object\" && exists(@.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists(@.keyvalue() ? (@.key != \"street\"))))))') IS TRUE", + "name": "tags_addresses_shape_c752a33a", + "prefix": "tags_addresses_shape" + }, { "expression": "array_position(\"labels\", NULL) IS NULL", "name": "tags_labels_elem_not_null_0c04ab5c", @@ -559,13 +554,26 @@ }, "uniques": [] } + }, + "valueObject": { + "Address": { + "fields": { + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "dfd3186b551a7868c3339bf4a5f80ca2a3ad4aa43a610cc64189db7c62a1e475" + "storageHash": "388580bf7258d1c37e6113016201f45f9054b3a56527588f9d8f6646473ea9ef" }, "capabilities": { "postgres": { diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/contract.d.ts b/packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/contract.d.ts new file mode 100644 index 000000000000..00525f6dcdc3 --- /dev/null +++ b/packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/contract.d.ts @@ -0,0 +1,485 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as PgTypes, + ParameterizedOutputTypesForTypeParams as PgParameterizedOutputTypes, +} from '@internal/target-postgres/codec-types'; +import type { QueryOperationTypes as PgTargetQueryOps } from '@internal/target-postgres/operation-types'; + +import type { + ColumnTypesFromStorage, + ContractWithTypeMaps, + RelationKeys, + TypeMaps as TypeMapsType, +} from '@internal/sql-contract/types'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@internal/contract/types'; + +export type StorageHash = + StorageHashBase<'312f3bd7f0c8c146f44a26148cb251da42b2d8d449c3d6ad0bdf7ae7823df718'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; + +export type CodecTypes = PgTypes; +export type LaneCodecTypes = CodecTypes; +export type QueryOperationTypes = PgTargetQueryOps; +export type AggregateTypes = { + readonly avg: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + }; + }; + readonly avgDecimal: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + }; + }; + readonly count: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + }; + readonly countBigInt: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + }; + readonly max: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly min: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly sum: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + }; + }; + readonly sumBigInt: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + }; + }; +}; +type DefaultLiteralValue = CodecId extends keyof CodecTypes + ? Encoded extends CodecTypes[CodecId]['json'] + ? Encoded + : CodecTypes[CodecId]['json'] + : Encoded; +type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['public']['Address']; +export type AddressInput = ValueObjectInputTypes['public']['Address']; +type FieldTypeOverrides = {}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; +type ColumnTypeOverrides = {}; +export type StorageColumnTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'output' +>; +export type StorageColumnInputTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'input' +>; + +export namespace Models { + export type public_User = { + address: FieldOutputTypes['public']['User']['address']; + id: FieldOutputTypes['public']['User']['id']; + name: FieldOutputTypes['public']['User']['name']; + readonly [RelationKeys]?: never; + }; +} + +export declare const models: { + public: { + User: Models.public_User; + }; +}; + +export type TypeMaps = TypeMapsType< + CodecTypes, + QueryOperationTypes, + FieldOutputTypes, + FieldInputTypes, + StorageColumnTypes, + StorageColumnInputTypes, + AggregateTypes +>; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly public: { + readonly id: 'public'; + readonly kind: 'postgres-schema'; + readonly entries: { + readonly table: { + readonly users: { + columns: { + readonly address: { + readonly dataType: 'pg/jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly many: false; + }; + readonly name: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'postgres'; + readonly targetFamily: 'sql'; + readonly roots: { + readonly users: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly public: { + readonly models: { + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int4@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'users'; + readonly namespaceId: 'public'; + readonly fields: { + readonly address: { readonly column: 'address' }; + readonly id: { readonly column: 'id' }; + readonly name: { readonly column: 'name' }; + }; + }; + }; + }; + }; + }; + }; + readonly capabilities: { + readonly postgres: { + readonly distinctOn: true; + readonly forKeyShare: true; + readonly forNoKeyUpdate: true; + readonly jsonAgg: true; + readonly lateral: true; + readonly limit: true; + readonly orderBy: true; + readonly returning: true; + }; + readonly sql: { + readonly checkConstraint: true; + readonly defaultInInsert: true; + readonly enums: true; + readonly forShare: true; + readonly forUpdate: true; + readonly insertOnConflictSkip: true; + readonly insertOnConflictWithoutTarget: true; + readonly lateral: true; + readonly lockNowait: true; + readonly lockOf: true; + readonly lockSkipLocked: true; + readonly returning: true; + readonly scalarList: true; + }; + }; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = ContractWithTypeMaps; + +export type Namespaces = Contract['storage']['namespaces']; diff --git a/packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/contract.json b/packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/contract.json new file mode 100644 index 000000000000..ba2542ad8405 --- /dev/null +++ b/packages/3-extensions/sql-orm-client/test/fixtures/value-objects/generated/contract.json @@ -0,0 +1,172 @@ +{ + "schemaVersion": "1", + "targetFamily": "sql", + "target": "postgres", + "profileHash": "3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2", + "roots": { + "users": { + "model": "User", + "namespace": "public" + } + }, + "domain": { + "namespaces": { + "public": { + "models": { + "User": { + "fields": { + "address": { + "nullable": true, + "type": { + "entityKind": "valueObject", + "entityName": "Address", + "kind": "valueObject", + "namespaceId": "public", + "plane": "storage" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "pg/int4@1", + "kind": "scalar" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "fields": { + "address": { + "column": "address" + }, + "id": { + "column": "id" + }, + "name": { + "column": "name" + } + }, + "namespaceId": "public", + "table": "users" + } + } + } + } + } + }, + "storage": { + "namespaces": { + "public": { + "entries": { + "table": { + "users": { + "checks": [ + { + "expression": "\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE", + "name": "users_address_shape_0c7d1c3a", + "prefix": "users_address_shape" + } + ], + "columns": { + "address": { + "codecId": "pg/jsonb@1", + "dataType": "pg/jsonb", + "nullable": true + }, + "id": { + "codecId": "pg/int4@1", + "dataType": "pg/int4", + "nullable": false + }, + "name": { + "codecId": "pg/text@1", + "dataType": "pg/text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + } + }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } + } + }, + "id": "public", + "kind": "postgres-schema" + } + }, + "storageHash": "312f3bd7f0c8c146f44a26148cb251da42b2d8d449c3d6ad0bdf7ae7823df718" + }, + "capabilities": { + "postgres": { + "distinctOn": true, + "forKeyShare": true, + "forNoKeyUpdate": true, + "jsonAgg": true, + "lateral": true, + "limit": true, + "orderBy": true, + "returning": true + }, + "sql": { + "checkConstraint": true, + "defaultInInsert": true, + "enums": true, + "forShare": true, + "forUpdate": true, + "insertOnConflictSkip": true, + "insertOnConflictWithoutTarget": true, + "lateral": true, + "lockNowait": true, + "lockOf": true, + "lockSkipLocked": true, + "returning": true, + "scalarList": true + } + }, + "extensions": {}, + "meta": {}, + "_generated": { + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", + "message": "This file is automatically generated by \"prisma contract emit\".", + "regenerate": "To regenerate, run: prisma contract emit" + } +} \ No newline at end of file diff --git a/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts b/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts index 8efc98d6d7bc..dbb764477396 100644 --- a/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts @@ -515,6 +515,27 @@ type VOContractBase = Omit< id: '__unbound__'; kind: 'schema'; entries: { + valueObject: { + readonly Address: { + readonly fields: { + readonly street: { + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + readonly nullable: false; + }; + readonly city: { + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + readonly nullable: false; + }; + readonly zip: { + readonly many: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + readonly nullable: false; + }; + }; + }; + }; table: { users: { columns: { @@ -586,39 +607,30 @@ type VOContractBase = Omit< }; readonly homeAddress: { readonly many: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly nullable: true; }; readonly workAddress: { readonly many: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly nullable: false; }; }; readonly relations: Record; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - readonly nullable: false; - }; - readonly city: { - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - readonly nullable: false; - }; - readonly zip: { - readonly many: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - readonly nullable: false; - }; - }; - }; - }; }; }; }; diff --git a/packages/3-extensions/sql-orm-client/test/simplify-deep.test-d.ts b/packages/3-extensions/sql-orm-client/test/simplify-deep.test-d.ts index a77f7f09d9e8..382c0d891525 100644 --- a/packages/3-extensions/sql-orm-client/test/simplify-deep.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/simplify-deep.test-d.ts @@ -1,3 +1,4 @@ +import type { JsonValue } from '@internal/utils/json'; import { describe, expectTypeOf, test } from 'vitest'; import { Collection } from '../src/collection'; import { createMockRuntime, getTestContext } from './helpers'; @@ -14,11 +15,7 @@ describe('Collection result types are simplified', () => { name: string; email: string; invitedById: number | null; - address: { - readonly street: string; - readonly city: string; - readonly zip: string | null; - } | null; + address: JsonValue | null; }>(); }); @@ -41,11 +38,7 @@ describe('Collection result types are simplified', () => { name: string; email: string; invitedById: number | null; - address: { - readonly street: string; - readonly city: string; - readonly zip: string | null; - } | null; + address: JsonValue | null; posts: { id: number; title: string; @@ -82,11 +75,7 @@ describe('Collection result types are simplified', () => { name: string; email: string; invitedById: number | null; - address: { - readonly street: string; - readonly city: string; - readonly zip: string | null; - } | null; + address: JsonValue | null; }>(); }); @@ -99,11 +88,7 @@ describe('Collection result types are simplified', () => { name: string; email: string; invitedById: number | null; - address: { - readonly street: string; - readonly city: string; - readonly zip: string | null; - } | null; + address: JsonValue | null; posts: { id: number; title: string; @@ -116,11 +101,7 @@ describe('Collection result types are simplified', () => { name: string; email: string; invitedById: number | null; - address: { - readonly street: string; - readonly city: string; - readonly zip: string | null; - } | null; + address: JsonValue | null; } | null; }>(); }); diff --git a/packages/3-extensions/sql-orm-client/test/value-object-inputs.test-d.ts b/packages/3-extensions/sql-orm-client/test/value-object-inputs.test-d.ts index 3c46ff53fc3d..6ffaf7ae1747 100644 --- a/packages/3-extensions/sql-orm-client/test/value-object-inputs.test-d.ts +++ b/packages/3-extensions/sql-orm-client/test/value-object-inputs.test-d.ts @@ -1,6 +1,6 @@ import { expectTypeOf, test } from 'vitest'; import type { CreateInput, DefaultModelRow, MutationUpdateInput } from '../src/types'; -import type { Contract } from './fixtures/generated/contract'; +import type { Contract } from './fixtures/value-objects/generated/contract'; type AddressShape = { readonly street: string; readonly city: string; readonly zip: string | null }; diff --git a/packages/3-extensions/supabase/src/contract/contract.d.ts b/packages/3-extensions/supabase/src/contract/contract.d.ts index dbc9c7898e78..d16f5f50c32b 100644 --- a/packages/3-extensions/supabase/src/contract/contract.d.ts +++ b/packages/3-extensions/supabase/src/contract/contract.d.ts @@ -363,14 +363,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly auth: { @@ -7535,7 +7537,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/supabase/test/fixtures/example-app/contract.d.ts b/packages/3-extensions/supabase/test/fixtures/example-app/contract.d.ts index d187d5be4845..1b4b4b1a6fa6 100644 --- a/packages/3-extensions/supabase/test/fixtures/example-app/contract.d.ts +++ b/packages/3-extensions/supabase/test/fixtures/example-app/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -460,7 +462,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/supabase/test/fixtures/no-policy/contract.d.ts b/packages/3-extensions/supabase/test/fixtures/no-policy/contract.d.ts index 8f590426799e..8ef45b701b3f 100644 --- a/packages/3-extensions/supabase/test/fixtures/no-policy/contract.d.ts +++ b/packages/3-extensions/supabase/test/fixtures/no-policy/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -460,7 +462,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-extensions/supabase/test/fixtures/renamed-policy/contract.d.ts b/packages/3-extensions/supabase/test/fixtures/renamed-policy/contract.d.ts index 6e439643e24a..54fb29f177f6 100644 --- a/packages/3-extensions/supabase/test/fixtures/renamed-policy/contract.d.ts +++ b/packages/3-extensions/supabase/test/fixtures/renamed-policy/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -460,7 +462,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-targets/mongo/1-target/src/core/mongo-target-contract-serializer.ts b/packages/3-targets/mongo/1-target/src/core/mongo-target-contract-serializer.ts index 11202ce0e773..b5ef43fe0b39 100644 --- a/packages/3-targets/mongo/1-target/src/core/mongo-target-contract-serializer.ts +++ b/packages/3-targets/mongo/1-target/src/core/mongo-target-contract-serializer.ts @@ -4,10 +4,8 @@ import { type MongoCollectionInput, type MongoContract, MongoStorage, - type MongoValueSetInput, } from '@internal/mongo-contract'; import { blindCast } from '@internal/utils/casts'; -import { ifDefined } from '@internal/utils/defined'; import type { JsonObject } from '@internal/utils/json'; import type { MongoTargetContract } from './mongo-target-contract'; import { MongoTargetDatabase, MongoTargetUnboundDatabase } from './mongo-target-database'; @@ -17,37 +15,25 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase { - const collectionCount = Object.keys(nsData.entries.collection ?? {}).length; - const valueSetCount = Object.keys(nsData.entries['valueSet'] ?? {}).length; - if (nsId === UNBOUND_NAMESPACE_ID && collectionCount === 0 && valueSetCount === 0) { + const hasEntities = Object.values(nsData.entries).some( + (slot) => Object.keys(slot).length > 0, + ); + if (nsId === UNBOUND_NAMESPACE_ID && !hasEntities) { return [nsId, MongoTargetUnboundDatabase.instance]; } - const dbInput: { - id: string; - entries?: Readonly>>> & { - readonly collection?: Readonly>; - readonly valueSet?: Readonly>; - }; - } = { id: nsData.id }; - if ( - nsData.entries['collection'] !== undefined || - nsData.entries['valueSet'] !== undefined - ) { - dbInput.entries = { - collection: blindCast< - Readonly>, - 'collection entries validated by the mongo storage schema before hydration' - >(nsData.entries['collection'] ?? {}), - ...ifDefined( - 'valueSet', - blindCast< - Readonly> | undefined, - 'valueSet entries validated by the mongo storage schema before hydration' - >(nsData.entries['valueSet']), - ), - }; - } - return [nsId, new MongoTargetDatabase(dbInput)]; + return [ + nsId, + new MongoTargetDatabase({ + id: nsData.id, + entries: { + ...nsData.entries, + collection: blindCast< + Readonly>, + 'collection entries validated by the mongo storage schema before hydration' + >(nsData.entries['collection'] ?? {}), + }, + }), + ]; }), ); const targetStorage = new MongoStorage({ @@ -61,28 +47,19 @@ export class MongoTargetContractSerializer extends MongoContractSerializerBase = {}; for (const [nsId, ns] of Object.entries(storage.namespaces)) { - const collectionsOut: Record = {}; - for (const [collName, coll] of Object.entries(ns.entries.collection ?? {})) { - collectionsOut[collName] = blindCast< - JsonObject, - 'a collection IR node serializes to a JSON-safe collection object' - >(JSON.parse(JSON.stringify(coll))); - } - const valueSetOut: Record = {}; - for (const [vsName, vs] of Object.entries(ns.entries.valueSet ?? {})) { - valueSetOut[vsName] = blindCast< + const entries: Record = { + collection: blindCast< JsonObject, - 'a value-set IR node serializes to a JSON-safe { kind, values } object' - >(JSON.parse(JSON.stringify(vs))); - } - namespacesJson[nsId] = { - id: ns.id, - kind: 'mongo-database', - entries: { - collection: collectionsOut, - ...ifDefined('valueSet', Object.keys(valueSetOut).length > 0 ? valueSetOut : undefined), - }, + 'collection IR nodes serialize to JSON-safe collection objects' + >(JSON.parse(JSON.stringify(ns.entries.collection ?? {}))), }; + for (const [kind, slot] of Object.entries(ns.entries)) { + if (kind === 'collection' || Object.keys(slot).length === 0) continue; + entries[kind] = blindCast( + JSON.parse(JSON.stringify(slot)), + ); + } + namespacesJson[nsId] = { id: ns.id, kind: 'mongo-database', entries }; } return blindCast< JsonObject, diff --git a/packages/3-targets/mongo/1-target/test/mongo-target-contract-serializer.test.ts b/packages/3-targets/mongo/1-target/test/mongo-target-contract-serializer.test.ts index 95f2011b6c85..781f92515b0c 100644 --- a/packages/3-targets/mongo/1-target/test/mongo-target-contract-serializer.test.ts +++ b/packages/3-targets/mongo/1-target/test/mongo-target-contract-serializer.test.ts @@ -7,6 +7,7 @@ import { MongoIndex, MongoStorage, MongoValidator, + MongoValueObject, MongoValueSet, } from '@internal/mongo-contract'; import { applicationDomainOf } from '@repo/test-utils'; @@ -340,4 +341,34 @@ describe('MongoTargetContractSerializer', () => { expect(roleVs?.values).toEqual(['admin', 'author', 'reader']); }); }); + + describe('entries.valueObject round-trip', () => { + const address = { + fields: { + city: { type: { kind: 'scalar', codecId: 'mongo/string@1' }, nullable: false, many: false }, + }, + }; + + function makeValueObjectContractJson() { + const base = makeValidContractJson(); + const ns = base.storage.namespaces[UNBOUND_NAMESPACE_ID] as { + entries: Record; + }; + ns.entries['valueObject'] = { Address: address }; + return base; + } + + it('hydrates entries.valueObject and serializes it back unchanged', () => { + const serializer = new MongoTargetContractSerializer(); + const contract = serializer.deserializeContract(makeValueObjectContractJson()); + const out = JSON.parse(JSON.stringify(serializer.serializeContract(contract))); + expect({ + hydrated: + contract.storage.namespaces[UNBOUND_NAMESPACE_ID]?.entries.valueObject?.[ + 'Address' + ] instanceof MongoValueObject, + serialized: out.storage.namespaces[UNBOUND_NAMESPACE_ID].entries.valueObject, + }).toEqual({ hydrated: true, serialized: { Address: address } }); + }); + }); }); diff --git a/packages/3-targets/mongo/9-facade/test/contract-from-json.test.ts b/packages/3-targets/mongo/9-facade/test/contract-from-json.test.ts index 8fffdf15912e..bc7f12dfdd38 100644 --- a/packages/3-targets/mongo/9-facade/test/contract-from-json.test.ts +++ b/packages/3-targets/mongo/9-facade/test/contract-from-json.test.ts @@ -5,7 +5,14 @@ import type { Contract } from '../../../../2-mongo-family/1-foundation/mongo-con import fixtureContractJson from '../../../../2-mongo-family/1-foundation/mongo-contract/test/fixtures/orm-contract.json' with { type: 'json', }; -import { defineContract, enumType, field, member, model } from '../src/exports/contract-builder'; +import { + defineContract, + enumType, + field, + member, + model, + valueObject, +} from '../src/exports/contract-builder'; import mongo from '../src/exports/runtime'; import mongoStatic, { contractFromJson } from '../src/exports/static'; @@ -82,4 +89,42 @@ describe('validating a contract once', () => { expect(second.contract).toBe(first.contract); expect(fromFirst.contract).toBe(first.contract); }); + + it('takes a TypeScript-authored contract with a value object', () => { + const Address = valueObject('Address', { + fields: { street: field.string(), zip: field.string().optional() }, + }); + const authored = defineContract({ + valueObjects: { Address }, + models: { + Shop: model('Shop', { + collection: 'shops', + fields: { _id: field.objectId(), address: field.valueObject(Address) }, + }), + }, + }); + + const { contract } = mongo({ contract: authored }); + + expect( + JSON.parse( + JSON.stringify( + Object.values(contract.storage.namespaces).map( + (namespace) => namespace.entries['valueObject'], + ), + ), + ), + ).toContainEqual({ + Address: { + fields: { + street: { + nullable: false, + many: false, + type: { kind: 'scalar', codecId: 'mongo/string@1' }, + }, + zip: { nullable: true, many: false, type: { kind: 'scalar', codecId: 'mongo/string@1' } }, + }, + }, + }); + }); }); diff --git a/packages/3-targets/mongo/9-facade/test/mongo.enum.e2e.test.ts b/packages/3-targets/mongo/9-facade/test/mongo.enum.e2e.test.ts index 7e2499fb3659..f422c1b028b3 100644 --- a/packages/3-targets/mongo/9-facade/test/mongo.enum.e2e.test.ts +++ b/packages/3-targets/mongo/9-facade/test/mongo.enum.e2e.test.ts @@ -80,7 +80,7 @@ const storageValueSets = blindCast< const ACCOUNT_VALIDATOR = deriveCollectionValidators({ models: ns?.models ?? {}, lookups: { codecLookup, dataTypeLookup: createDataTypeLookup(mongoDataTypes) }, - valueObjects: {}, + storage: { namespaces: {} }, valueSets: storageValueSets, controlPolicies: { collections: {}, defaultControlPolicy: undefined }, })['accounts']; diff --git a/packages/3-targets/mysql/9-facade/test/fixtures/generated/contract.d.ts b/packages/3-targets/mysql/9-facade/test/fixtures/generated/contract.d.ts index 6dc7cedc660f..43faf6d19958 100644 --- a/packages/3-targets/mysql/9-facade/test/fixtures/generated/contract.d.ts +++ b/packages/3-targets/mysql/9-facade/test/fixtures/generated/contract.d.ts @@ -49,14 +49,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly __unbound__: { @@ -341,7 +343,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/3-targets/postgres/1-target/src/core/check-expressions.ts b/packages/3-targets/postgres/1-target/src/core/check-expressions.ts index 63c69f3e0fc8..01442ff52775 100644 --- a/packages/3-targets/postgres/1-target/src/core/check-expressions.ts +++ b/packages/3-targets/postgres/1-target/src/core/check-expressions.ts @@ -1,42 +1,14 @@ -import type { CheckKind } from '@internal/sql-schema-ir/naming'; +import type { + CheckExpressionCandidate, + CheckExpressionInput, + ResolvedColumnValueObject, + ResolvedValueObject, + ResolvedValueObjectField, + ResolvedValueObjectFieldType, +} from '@internal/sql-contract/check-expressions'; import { invariant } from '@internal/utils/assertions'; import { escapeLiteral, quoteIdentifier } from './sql-utils'; -/** - * What a rendered check enforces. The contract builder turns this into the - * wire-name prefix's trailing segment, so the kind stays readable in the - * physical constraint name. The vocabulary is the family's, so this names - * `CheckKind` rather than restating its members. - */ -export type PostgresCheckKind = CheckKind; - -/** - * One column's shape, as the contract builder knows it. `memberValues` is - * present only for a domain enum authored through an `enumType()` handle — - * a column bound to a native enum type carries its enforcement in the type - * itself and arrives here with `memberValues` undefined. - */ -export interface PostgresCheckExpressionInput { - readonly tableName: string; - readonly columnName: string; - readonly many: boolean | { readonly elementNullable: boolean }; - readonly memberValues: readonly (string | number)[] | undefined; -} - -/** - * A check the target wants written. The target contributes only what it - * uniquely knows — which predicate, spelled how — while the family owns the - * naming: it composes the prefix from the table, the column, and the kind, - * caps it, and appends the content hash. Nothing here is a name. - */ -export interface PostgresCheckExpressionCandidate { - readonly kind: PostgresCheckKind; - /** The column this predicate constrains; the family names the check after it. */ - readonly columnName: string; - /** Opaque SQL: the predicate body, without the surrounding `CHECK (…)`. */ - readonly expression: string; -} - /** * Renders the checks a Postgres column needs, as opaque predicate text. * @@ -52,14 +24,12 @@ export interface PostgresCheckExpressionCandidate { * output instead, which for `inet` always carries the prefix length. */ export function postgresRenderCheckExpressions( - input: PostgresCheckExpressionInput, -): readonly PostgresCheckExpressionCandidate[] { - const candidates: PostgresCheckExpressionCandidate[] = []; + input: CheckExpressionInput, +): readonly CheckExpressionCandidate[] { + const candidates: CheckExpressionCandidate[] = []; const column = quoteIdentifier(input.columnName); const isMany = input.many !== false; - const elementNullable = - (typeof input.many === 'object' && input.many.elementNullable) || - ('elementNullable' in input && input.elementNullable === true); + const elementNullable = input.many !== false && input.many.elementNullable; if (input.memberValues !== undefined) { invariant( @@ -89,9 +59,109 @@ export function postgresRenderCheckExpressions( }); } + if (input.valueObject !== undefined) { + candidates.push({ + kind: 'shape', + columnName: input.columnName, + expression: valueObjectCheck(column, input.valueObject), + }); + } + return candidates; } +/** + * One strict `jsonpath` predicate over the whole column value. Strict mode keeps a comparison from unwrapping an array, and `IS TRUE` makes a predicate that evaluates to unknown fail the check, as a CHECK would otherwise pass on NULL. Member values are read through `keyvalue()`, so an absent member is never an error. + */ +function valueObjectCheck(column: string, columnValueObject: ResolvedColumnValueObject): string { + const field: ResolvedValueObjectField = { + nullable: columnValueObject.nullable, + many: columnValueObject.many, + type: { kind: 'valueObject', valueObject: columnValueObject.valueObject }, + }; + const predicate = fieldPredicate(field, '$'); + invariant(predicate !== undefined, 'a column that holds a value object always has a predicate'); + const match = (path: string) => + `jsonb_path_match(${column}, '${escapeLiteral(`strict ${path}`)}') IS TRUE`; + return field.nullable + ? `${column} IS NULL OR ${match(`$.type() == "null" || (${predicate})`)}` + : match(predicate); +} + +/** What the value at `path` must satisfy, or `undefined` when any value is accepted. */ +function fieldPredicate(field: ResolvedValueObjectField, path: string): string | undefined { + if (field.many === false) return valuePredicate(field.type, path); + const element = valuePredicate(field.type, '@'); + const { elementNullable } = field.many; + const invalidElement = + element === undefined + ? elementNullable + ? undefined + : '@.type() == "null"' + : elementNullable + ? `!(@.type() == "null" || (${element}))` + : `!((${element}))`; + const isArray = `${path}.type() == "array"`; + return invalidElement === undefined + ? isArray + : `${isArray} && !exists(${path}[*] ? (${invalidElement}))`; +} + +/** A member of any JSON type accepts every value, JSON `null` included; a member of any other JSON type is of that type, so never JSON `null`. */ +function valuePredicate(type: ResolvedValueObjectFieldType, path: string): string | undefined { + switch (type.kind) { + case 'valueObject': + return objectPredicate(type.valueObject, path); + case 'anyObject': + return `${path}.type() == "object"`; + case 'scalar': + if (type.values !== undefined) return valueSetPredicate(type.values, path); + return type.jsonType === 'any' ? undefined : `${path}.type() == "${type.jsonType}"`; + } +} + +function valueSetPredicate(values: readonly (string | number)[], path: string): string { + const alternatives = (jsonType: 'string' | 'number', members: readonly (string | number)[]) => + `${path}.type() == "${jsonType}" && (${members.map((value) => `${path} == ${jsonPathLiteral(value)}`).join(' || ')})`; + const strings = values.filter((value) => typeof value === 'string'); + const numbers = values.filter((value) => typeof value === 'number'); + invariant(values.length > 0, 'a value set restricting a value object member has no values'); + if (numbers.length === 0) return alternatives('string', strings); + if (strings.length === 0) return alternatives('number', numbers); + return `(${alternatives('string', strings)}) || (${alternatives('number', numbers)})`; +} + +function objectPredicate(valueObject: ResolvedValueObject, path: string): string { + const names = Object.keys(valueObject.fields).sort(); + const members = names.flatMap((name) => { + const field = valueObject.fields[name]; + invariant(field !== undefined, `value object member "${name}" is missing`); + const isMember = `@.key == ${jsonPathLiteral(name)}`; + const predicate = fieldPredicate(field, '@.value'); + if (!field.nullable) { + return [ + predicate === undefined + ? `exists(${path}.keyvalue() ? (${isMember}))` + : `exists(${path}.keyvalue() ? (${isMember} && (${predicate})))`, + ]; + } + return predicate === undefined + ? [] + : [ + `!exists(${path}.keyvalue() ? (${isMember} && !(@.value.type() == "null" || (${predicate}))))`, + ]; + }); + const otherKey = + names.length === 0 + ? `!exists(${path}.keyvalue())` + : `!exists(${path}.keyvalue() ? (${names.map((name) => `@.key != ${jsonPathLiteral(name)}`).join(' && ')}))`; + return [`${path}.type() == "object"`, ...members, otherKey].join(' && '); +} + +function jsonPathLiteral(value: string | number): string { + return typeof value === 'string' ? JSON.stringify(value) : String(value); +} + function sqlLiteral(value: string | number): string { return typeof value === 'string' ? `'${escapeLiteral(value)}'` : String(value); } diff --git a/packages/3-targets/postgres/1-target/src/core/codec-descriptor.ts b/packages/3-targets/postgres/1-target/src/core/codec-descriptor.ts index 8ea15cf81e3b..0c08edc0e32e 100644 --- a/packages/3-targets/postgres/1-target/src/core/codec-descriptor.ts +++ b/packages/3-targets/postgres/1-target/src/core/codec-descriptor.ts @@ -13,6 +13,7 @@ import { type DataTypeId, validateCodecTypeParams, } from '@internal/framework-components/codec'; +import { isJsonType, type JsonType } from '@internal/sql-contract/codec-json-type'; import { CaseExpr, ColumnRef, @@ -41,6 +42,8 @@ const ARRAY_ORDINALITY_COLUMN = 'ordinality'; export interface AnyPostgresCodecDescriptor extends AnyCodecDescriptor { readonly descriptorKind: typeof POSTGRES_CODEC_DESCRIPTOR_KIND; + /** The JSON type the codec's value is written as inside a JSON document. `PostgresCodecDescriptor` and `postgresCodec` require it; a registry may still hold a descriptor built without it. */ + readonly jsonType?: JsonType; projectJson(expression: ProjectionExpr, ref: CodecRef): ProjectionExpr; } @@ -49,6 +52,7 @@ export abstract class PostgresCodecDescriptor

implements AnyPostgresCodecDescriptor { readonly descriptorKind = POSTGRES_CODEC_DESCRIPTOR_KIND; + abstract readonly jsonType: JsonType; protected abstract jsonProjection(expression: ProjectionExpr, params: P): ProjectionExpr; @@ -140,12 +144,15 @@ export type AdaptedPostgresCodecDescriptor keyof CodecDescriptorTemplate> > & Pick & - Pick; + Pick & { + readonly jsonType: JsonType; + }; class PostgresCodecDescriptorAdapter< - D extends AnyCodecDescriptorTemplate, + D extends AnyCodecDescriptorTemplate & { readonly jsonType: JsonType }, > extends PostgresCodecDescriptor> { override readonly dataType: DataTypeId; + override readonly jsonType: JsonType; override readonly codecId: string; override readonly traits: readonly CodecTrait[]; override readonly paramsSchema: StandardSchemaV1> | undefined; @@ -166,6 +173,7 @@ class PostgresCodecDescriptorAdapter< ) { super(); this.dataType = options.dataType.id; + this.jsonType = descriptor.jsonType; this.codecId = descriptor.codecId; this.traits = descriptor.traits; this.paramsSchema = blindCast< @@ -215,7 +223,9 @@ class PostgresCodecDescriptorAdapter< } } -export function postgresCodec( +export function postgresCodec< + D extends AnyCodecDescriptorTemplate & { readonly jsonType: JsonType }, +>( descriptor: D, options: PostgresCodecOptions, DescriptorCodec>, ): AdaptedPostgresCodecDescriptor { @@ -253,7 +263,8 @@ export function isPostgresCodecDescriptor(value: unknown): value is AnyPostgresC 'factory' in value && typeof value.factory === 'function' && 'projectJson' in value && - typeof value.projectJson === 'function' + typeof value.projectJson === 'function' && + (!('jsonType' in value) || isJsonType(value.jsonType)) ); } diff --git a/packages/3-targets/postgres/1-target/src/core/codecs.ts b/packages/3-targets/postgres/1-target/src/core/codecs.ts index 19384ba30946..a0679dabc659 100644 --- a/packages/3-targets/postgres/1-target/src/core/codecs.ts +++ b/packages/3-targets/postgres/1-target/src/core/codecs.ts @@ -302,6 +302,7 @@ export class PgTextCodec extends CodecImpl< } export class PgTextDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -381,6 +382,7 @@ export function isPgEnumParams(value: unknown): value is PgEnumParams { } export class PgEnumDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -506,6 +508,7 @@ export class PgTextArrayCodec extends CodecImpl< } export class PgTextArrayDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'array' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return printedTextJsonProjection(expression); } @@ -541,6 +544,7 @@ export class PgInt4Codec extends CodecImpl< } export class PgInt4Descriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -585,6 +589,7 @@ export class PgInt2Codec extends CodecImpl< } export class PgInt2Descriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -635,6 +640,7 @@ export class PgInt8Codec extends CodecImpl< } export class PgInt8Descriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return decimalTextJsonProjection(expression); } @@ -687,6 +693,7 @@ export class PgInt8NumberCodec extends CodecImpl< } export class PgInt8NumberDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return decimalTextJsonProjection(expression); } @@ -731,6 +738,7 @@ export class PgFloat4Codec extends CodecImpl< } export class PgFloat4Descriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -775,6 +783,7 @@ export class PgFloat8Codec extends CodecImpl< } export class PgFloat8Descriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -819,6 +828,7 @@ export class PgBoolCodec extends CodecImpl< } export class PgBoolDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'boolean' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -863,6 +873,7 @@ export class PgNumericCodec extends CodecImpl< } export class PgNumericDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return decimalTextJsonProjection(expression); } @@ -912,6 +923,7 @@ export class PgUnboundedIntCodec extends CodecImpl< } export class PgUnboundedIntDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return decimalTextJsonProjection(expression); } @@ -962,6 +974,7 @@ export class PgTimetzCodec extends CodecImpl< } export class PgTimetzDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1006,6 +1019,7 @@ export class PgBitCodec extends CodecImpl< } export class PgBitDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1050,6 +1064,7 @@ export class PgVarbitCodec extends CodecImpl< } export class PgVarbitDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1094,6 +1109,7 @@ export class PgByteaCodec extends CodecImpl< } export class PgByteaDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'object' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return printedTextJsonProjection(expression); } @@ -1137,6 +1153,7 @@ export class PgUuidCodec extends CodecImpl< } export class PgUuidDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1181,6 +1198,7 @@ export class PgInetCodec extends CodecImpl< } export class PgInetDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1241,6 +1259,7 @@ export class PgTsqueryCodec extends CodecImpl< * column, and no traits, because comparing or ordering queries means nothing to an application. */ export class PgTsqueryDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1300,6 +1319,7 @@ export class PgIntervalCodec extends CodecImpl< } export class PgIntervalDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'object' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return printedTextJsonProjection(expression); } @@ -1342,6 +1362,7 @@ export class PgJsonCodec extends CodecImpl< } export class PgJsonDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return printedTextJsonProjection(expression); } @@ -1385,6 +1406,7 @@ export class PgJsonbCodec extends CodecImpl< } export class PgJsonbDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return printedTextJsonProjection(expression); } @@ -1426,6 +1448,7 @@ export class PgFloatCodec extends SqlFloatCodec { } export class PgCharDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return unpaddedCharJsonProjection(expression); } @@ -1449,6 +1472,7 @@ export const pgCharColumn = (params: LengthParams = {}) => pgCharColumn satisfies ColumnHelperFor; export class PgVarcharDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1472,6 +1496,7 @@ export const pgVarcharColumn = (params: LengthParams = {}) => pgVarcharColumn satisfies ColumnHelperFor; export class PgIntDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -1495,6 +1520,7 @@ export const pgIntColumn = () => pgIntColumn satisfies ColumnHelperFor; export class PgFloatDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'number' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } diff --git a/packages/3-targets/postgres/1-target/src/core/date-codecs.ts b/packages/3-targets/postgres/1-target/src/core/date-codecs.ts index 8690b596b2dc..3cdeb7557f39 100644 --- a/packages/3-targets/postgres/1-target/src/core/date-codecs.ts +++ b/packages/3-targets/postgres/1-target/src/core/date-codecs.ts @@ -134,6 +134,7 @@ export class PgTimestamptzDateCodec extends CodecImpl< } export class PgTimestamptzDateDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -178,6 +179,7 @@ export class PgTimestampDateCodec extends CodecImpl< } export class PgTimestampDateDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } diff --git a/packages/3-targets/postgres/1-target/src/core/interval-string-codec.ts b/packages/3-targets/postgres/1-target/src/core/interval-string-codec.ts index 56bd9c26df92..5034fc759f9a 100644 --- a/packages/3-targets/postgres/1-target/src/core/interval-string-codec.ts +++ b/packages/3-targets/postgres/1-target/src/core/interval-string-codec.ts @@ -70,6 +70,7 @@ export class PgIntervalStringCodec extends CodecImpl< } export class PgIntervalStringDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } diff --git a/packages/3-targets/postgres/1-target/src/core/migrations/operations/constraints.ts b/packages/3-targets/postgres/1-target/src/core/migrations/operations/constraints.ts index b29753329bb5..20a34d1bd135 100644 --- a/packages/3-targets/postgres/1-target/src/core/migrations/operations/constraints.ts +++ b/packages/3-targets/postgres/1-target/src/core/migrations/operations/constraints.ts @@ -343,6 +343,10 @@ export async function renameConstraint( }; } +export function dropCheckConstraintId(tableName: string, constraintName: string): string { + return `dropCheckConstraint.${tableName}.${constraintName}`; +} + export async function dropCheckConstraint( schemaName: string, tableName: string, @@ -356,7 +360,7 @@ export async function dropCheckConstraint( table: tableName, }); return { - id: `dropCheckConstraint.${tableName}.${constraintName}`, + id: dropCheckConstraintId(tableName, constraintName), label: `Drop check constraint "${constraintName}" on "${tableName}"`, operationClass: 'widening', target: targetDetails('checkConstraint', constraintName, schemaName, tableName), diff --git a/packages/3-targets/postgres/1-target/src/core/migrations/rows-failing-check.ts b/packages/3-targets/postgres/1-target/src/core/migrations/rows-failing-check.ts new file mode 100644 index 000000000000..9f38bceef55c --- /dev/null +++ b/packages/3-targets/postgres/1-target/src/core/migrations/rows-failing-check.ts @@ -0,0 +1,87 @@ +import type { SqlMigrationPlanOperation } from '@internal/family-sql/control'; +import type { RowsFailingCheck } from '@internal/framework-components/control'; +import type { SqlStorage } from '@internal/sql-contract/types'; +import { CHECK_KINDS, composeCheckWirePrefix, parseWireName } from '@internal/sql-schema-ir/naming'; +import { ifDefined } from '@internal/utils/defined'; +import { dropCheckConstraintId } from './operations/constraints'; +import { qualifyTableName } from './planner-sql-checks'; +import type { PostgresPlanTargetDetails } from './planner-target-details'; + +/** SQLSTATE `check_violation`: a row fails a check. */ +export const CHECK_VIOLATION = '23514'; + +type Operation = SqlMigrationPlanOperation; + +/** + * The existing rows that the check `operation` adds refuses, when `constraint`, the check the + * database names in its `check_violation`, is that check and the contract declares it. + */ +export function rowsFailingCheckOf(input: { + readonly operation: Operation; + readonly constraint: string | undefined; + readonly storage: SqlStorage; + readonly operations: readonly Operation[]; + readonly operationIndex: number; +}): + | { readonly summary: string; readonly why: string; readonly fact: RowsFailingCheck } + | undefined { + const details = input.operation.target.details; + const table = details?.table; + if (details?.objectType !== 'checkConstraint' || table === undefined) return undefined; + if (input.constraint !== details.name) return undefined; + const declared = declaredCheck(input.storage, table, details.name); + if (declared === undefined) return undefined; + const { prefix, expression } = declared.check; + const column = + prefix === undefined + ? undefined + : declared.columns.find((name) => + CHECK_KINDS.some((kind) => composeCheckWirePrefix(table, name, kind) === prefix), + ); + const replacedCheck = + prefix === undefined + ? undefined + : input.operations + .slice(0, input.operationIndex) + .reverse() + .map(({ id, target }) => + target.details === undefined ? undefined : { id, ...target.details }, + ) + .find( + (dropped) => + dropped !== undefined && + dropped.id === dropCheckConstraintId(table, dropped.name) && + parseWireName(dropped.name)?.prefix === prefix, + )?.name; + const query = `SELECT * FROM ${qualifyTableName(details.schema, table)} WHERE NOT (${expression})`; + const onColumn = column === undefined ? '' : ` on column "${column}"`; + return { + summary: `Rows of table "${table}" fail check "${details.name}"${onColumn}, so the check cannot be added.`, + why: `Find them with \`${query}\`.`, + fact: { + check: details.name, + ...ifDefined('replacedCheck', replacedCheck), + operationIndex: input.operationIndex, + }, + }; +} + +function declaredCheck( + storage: SqlStorage, + table: string, + name: string, +): + | { + readonly check: { readonly prefix?: string; readonly expression: string }; + readonly columns: readonly string[]; + } + | undefined { + for (const namespace of Object.values(storage.namespaces)) { + const declared = namespace.entries.table?.[table]; + const check = declared?.checks?.find((candidate) => candidate.name === name); + if (declared !== undefined && check !== undefined) { + return { check, columns: Object.keys(declared.columns) }; + } + } + return undefined; +} diff --git a/packages/3-targets/postgres/1-target/src/core/migrations/runner.ts b/packages/3-targets/postgres/1-target/src/core/migrations/runner.ts index 4b30fc3315c6..66001a0b1b7b 100644 --- a/packages/3-targets/postgres/1-target/src/core/migrations/runner.ts +++ b/packages/3-targets/postgres/1-target/src/core/migrations/runner.ts @@ -55,6 +55,7 @@ import { import { MARKER_LOCK_KEY, MARKER_LOCK_SQL } from './marker-lock'; import { qualifyTableName } from './planner-sql-checks'; import type { PostgresPlanTargetDetails } from './planner-target-details'; +import { CHECK_VIOLATION, rowsFailingCheckOf } from './rows-failing-check'; import { SCHEMA_OBJECTS_SQL, schemaBlockingObjects, @@ -540,7 +541,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner['driver'], operation: SqlMigrationPlanOperation, - plan: { readonly contract: Contract; readonly operationIndex: number }, + plan: PlanPosition, ): Promise> { for (const step of operation.execute) { try { @@ -564,6 +565,29 @@ class PostgresMigrationRunner implements SqlMigrationRunner { for (const candidate of postgresRenderCheckExpressions({ tableName: table.name, columnName: column.name, - many: column.many === true, + many: column.many === true ? { elementNullable: false } : false, memberValues: undefined, + valueObject: undefined, })) { const derivedName = formatWireName( composeCheckWirePrefix(table.name, column.name, candidate.kind), @@ -329,8 +330,9 @@ function enforcesElementNotNull( return postgresRenderCheckExpressions({ tableName: table.name, columnName: column.name, - many: true, + many: { elementNullable: false }, memberValues: undefined, + valueObject: undefined, }).some( (candidate) => candidate.kind === 'elementNotNull' && diff --git a/packages/3-targets/postgres/1-target/src/core/psl-print/contract-model-index.ts b/packages/3-targets/postgres/1-target/src/core/psl-print/contract-model-index.ts index 04c483dec81e..cf8a811ecd11 100644 --- a/packages/3-targets/postgres/1-target/src/core/psl-print/contract-model-index.ts +++ b/packages/3-targets/postgres/1-target/src/core/psl-print/contract-model-index.ts @@ -2,6 +2,7 @@ import type { Contract, ContractModelBase, CrossReference } from '@internal/cont import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { UNBOUND_PSL_NAMESPACE_NAME } from '@internal/framework-components/psl-ast'; import { canonicalizeJson } from '@internal/framework-components/utils'; +import { discriminatorValueSet } from '@internal/sql-contract/check-expressions'; import type { ForeignKey, SqlModelStorage, @@ -142,3 +143,19 @@ export function isVariantLinkForeignKey(fk: ForeignKey, variant: VariantInfo | u fk.onUpdate === undefined ); } + +/** + * The value set the PSL source writes for a discriminator, keyed `.` and holding the variants' values, sorted. A discriminator an enum types keeps the enum's value set, so it writes none. + */ +export function discriminatorValueSetOf( + entry: ModelWithTable, +): { readonly name: string; readonly values: readonly string[] } | undefined { + const { discriminator, variants } = entry.model; + if (discriminator === undefined || variants === undefined) return undefined; + const derived = discriminatorValueSet(entry.name, discriminator.field, Object.keys(variants)); + const columnName = entry.storage.fields[discriminator.field]?.column; + const valueSet = columnName === undefined ? undefined : entry.table.columns[columnName]?.valueSet; + return valueSet?.entityName === derived.name && valueSet.namespaceId === entry.namespaceId + ? derived + : undefined; +} diff --git a/packages/3-targets/postgres/1-target/src/core/psl-print/domain-types.ts b/packages/3-targets/postgres/1-target/src/core/psl-print/domain-types.ts index 8615b0e6b51a..202c8f7a384c 100644 --- a/packages/3-targets/postgres/1-target/src/core/psl-print/domain-types.ts +++ b/packages/3-targets/postgres/1-target/src/core/psl-print/domain-types.ts @@ -68,7 +68,7 @@ function buildMemberType(input: { refuseUnwritableFieldShape(field, coordinate); const { type } = field; if (type.kind === 'valueObject') { - return { kind: 'valueObject', typeName: type.name }; + return { kind: 'valueObject', typeName: type.entityName }; } refuseUnderivedMemberValueSet({ field, @@ -95,7 +95,7 @@ function buildMemberType(input: { }); } -/** The `type` blocks of one namespace, one per value object the domain declares there. */ +/** The `type` blocks of one namespace, one per value object its storage holds. */ export function buildCompositeTypes(input: { readonly contract: Contract; readonly namespaceId: string; @@ -105,7 +105,8 @@ export function buildCompositeTypes(input: { readonly domainEnums: Readonly>; }): readonly PslCompositeType[] { refuseValueObjectsOutsideDefaultNamespace(input.contract, input.namespaceId); - const valueObjects = input.contract.domain.namespaces[input.namespaceId]?.valueObjects ?? {}; + const valueObjects = + input.contract.storage.namespaces[input.namespaceId]?.entries.valueObject ?? {}; return Object.entries(valueObjects).map(([name, valueObject]) => { refuseUnwritableName('value object', name); return { diff --git a/packages/3-targets/postgres/1-target/src/core/psl-print/model-attributes.ts b/packages/3-targets/postgres/1-target/src/core/psl-print/model-attributes.ts index b608fda7dfbd..bac3e014b31e 100644 --- a/packages/3-targets/postgres/1-target/src/core/psl-print/model-attributes.ts +++ b/packages/3-targets/postgres/1-target/src/core/psl-print/model-attributes.ts @@ -1,4 +1,5 @@ import type { PslModelAttribute } from '@internal/framework-components/psl-ast'; +import type { ResolvedColumnValueObject } from '@internal/sql-contract/check-expressions'; import { escapePslString } from '@internal/sql-contract/data-type-support'; import type { StorageTable } from '@internal/sql-contract/types'; import { pslModelMapName } from '@internal/sql-contract-psl/map-names'; @@ -36,30 +37,35 @@ export interface DerivedCheck { * The checks the PSL source derives itself for a table, keyed by name, so * they are not also written as `@@check`. A table that is not managed derives * none. A managed one derives a membership check for each column typed by a - * domain enum and an element-not-null check for each list column whose - * elements are not nullable. + * domain enum and for a discriminator column, an element-not-null check for + * each list column whose elements are not nullable, and a value object check + * for each column that holds a value object. */ export function derivedChecks(input: { readonly table: StorageTable; readonly tableName: string; readonly managed: boolean; - readonly domainEnumValues: ReadonlyMap; + /** The values of each value set a domain enum or a discriminator writes, by name. */ + readonly memberValueSets: ReadonlyMap; + /** The value object field each column that holds one stores, by column name. */ + readonly columnValueObjects: ReadonlyMap; }): ReadonlyMap { const checks = new Map(); if (!input.managed) return checks; for (const [columnName, column] of Object.entries(input.table.columns)) { - const enumValues = + const setValues = column.codecId === PG_ENUM_CODEC_ID || column.valueSet === undefined ? undefined - : input.domainEnumValues.get(column.valueSet.entityName); - const memberValues = enumValues?.filter( + : input.memberValueSets.get(column.valueSet.entityName); + const memberValues = setValues?.filter( (value): value is string | number => typeof value === 'string' || typeof value === 'number', ); for (const candidate of postgresRenderCheckExpressions({ tableName: input.tableName, columnName, many: column.many, - memberValues: memberValues?.length === enumValues?.length ? memberValues : undefined, + memberValues: memberValues?.length === setValues?.length ? memberValues : undefined, + valueObject: input.columnValueObjects.get(columnName), })) { const prefix = composeCheckWirePrefix(input.tableName, columnName, candidate.kind); checks.set(formatWireName(prefix, computeCheckContentHash(candidate.expression)), { diff --git a/packages/3-targets/postgres/1-target/src/core/psl-print/psl-contract.ts b/packages/3-targets/postgres/1-target/src/core/psl-print/psl-contract.ts index 705e5dbf788b..66ca8b326d4b 100644 --- a/packages/3-targets/postgres/1-target/src/core/psl-print/psl-contract.ts +++ b/packages/3-targets/postgres/1-target/src/core/psl-print/psl-contract.ts @@ -18,6 +18,10 @@ import { makePslNamespaceEntries, UNSPECIFIED_PSL_NAMESPACE_ID, } from '@internal/framework-components/psl-ast'; +import { + type ResolvedColumnValueObject, + resolveColumnValueObject, +} from '@internal/sql-contract/check-expressions'; import type { ForeignKey, SqlModelStorage, SqlStorage } from '@internal/sql-contract/types'; import { invariant } from '@internal/utils/assertions'; import { blindCast } from '@internal/utils/casts'; @@ -25,6 +29,7 @@ import { DEFAULT_NAMESPACE_ID } from '../namespace-ids'; import { PostgresNativeEnum } from '../postgres-native-enum'; import { SYNTHETIC_SPAN } from '../psl-build/psl-literals'; import { + discriminatorValueSetOf, indexContractModels, type ModelWithTable, modelCoordinate, @@ -102,13 +107,49 @@ function nativeEnumsOf( return nativeEnums; } -/** The value sets the PSL source derives for a namespace from the enum blocks written for it. */ +/** The value object each column of a table holds, over the fields of every model the table stores. Nullability comes from the column, as in the build: a variant that shares its base table has a nullable column. */ +function columnValueObjectsOf( + contract: Contract, + entries: readonly ModelWithTable[], + codecLookup: SqlPslBuildContext['codecLookup'], +): ReadonlyMap { + const columnValueObjects = new Map(); + for (const entry of entries) { + for (const [fieldName, field] of Object.entries(entry.model.fields)) { + const columnName = entry.storage.fields[fieldName]?.column; + const column = columnName === undefined ? undefined : entry.table.columns[columnName]; + if (field.type.kind !== 'valueObject' || columnName === undefined || column === undefined) { + continue; + } + columnValueObjects.set( + columnName, + resolveColumnValueObject( + contract.storage, + { + reference: field.type, + nullable: column.nullable, + many: field.many ?? false, + }, + codecLookup, + ), + ); + } + } + return columnValueObjects; +} + +/** The value sets the PSL source derives for a namespace from the enum blocks written for it and from its discriminators. */ function derivedValueSetsOf( namespaceId: string, enums: NativeEnumEmission, defaultDomainEnums: Readonly>, + namespaceModels: readonly ModelWithTable[], ): ReadonlyMap { const derived = new Map(enums.derivedValueSets); + for (const entry of namespaceModels) { + const valueSet = discriminatorValueSetOf(entry); + if (valueSet !== undefined) derived.set(valueSet.name, valueSet.values); + } if (namespaceId !== DEFAULT_NAMESPACE_ID) return derived; for (const [name, domainEnum] of Object.entries(defaultDomainEnums)) { if (derived.has(name)) refuseEnumAndNativeEnumSharingName(namespaceId, name); @@ -171,12 +212,24 @@ function buildModel( } } refuseUntravelledForeignKeys(entry, variant, travelledForeignKeys); + const discriminatorValueSet = discriminatorValueSetOf(entry); const checks = derivedChecks({ table: entry.table, tableName: entry.tableName, managed: effectiveControlPolicy(entry.table.control, all.contract.defaultControlPolicy) === 'managed', - domainEnumValues: all.domainEnumValues, + memberValueSets: + discriminatorValueSet === undefined + ? all.domainEnumValues + : new Map([ + ...all.domainEnumValues, + [discriminatorValueSet.name, discriminatorValueSet.values], + ]), + columnValueObjects: columnValueObjectsOf( + all.contract, + [entry, ...singleTableVariants], + all.context.codecLookup, + ), }); if (variant?.singleTable !== true) refuseUnderivedChecks(entry, checks); return { @@ -222,13 +275,14 @@ function buildNamespace( refuseUnderivedValueSets({ namespaceId, actual: valueSets, - derived: derivedValueSetsOf(namespaceId, enums, all.defaultDomainEnums), + derived: derivedValueSetsOf( + namespaceId, + enums, + all.defaultDomainEnums, + models.filter((entry) => entry.namespaceId === namespaceId), + ), }); - const rlsTables = rlsEnabledTables(namespaceId, namespace.entries['rls']); - const namespaceModels = models - .filter((entry) => entry.namespaceId === namespaceId) - .map((entry) => buildModel(all, entry, enums, rlsTables)); const compositeTypes = buildCompositeTypes({ contract, namespaceId, @@ -237,6 +291,10 @@ function buildNamespace( enumBlockNames: enums.blockNamesByTypeName, domainEnums: all.defaultDomainEnums, }); + const rlsTables = rlsEnabledTables(namespaceId, namespace.entries['rls']); + const namespaceModels = models + .filter((entry) => entry.namespaceId === namespaceId) + .map((entry) => buildModel(all, entry, enums, rlsTables)); const modelNameForTable = (tableName: string): string | undefined => models.find( (entry) => diff --git a/packages/3-targets/postgres/1-target/src/core/psl-print/refusals.ts b/packages/3-targets/postgres/1-target/src/core/psl-print/refusals.ts index 55db80bfb3b2..16658c3abb4e 100644 --- a/packages/3-targets/postgres/1-target/src/core/psl-print/refusals.ts +++ b/packages/3-targets/postgres/1-target/src/core/psl-print/refusals.ts @@ -13,7 +13,7 @@ import type { ContractField, ExecutionMutationDefaultValue, ScalarFieldType, - ValueObjectFieldType, + ValueObjectReference, } from '@internal/contract/types'; import type { GeneratorPhase } from '@internal/framework-components/components'; import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; @@ -302,7 +302,7 @@ export function refuseFieldColumnMismatch(input: { export function refuseUnwritableFieldShape( field: ContractField, coordinate: string, -): asserts field is ContractField & { readonly type: ScalarFieldType | ValueObjectFieldType } { +): asserts field is ContractField & { readonly type: ScalarFieldType | ValueObjectReference } { if (field.type.kind === 'union') { throw unsupported( `field ${coordinate} has a ${field.type.kind} type, which cannot be written in Prisma 8 PSL.`, @@ -334,15 +334,15 @@ export function refuseUnderivedMemberValueSet(input: { if (valueSet === undefined) return; const domainEnum = input.domainEnums[valueSet.entityName]; if ( - sameJson(valueSet, derivedValueSetRefs(valueSet.entityName).domain) && + sameJson(valueSet, derivedValueSetRefs(valueSet.entityName).storage) && domainEnum?.codecId === type.codecId && type.typeParams === undefined ) { return; } throw unsupported( - `value-object member ${coordinate} carries a value set other than an enum of the default namespace with that enum's codec and no type parameters, which cannot be written in Prisma 8 PSL.`, - "PSL types the member by the enum name, and the PSL source then points the member at that enum in the default namespace and gives it the enum's codec.", + `value-object member ${coordinate} carries a value set other than the value set of an enum of the default namespace, with that enum's codec and no type parameters, which cannot be written in Prisma 8 PSL.`, + "PSL types the member by the enum name, and the PSL source then points the member at that enum's value set in the default namespace and gives it the enum's codec.", KEEP_SOURCE, { coordinate }, ); @@ -526,7 +526,7 @@ export function refuseValueObjectsOutsideDefaultNamespace( contract: Contract, namespaceId: string, ): void { - const names = Object.keys(contract.domain.namespaces[namespaceId]?.valueObjects ?? {}); + const names = Object.keys(contract.storage.namespaces[namespaceId]?.entries.valueObject ?? {}); if (namespaceId === DEFAULT_NAMESPACE_ID || names.length === 0) return; throw unsupported( `namespace "${namespaceId}" declares the value object${names.length === 1 ? '' : 's'} ${names.join(', ')}, which cannot be written in Prisma 8 PSL: the PSL source reads every value object into the default namespace.`, @@ -572,7 +572,7 @@ export function refuseUnderivedChecks( if (check?.prefix === expected.prefix && check.expression === expected.expression) continue; throw unsupported( `table "${entry.namespaceId}"."${entry.tableName}" ${check === undefined ? 'lacks' : 'changes'} the check "${name}" the PSL source derives, so it cannot be written in Prisma 8 PSL.`, - 'The PSL source derives a membership check for each column of a managed table typed by an enum, and an element-not-null check for each list column whose elements are not nullable.', + 'The PSL source derives, on a managed table, a membership check for each column typed by an enum and for a discriminator column, an element-not-null check for each list column whose elements are not nullable, and a value object check for each column that holds a value object.', KEEP_SOURCE, { namespaceId: entry.namespaceId, table: entry.tableName, name }, ); @@ -821,11 +821,7 @@ export function refuseUnwrittenNamespaces(input: { const storageNamespaces = new Set([DEFAULT_NAMESPACE_ID, ...input.writtenStorageNamespaces]); const defaultDomain = contract.domain.namespaces[DEFAULT_NAMESPACE_ID]; const domainNamespaces = new Set(input.models.map((entry) => entry.namespaceId)); - if ( - domainNamespaces.size === 0 || - Object.keys(defaultDomain?.valueObjects ?? {}).length > 0 || - Object.keys(defaultDomain?.enum ?? {}).length > 0 - ) { + if (domainNamespaces.size === 0 || Object.keys(defaultDomain?.enum ?? {}).length > 0) { domainNamespaces.add(DEFAULT_NAMESPACE_ID); } const planes = [ @@ -1078,6 +1074,7 @@ export function refuseUnwritableName(kind: string, name: string): void { const PRINTED_ENTRY_KINDS: ReadonlySet = new Set([ 'table', 'valueSet', + 'valueObject', 'native_enum', 'rls', 'policy', @@ -1094,7 +1091,7 @@ export function refuseUnprintedEntryKinds( if (names.length === 0) continue; throw unsupported( `namespace "${namespaceId}" declares ${names.length} "${kind}" ${names.length === 1 ? 'entity' : 'entities'} (${names.join(', ')}), which the Postgres printer cannot write.`, - 'The printer writes tables, value sets, native enums, row-level security settings, policies and roles, and no other entity kind.', + 'The printer writes tables, value sets, value objects, native enums, row-level security settings, policies and roles, and no other entity kind.', KEEP_SOURCE, { namespaceId, kind, names }, ); diff --git a/packages/3-targets/postgres/1-target/src/core/psl-print/scalar-fields.ts b/packages/3-targets/postgres/1-target/src/core/psl-print/scalar-fields.ts index 787c5c389d31..9cddfd727d4e 100644 --- a/packages/3-targets/postgres/1-target/src/core/psl-print/scalar-fields.ts +++ b/packages/3-targets/postgres/1-target/src/core/psl-print/scalar-fields.ts @@ -3,11 +3,7 @@ import type { SqlPslBuildContext } from '@internal/family-sql/control'; import type { InferredTypeConstructors } from '@internal/family-sql/psl-build'; import type { PslField, PslFieldAttribute } from '@internal/framework-components/psl-ast'; import { escapePslString } from '@internal/sql-contract/data-type-support'; -import { - nonEmptyTypeParams, - type SqlStorage, - type StorageColumn, -} from '@internal/sql-contract/types'; +import { nonEmptyTypeParams, type SqlStorage, StorageColumn } from '@internal/sql-contract/types'; import { pslFieldMapName } from '@internal/sql-contract-psl/map-names'; import { assertDefined } from '@internal/utils/assertions'; import { ifDefined } from '@internal/utils/defined'; @@ -20,7 +16,11 @@ import { } from '../psl-build/psl-literals'; import { buildColumnDefault } from './column-defaults'; import { buildColumnType, type PslColumnType } from './column-types'; -import type { ModelWithTable, VariantInfo } from './contract-model-index'; +import { + discriminatorValueSetOf, + type ModelWithTable, + type VariantInfo, +} from './contract-model-index'; import type { NativeEnumEmission } from './enum-blocks'; import { buildGeneratorAttributes } from './generated-values'; import { @@ -107,11 +107,17 @@ export function buildScalarFields(input: { input; const primaryKeyColumns = variant === undefined ? (entry.table.primaryKey?.columns ?? []) : []; const fields: PslField[] = []; + const discriminatorField = + discriminatorValueSetOf(entry) === undefined ? undefined : entry.model.discriminator?.field; for (const [fieldName, fieldStorage] of Object.entries(entry.storage.fields)) { const columnName = fieldStorage.column; const coordinate = `"${entry.namespaceId}"."${entry.tableName}"."${columnName}"`; - const column = entry.table.columns[columnName]; + const storedColumn = entry.table.columns[columnName]; + const column = + fieldName === discriminatorField && storedColumn !== undefined + ? withoutValueSet(storedColumn) + : storedColumn; const field = entry.model.fields[fieldName]; assertDefined(column, `column ${coordinate} is not declared by its table`); if (field === undefined) refuseStorageOfUndeclaredField({ entry, fieldName, coordinate }); @@ -129,7 +135,7 @@ export function buildScalarFields(input: { }); const columnType: PslColumnType = field.type.kind === 'valueObject' - ? { kind: 'valueObject', typeName: field.type.name } + ? { kind: 'valueObject', typeName: field.type.entityName } : buildColumnType({ column, inferredConstructors, @@ -187,3 +193,16 @@ export function buildScalarFields(input: { return fields; } + +/** A discriminator column without the value set the PSL source writes for it from the variants, so it is written as the plain column it is declared as. */ +function withoutValueSet(column: StorageColumn): StorageColumn { + return new StorageColumn({ + dataType: column.dataType, + codecId: column.codecId, + nullable: column.nullable, + many: column.many, + ...ifDefined('typeParams', column.typeParams), + ...ifDefined('default', column.default), + ...ifDefined('control', column.control), + }); +} diff --git a/packages/3-targets/postgres/1-target/src/core/schema-ir/node-storage-coordinate.ts b/packages/3-targets/postgres/1-target/src/core/schema-ir/node-storage-coordinate.ts index 166be6782f01..a43a5e822f54 100644 --- a/packages/3-targets/postgres/1-target/src/core/schema-ir/node-storage-coordinate.ts +++ b/packages/3-targets/postgres/1-target/src/core/schema-ir/node-storage-coordinate.ts @@ -1,4 +1,4 @@ -import type { EntityCoordinate } from '@internal/framework-components/ir'; +import type { EntityCoordinate } from '@internal/contract/types'; import type { SqlSchemaIRNode } from '@internal/sql-schema-ir/types'; import { PostgresNativeEnumSchemaNode } from './postgres-native-enum-schema-node'; import { PostgresTableSchemaNode } from './postgres-table-schema-node'; diff --git a/packages/3-targets/postgres/1-target/src/core/temporal-codecs.ts b/packages/3-targets/postgres/1-target/src/core/temporal-codecs.ts index dd5fba1d7b96..a49c03d93a64 100644 --- a/packages/3-targets/postgres/1-target/src/core/temporal-codecs.ts +++ b/packages/3-targets/postgres/1-target/src/core/temporal-codecs.ts @@ -64,6 +64,7 @@ export class PgDateTemporalCodec extends CodecImpl< } export class PgDateTemporalDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -111,6 +112,7 @@ export class PgTimestampTemporalCodec extends CodecImpl< } export class PgTimestampTemporalDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -167,6 +169,7 @@ export class PgTimestampInstantCodec extends CodecImpl< } export class PgTimestampInstantDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -220,6 +223,7 @@ export class PgTimestamptzTemporalCodec extends CodecImpl< } export class PgTimestamptzTemporalDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -267,6 +271,7 @@ export class PgTimeTemporalCodec extends CodecImpl< } export class PgTimeTemporalDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } diff --git a/packages/3-targets/postgres/1-target/src/core/temporal-string-codecs.ts b/packages/3-targets/postgres/1-target/src/core/temporal-string-codecs.ts index 648d87e1b34b..31b3664eaf81 100644 --- a/packages/3-targets/postgres/1-target/src/core/temporal-string-codecs.ts +++ b/packages/3-targets/postgres/1-target/src/core/temporal-string-codecs.ts @@ -50,6 +50,7 @@ export class PgDateStringCodec extends CodecImpl< } export class PgDateStringDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -105,6 +106,7 @@ export class PgTimestampStringCodec extends CodecImpl< } export class PgTimestampStringDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -149,6 +151,7 @@ export class PgTimestamptzStringCodec extends CodecImpl< } export class PgTimestamptzStringDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } @@ -199,6 +202,7 @@ export class PgTimeStringCodec extends CodecImpl< } export class PgTimeStringDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return CastExpr.as(expression, 'text'); } diff --git a/packages/3-targets/postgres/1-target/src/exports/types.ts b/packages/3-targets/postgres/1-target/src/exports/types.ts index 89938d1a256b..8eb7180307ca 100644 --- a/packages/3-targets/postgres/1-target/src/exports/types.ts +++ b/packages/3-targets/postgres/1-target/src/exports/types.ts @@ -1,9 +1,4 @@ -export { - type PostgresCheckExpressionCandidate, - type PostgresCheckExpressionInput, - type PostgresCheckKind, - postgresRenderCheckExpressions, -} from '../core/check-expressions'; +export { postgresRenderCheckExpressions } from '../core/check-expressions'; export { PostgresNativeEnum, type PostgresNativeEnumInput } from '../core/postgres-native-enum'; export { PostgresRlsEnablement, diff --git a/packages/3-targets/postgres/1-target/test/check-expressions.integration.test.ts b/packages/3-targets/postgres/1-target/test/check-expressions.integration.test.ts index 631b7ce3aa65..fa8b54d78ac1 100644 --- a/packages/3-targets/postgres/1-target/test/check-expressions.integration.test.ts +++ b/packages/3-targets/postgres/1-target/test/check-expressions.integration.test.ts @@ -14,8 +14,9 @@ describe('numeric membership checks in Postgres', () => { const checks = postgresRenderCheckExpressions({ tableName: 'members', columnName: 'value', - many, + many: many ? { elementNullable: false } : false, memberValues, + valueObject: undefined, }); await client.query( `CREATE TABLE members (value ${storageType}${many ? '[]' : ''}, ${checks.map(({ expression }) => `CHECK (${expression})`).join(', ')})`, @@ -59,6 +60,7 @@ describe('list membership checks in Postgres', () => { columnName, many: { elementNullable: false }, memberValues: members, + valueObject: undefined, }), ); await client.query( @@ -127,6 +129,7 @@ describe('list membership checks in Postgres', () => { columnName: many ? 'list' : 'scalar', many: many ? { elementNullable: false } : false, memberValues: members, + valueObject: undefined, }), ); await client.query( diff --git a/packages/3-targets/postgres/1-target/test/check-expressions.test.ts b/packages/3-targets/postgres/1-target/test/check-expressions.test.ts index d1dd10bc3dc3..c60a4e528678 100644 --- a/packages/3-targets/postgres/1-target/test/check-expressions.test.ts +++ b/packages/3-targets/postgres/1-target/test/check-expressions.test.ts @@ -1,11 +1,13 @@ +import type { CheckExpressionInput } from '@internal/sql-contract/check-expressions'; import { describe, expect, it } from 'vitest'; import { postgresRenderCheckExpressions } from '../src/core/check-expressions'; -const base = { +const base: CheckExpressionInput = { tableName: 'User', columnName: 'role', many: false, memberValues: undefined, + valueObject: undefined, }; describe('postgresRenderCheckExpressions', () => { @@ -153,6 +155,7 @@ describe('postgresRenderCheckExpressions', () => { columnName: 'sta"tus', many: false, memberValues: ["o'brien"], + valueObject: undefined, }), ).toEqual([ { kind: 'membership', columnName: 'sta"tus', expression: `"sta""tus" IN ('o''brien')` }, diff --git a/packages/3-targets/postgres/1-target/test/check-expressions.value-object.test.ts b/packages/3-targets/postgres/1-target/test/check-expressions.value-object.test.ts new file mode 100644 index 000000000000..d55f8a64c8e3 --- /dev/null +++ b/packages/3-targets/postgres/1-target/test/check-expressions.value-object.test.ts @@ -0,0 +1,174 @@ +import type { + ResolvedColumnValueObject, + ResolvedValueObject, + ResolvedValueObjectField, + ResolvedValueObjectFieldType, +} from '@internal/sql-contract/check-expressions'; +import type { JsonType } from '@internal/sql-contract/codec-json-type'; +import { describe, expect, it } from 'vitest'; +import { postgresRenderCheckExpressions } from '../src/core/check-expressions'; + +function scalar( + jsonType: JsonType, + values: readonly (string | number)[] | undefined = undefined, +): ResolvedValueObjectFieldType { + return { kind: 'scalar', jsonType, values }; +} + +function member( + type: ResolvedValueObjectFieldType, + options: Partial> = {}, +): ResolvedValueObjectField { + return { nullable: false, many: false, ...options, type }; +} + +function holding(valueObject: ResolvedValueObject): ResolvedValueObjectFieldType { + return { kind: 'valueObject', valueObject }; +} + +function render( + valueObject: ResolvedValueObject, + column: Partial> = {}, +): readonly { readonly kind: string; readonly expression: string }[] { + return postgresRenderCheckExpressions({ + tableName: 'user', + columnName: 'address', + many: false, + memberValues: undefined, + valueObject: { nullable: false, many: false, ...column, valueObject }, + }).map(({ kind, expression }) => ({ kind, expression })); +} + +const street = member(scalar('string')); +const flat: ResolvedValueObject = { fields: { street } }; + +const isObject = '$.type() == "object"'; +const hasStreet = 'exists($.keyvalue() ? (@.key == "street" && (@.value.type() == "string")))'; +const onlyStreet = '!exists($.keyvalue() ? (@.key != "street"))'; + +describe('postgresRenderCheckExpressions for a column that holds a value object', () => { + it('requires an object with each required member, of its JSON type, and no other key', () => { + expect(render(flat)).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && ${hasStreet} && ${onlyStreet}') IS TRUE`, + }, + ]); + }); + + it('accepts SQL NULL and JSON null in a nullable column', () => { + expect(render(flat, { nullable: true })).toEqual([ + { + kind: 'shape', + expression: `"address" IS NULL OR jsonb_path_match("address", 'strict $.type() == "null" || (${isObject} && ${hasStreet} && ${onlyStreet})') IS TRUE`, + }, + ]); + }); + + it('lets a nullable member be absent or JSON null', () => { + const zip = member(scalar('number'), { nullable: true }); + expect(render({ fields: { zip } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && !exists($.keyvalue() ? (@.key == "zip" && !(@.value.type() == "null" || (@.value.type() == "number")))) && !exists($.keyvalue() ? (@.key != "zip"))') IS TRUE`, + }, + ]); + }); + + it('checks list members and their element nullability', () => { + const tags = member(scalar('string'), { many: { elementNullable: false } }); + const flags = member(scalar('boolean'), { many: { elementNullable: true } }); + expect(render({ fields: { tags, flags } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "flags" && (@.value.type() == "array" && !exists(@.value[*] ? (!(@.type() == "null" || (@.type() == "boolean"))))))) && exists($.keyvalue() ? (@.key == "tags" && (@.value.type() == "array" && !exists(@.value[*] ? (!((@.type() == "string"))))))) && !exists($.keyvalue() ? (@.key != "flags" && @.key != "tags"))') IS TRUE`, + }, + ]); + }); + + it('restricts an enum member to its value set', () => { + const kind = member(scalar('string', ['home', "o'brien"])); + expect(render({ fields: { kind } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "kind" && (@.value.type() == "string" && (@.value == "home" || @.value == "o''brien")))) && !exists($.keyvalue() ? (@.key != "kind"))') IS TRUE`, + }, + ]); + }); + + it('restricts a numeric enum member to its value set', () => { + const rank = member(scalar('number', [1, -2])); + expect(render({ fields: { rank } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "rank" && (@.value.type() == "number" && (@.value == 1 || @.value == -2)))) && !exists($.keyvalue() ? (@.key != "rank"))') IS TRUE`, + }, + ]); + }); + + it('inlines a nested value object and a list of them', () => { + const geo = member(holding(flat)); + const stops = member(holding(flat), { many: { elementNullable: false } }); + const nestedAt = (path: string) => + `${path}.type() == "object" && exists(${path}.keyvalue() ? (@.key == "street" && (@.value.type() == "string"))) && !exists(${path}.keyvalue() ? (@.key != "street"))`; + expect(render({ fields: { geo, stops } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "geo" && (${nestedAt('@.value')}))) && exists($.keyvalue() ? (@.key == "stops" && (@.value.type() == "array" && !exists(@.value[*] ? (!((${nestedAt('@')}))))))) && !exists($.keyvalue() ? (@.key != "geo" && @.key != "stops"))') IS TRUE`, + }, + ]); + }); + + it('accepts any object where a value object refers back into itself', () => { + const children = member({ kind: 'anyObject' }, { many: { elementNullable: false } }); + expect(render({ fields: { children } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "children" && (@.value.type() == "array" && !exists(@.value[*] ? (!((@.type() == "object"))))))) && !exists($.keyvalue() ? (@.key != "children"))') IS TRUE`, + }, + ]); + }); + + it('checks a list column as an array of the value object', () => { + expect(render(flat, { many: { elementNullable: false } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict $.type() == "array" && !exists($[*] ? (!((@.type() == "object" && exists(@.keyvalue() ? (@.key == "street" && (@.value.type() == "string"))) && !exists(@.keyvalue() ? (@.key != "street"))))))') IS TRUE`, + }, + ]); + }); + + it('requires an object or array member to be of its JSON type, and lets a member of any JSON type hold any value, JSON null included', () => { + const bytes = member(scalar('object')); + const data = member(scalar('any')); + const notes = member(scalar('any'), { nullable: true }); + const tags = member(scalar('array')); + const blobs = member(scalar('object'), { many: { elementNullable: false } }); + expect(render({ fields: { blobs, bytes, data, notes, tags } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "blobs" && (@.value.type() == "array" && !exists(@.value[*] ? (!((@.type() == "object"))))))) && exists($.keyvalue() ? (@.key == "bytes" && (@.value.type() == "object"))) && exists($.keyvalue() ? (@.key == "data")) && exists($.keyvalue() ? (@.key == "tags" && (@.value.type() == "array"))) && !exists($.keyvalue() ? (@.key != "blobs" && @.key != "bytes" && @.key != "data" && @.key != "notes" && @.key != "tags"))') IS TRUE`, + }, + ]); + }); + + it('requires a member written as a JSON string to be a string', () => { + const amount = member(scalar('string')); + expect(render({ fields: { amount } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "amount" && (@.value.type() == "string"))) && !exists($.keyvalue() ? (@.key != "amount"))') IS TRUE`, + }, + ]); + }); + + it('writes member names as jsonpath strings', () => { + const quoted = member(scalar('string')); + expect(render({ fields: { 'say "hi"': quoted } })).toEqual([ + { + kind: 'shape', + expression: `jsonb_path_match("address", 'strict ${isObject} && exists($.keyvalue() ? (@.key == "say \\"hi\\"" && (@.value.type() == "string"))) && !exists($.keyvalue() ? (@.key != "say \\"hi\\""))') IS TRUE`, + }, + ]); + }); +}); diff --git a/packages/3-targets/postgres/1-target/test/codec-json-type.test.ts b/packages/3-targets/postgres/1-target/test/codec-json-type.test.ts new file mode 100644 index 000000000000..8f33a027a96e --- /dev/null +++ b/packages/3-targets/postgres/1-target/test/codec-json-type.test.ts @@ -0,0 +1,87 @@ +import { declaredJsonType } from '@internal/sql-contract/codec-json-type'; +import { describe, expect, it } from 'vitest'; +import { buildPostgresCodecDescriptorRegistry } from '../src/core/codec-descriptor'; +import { postgresCodecDescriptorRegistry } from '../src/core/registry'; + +describe('the JSON type a codec declares', () => { + it('is declared by every Postgres and SQL family codec', () => { + const undeclared = [...postgresCodecDescriptorRegistry.values()] + .filter((descriptor) => declaredJsonType(descriptor) === undefined) + .map((descriptor) => descriptor.codecId); + expect(undeclared).toEqual([]); + }); + + it('is the type the codec value is written as inside a JSON document', () => { + expect( + Object.fromEntries( + [ + 'sql/text@1', + 'sql/int@1', + 'sql/raw@1', + 'pg/numeric@1', + 'pg/bool@1', + 'pg/enum@1', + 'pg/text-array@1', + 'pg/bytea@1', + 'pg/interval@1', + 'pg/jsonb@1', + 'pg/int8@1', + 'pg/timestamp-date@1', + 'pg/timestamp-instant@1', + 'pg/interval-string@1', + ].map((codecId) => [ + codecId, + postgresCodecDescriptorRegistry.descriptorFor(codecId)?.jsonType, + ]), + ), + ).toEqual({ + 'sql/text@1': 'string', + 'sql/int@1': 'number', + 'sql/raw@1': 'any', + 'pg/numeric@1': 'string', + 'pg/bool@1': 'boolean', + 'pg/enum@1': 'string', + 'pg/text-array@1': 'array', + 'pg/bytea@1': 'object', + 'pg/interval@1': 'object', + 'pg/jsonb@1': 'any', + 'pg/int8@1': 'number', + 'pg/timestamp-date@1': 'string', + 'pg/timestamp-instant@1': 'string', + 'pg/interval-string@1': 'string', + }); + }); + + it('is not required of a descriptor the registry accepts, so a codec compiled before codecs declared one still registers', () => { + const text = postgresCodecDescriptorRegistry.descriptorFor('pg/text@1'); + const undeclared = { + descriptorKind: text?.descriptorKind, + codecId: 'demo/undeclared@1', + traits: text?.traits, + paramsSchema: undefined, + isParameterized: false, + factory: text?.factory.bind(text), + projectJson: text?.projectJson.bind(text), + }; + expect( + buildPostgresCodecDescriptorRegistry([undeclared]).descriptorFor('demo/undeclared@1'), + ).toBe(undeclared); + }); + + it('is refused when a descriptor declares one that is not a JSON type', () => { + const text = postgresCodecDescriptorRegistry.descriptorFor('pg/text@1'); + const misdeclared = { + descriptorKind: text?.descriptorKind, + codecId: 'demo/misdeclared@1', + traits: text?.traits, + paramsSchema: undefined, + isParameterized: false, + factory: text?.factory.bind(text), + projectJson: text?.projectJson.bind(text), + jsonType: 'presenceOnly', + }; + expect(() => buildPostgresCodecDescriptorRegistry([misdeclared])).toThrow( + expect.objectContaining({ code: 'RUNTIME.CODEC_DESCRIPTOR_INVALID' }), + ); + }); +}); diff --git a/packages/3-targets/postgres/1-target/test/migrations/rows-failing-check.test.ts b/packages/3-targets/postgres/1-target/test/migrations/rows-failing-check.test.ts new file mode 100644 index 000000000000..4262613674b0 --- /dev/null +++ b/packages/3-targets/postgres/1-target/test/migrations/rows-failing-check.test.ts @@ -0,0 +1,120 @@ +import { coreHash } from '@internal/contract/types'; +import { CheckConstraint, SqlStorage, StorageTable } from '@internal/sql-contract/types'; +import { computeCheckContentHash, formatWireName } from '@internal/sql-schema-ir/naming'; +import { describe, expect, it } from 'vitest'; +import { buildTargetDetails } from '../../src/core/migrations/planner-target-details'; +import { rowsFailingCheckOf } from '../../src/core/migrations/rows-failing-check'; +import { postgresCreateNamespace } from '../../src/core/postgres-schema'; + +const OLD_EXPRESSION = `"address" IS NULL`; +const NEW_EXPRESSION = `"address" IS NOT NULL`; +const PREFIX = 'user_address_shape'; +const OLD = formatWireName(PREFIX, computeCheckContentHash(OLD_EXPRESSION)); +const NEW = formatWireName(PREFIX, computeCheckContentHash(NEW_EXPRESSION)); + +function storageWith(check: CheckConstraint): SqlStorage { + return new SqlStorage({ + storageHash: coreHash('contract'), + namespaces: { + public: postgresCreateNamespace({ + id: 'public', + entries: { + table: { + user: new StorageTable({ + columns: { + id: { dataType: 'pg/int4', codecId: 'pg/int4@1', nullable: false }, + address: { dataType: 'pg/jsonb', codecId: 'pg/jsonb@1', nullable: true }, + }, + primaryKey: { columns: ['id'] }, + foreignKeys: [], + uniques: [], + indexes: [], + checks: [check], + }), + }, + }, + }), + }, + }); +} + +function checkOperation(id: string, name: string) { + return { + id, + label: id, + operationClass: 'additive' as const, + target: { + id: 'postgres', + details: buildTargetDetails('checkConstraint', name, 'public', 'user'), + }, + precheck: [], + execute: [], + postcheck: [], + }; +} + +const wireCheck = new CheckConstraint({ + naming: { kind: 'wire', prefix: PREFIX, hash: computeCheckContentHash(NEW_EXPRESSION) }, + expression: NEW_EXPRESSION, +}); +const drop = checkOperation(`dropCheckConstraint.user.${OLD}`, OLD); +const add = checkOperation(`checkConstraint.user.${NEW}`, NEW); + +describe('the rows a check the apply adds refuses', () => { + it('names the check, the table and the column, gives the query, and the check the plan drops before it', () => { + expect( + rowsFailingCheckOf({ + operation: add, + constraint: NEW, + storage: storageWith(wireCheck), + operations: [drop, add], + operationIndex: 1, + }), + ).toEqual({ + summary: `Rows of table "user" fail check "${NEW}" on column "address", so the check cannot be added.`, + why: `Find them with \`SELECT * FROM "public"."user" WHERE NOT ("address" IS NOT NULL)\`.`, + fact: { check: NEW, replacedCheck: OLD, operationIndex: 1 }, + }); + }); + + it('names no column and no replaced check for a check named by hand', () => { + const exact = new CheckConstraint({ + naming: { kind: 'exact', name: 'address_present' }, + expression: NEW_EXPRESSION, + }); + const addExact = checkOperation('checkConstraint.user.address_present', 'address_present'); + expect( + rowsFailingCheckOf({ + operation: addExact, + constraint: 'address_present', + storage: storageWith(exact), + operations: [drop, addExact], + operationIndex: 1, + }), + ).toEqual({ + summary: 'Rows of table "user" fail check "address_present", so the check cannot be added.', + why: 'Find them with `SELECT * FROM "public"."user" WHERE NOT ("address" IS NOT NULL)`.', + fact: { check: 'address_present', operationIndex: 1 }, + }); + }); + + it('says nothing when the violated check is not the one the operation adds, or the contract does not declare it', () => { + const storage = storageWith(wireCheck); + expect([ + rowsFailingCheckOf({ + operation: add, + constraint: 'other', + storage, + operations: [add], + operationIndex: 0, + }), + rowsFailingCheckOf({ + operation: checkOperation('checkConstraint.user.undeclared', 'undeclared'), + constraint: 'undeclared', + storage, + operations: [], + operationIndex: 0, + }), + ]).toEqual([undefined, undefined]); + }); +}); diff --git a/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test-d.ts b/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test-d.ts index 32dd3b07dbc1..bce54bd7e38b 100644 --- a/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test-d.ts +++ b/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test-d.ts @@ -10,12 +10,14 @@ import { dataType, dataTypeId, } from '@internal/framework-components/codec'; +import type { JsonType } from '@internal/sql-contract/codec-json-type'; import { FunctionCallExpr, type ProjectionExpr } from '@internal/sql-relational-core/ast'; import type { StandardSchemaV1 } from '@standard-schema/spec'; import { expectTypeOf, test } from 'vitest'; import { definePostgresCodecs, PostgresCodecDescriptor, + type PostgresCodecDescriptorRegistry, postgresCodec, } from '../src/exports/codec-descriptor'; @@ -65,6 +67,7 @@ class VectorCodec extends CodecImpl< } class GenericVectorDescriptor extends CodecDescriptorImpl { + readonly jsonType = 'array' as const; override readonly dataType = dataTypeId('demo/fixture'); override readonly codecId = 'demo/vector@1' as const; override readonly traits = ['equality'] as const; @@ -83,6 +86,7 @@ class GenericVectorDescriptor extends CodecDescriptorImpl { } class DirectVectorDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; override readonly dataType = dataTypeId('demo/fixture'); override readonly codecId = 'demo/direct-vector@1' as const; override readonly traits = ['equality'] as const; @@ -188,6 +192,7 @@ test('a factory option builds a codec of the family codec it adapts', () => { // @ts-expect-error -- direct descriptors must implement scalar JSON projection class MissingJsonProjection extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; override readonly dataType = dataTypeId('demo/fixture'); override readonly codecId = 'demo/missing-json@1' as const; override readonly traits: readonly CodecTrait[] = []; @@ -198,3 +203,8 @@ class MissingJsonProjection extends PostgresCodecDescriptor { } void MissingJsonProjection; + +test('a registered descriptor may lack the JSON type, which only an author must declare', () => { + type Registered = NonNullable>; + expectTypeOf().toEqualTypeOf(); +}); diff --git a/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test.ts b/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test.ts index 054671a7fcc4..9e04ecaaeb5b 100644 --- a/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test.ts +++ b/packages/3-targets/postgres/1-target/test/postgres-codec-descriptor.test.ts @@ -98,6 +98,7 @@ class VectorCodec extends CodecImpl< } class GenericVectorDescriptor extends CodecDescriptorImpl { + readonly jsonType = 'array' as const; override readonly dataType = dataTypeId('demo/fixture'); override readonly codecId = 'demo/vector@1' as const; override readonly traits = ['equality'] as const; @@ -130,6 +131,7 @@ class RenderingVectorDescriptor extends GenericVectorDescriptor { } class DirectVectorDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; override readonly dataType = dataTypeId('demo/fixture'); override readonly codecId = 'demo/direct-vector@1' as const; override readonly traits = ['equality'] as const; @@ -311,6 +313,7 @@ describe('postgresCodec', () => { expect(descriptor.descriptorKind).toBe('postgres-codec'); expect(descriptor.codecId).toBe(genericVectorDescriptor.codecId); expect(descriptor.traits).toBe(genericVectorDescriptor.traits); + expect(descriptor.jsonType).toBe('array'); expect(descriptor.dataType).toBe(fixtureVectorType.id); expect(descriptor.isParameterized).toBe(genericVectorDescriptor.isParameterized); expect(descriptor.renderOutputType?.({ length: 6 })).toBe('Vector<6>'); @@ -410,6 +413,7 @@ describe('Postgres codec descriptor registry', () => { isParameterized: descriptor.isParameterized, factory: descriptor.factory.bind(descriptor), projectJson: descriptor.projectJson.bind(descriptor), + jsonType: descriptor.jsonType, }; expect(structuralDescriptor).not.toBeInstanceOf(PostgresCodecDescriptor); @@ -431,6 +435,7 @@ describe('Postgres codec descriptor registry', () => { isParameterized: descriptor.isParameterized, factory: descriptor.factory.bind(descriptor), projectJson: descriptor.projectJson.bind(descriptor), + jsonType: descriptor.jsonType, }; expect(() => buildPostgresCodecDescriptorRegistry([genericVectorDescriptor])).toThrow( diff --git a/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.check.test.ts b/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.check.test.ts index 09fc7cc66c91..60d5c6cb1809 100644 --- a/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.check.test.ts +++ b/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.check.test.ts @@ -19,8 +19,9 @@ import { printPslFromFlat } from '../fixtures'; const elementCandidate = postgresRenderCheckExpressions({ tableName: 'users', columnName: 'tags', - many: true, + many: { elementNullable: false }, memberValues: undefined, + valueObject: undefined, })[0]; const derivedPrefix = composeCheckWirePrefix('users', 'tags', 'elementNotNull'); diff --git a/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.list-element-nullability.test.ts b/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.list-element-nullability.test.ts index 0f8f01fd0ed6..470d859565bd 100644 --- a/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.list-element-nullability.test.ts +++ b/packages/3-targets/postgres/1-target/test/psl-infer/inferred-psl/inferred-psl.list-element-nullability.test.ts @@ -17,8 +17,9 @@ import { printPslFromFlat } from '../fixtures'; const elementCandidate = postgresRenderCheckExpressions({ tableName: 'users', columnName: 'tags', - many: true, + many: { elementNullable: false }, memberValues: undefined, + valueObject: undefined, })[0]; const derivedPrefix = composeCheckWirePrefix('users', 'tags', 'elementNotNull'); diff --git a/packages/3-targets/postgres/1-target/test/psl-print/build-context.ts b/packages/3-targets/postgres/1-target/test/psl-print/build-context.ts index ce54c9c47ac1..4e31afeeafee 100644 --- a/packages/3-targets/postgres/1-target/test/psl-print/build-context.ts +++ b/packages/3-targets/postgres/1-target/test/psl-print/build-context.ts @@ -18,7 +18,8 @@ import { postgresTargetDescriptorMeta } from '../../src/core/descriptor-meta'; import { postgresCodecDescriptorRegistry } from '../../src/core/registry'; import { postgresScalarAndNativeTypeConstructors } from '../../src/core/type-constructors'; -const citextTemplate: CodecDescriptorTemplate = { +const citextTemplate: CodecDescriptorTemplate & { readonly jsonType: 'string' } = { + jsonType: 'string', codecId: 'ext/citext@1', traits: [], paramsSchema: undefined, diff --git a/packages/3-targets/postgres/1-target/test/psl-print/discriminator.test.ts b/packages/3-targets/postgres/1-target/test/psl-print/discriminator.test.ts new file mode 100644 index 000000000000..29f38d97f2e9 --- /dev/null +++ b/packages/3-targets/postgres/1-target/test/psl-print/discriminator.test.ts @@ -0,0 +1,109 @@ +import { + composeCheckWirePrefix, + computeCheckContentHash, + formatWireName, +} from '@internal/sql-schema-ir/naming'; +import { describe, expect, it } from 'vitest'; +import { attributeText, fieldText } from './print-support'; +import { + deserialize, + INT_COLUMN, + INT_FIELD, + PUBLIC, + printing, + TEXT_COLUMN, + TEXT_FIELD, +} from './refusal-support'; + +const membership = `"type" IN ('bug', 'feature')`; +const prefix = composeCheckWirePrefix('Task', 'type', 'membership'); +const derivedCheck = { + name: formatWireName(prefix, computeCheckContentHash(membership)), + prefix, + expression: membership, +}; + +function taskContract(checks: readonly (typeof derivedCheck)[]) { + const variant = { + storage: { table: 'Task', namespaceId: 'public', fields: {} }, + fields: {}, + relations: {}, + base: { namespace: PUBLIC, model: 'Task' }, + }; + return deserialize({ + roots: { Task: { namespace: PUBLIC, model: 'Task' } }, + namespaces: { + public: { + models: { + Task: { + storage: { + table: 'Task', + namespaceId: 'public', + fields: { id: { column: 'id' }, type: { column: 'type' } }, + }, + fields: { id: INT_FIELD, type: TEXT_FIELD }, + relations: {}, + discriminator: { field: 'type' }, + variants: { + bug: { namespace: PUBLIC, model: 'Bug' }, + feature: { namespace: PUBLIC, model: 'Feature' }, + }, + }, + Bug: variant, + Feature: variant, + }, + }, + }, + storage: { + namespaces: { + public: { + id: 'public', + entries: { + table: { + Task: { + columns: { + id: INT_COLUMN, + type: { + ...TEXT_COLUMN, + valueSet: { + plane: 'storage', + entityKind: 'valueSet', + namespaceId: 'public', + entityName: 'Task.type', + }, + }, + }, + uniques: [], + indexes: [], + foreignKeys: [], + primaryKey: { columns: ['id'] }, + checks, + }, + }, + valueSet: { 'Task.type': { kind: 'valueSet', values: ['bug', 'feature'] } }, + }, + }, + }, + }, + }); +} + +describe('a discriminator column with its value set and membership check', () => { + it('is written as the plain field, without the check or an enum the PSL source derives', () => { + const document = printing(taskContract([derivedCheck]))(); + const task = document.namespaces + .flatMap((namespace) => namespace.models) + .find((model) => model.name === 'Task'); + expect({ + fields: task?.fields.map(fieldText), + attributes: task?.attributes.map(attributeText), + }).toEqual({ + fields: ['id Int @id', 'type String'], + attributes: ['@@discriminator(type)'], + }); + }); + + it('is refused when its table lacks the membership check', () => { + expect(printing(taskContract([]))).toThrow(`lacks the check "${derivedCheck.name}"`); + }); +}); diff --git a/packages/3-targets/postgres/1-target/test/psl-print/member-support.ts b/packages/3-targets/postgres/1-target/test/psl-print/member-support.ts index 29597cd446f9..4ea88f23ac27 100644 --- a/packages/3-targets/postgres/1-target/test/psl-print/member-support.ts +++ b/packages/3-targets/postgres/1-target/test/psl-print/member-support.ts @@ -1,13 +1,28 @@ import type { ContractField } from '@internal/contract/types'; import { deserialize, widgetContract } from './refusal-support'; +/** A table that derives no checks, so these contracts need not carry the value object check of `address`. */ +const UNCHECKED_TABLE = { control: 'external' } as const; + /** A contract whose one value object, `Address`, has the one member `street`. */ export function addressContract(field: ContractField) { return deserialize( widgetContract({ columns: { address: { dataType: 'pg/jsonb', codecId: 'pg/jsonb@1', nullable: false } }, - fields: { address: { nullable: false, type: { kind: 'valueObject', name: 'Address' } } }, - domain: { valueObjects: { Address: { fields: { street: field } } } }, + table: UNCHECKED_TABLE, + fields: { + address: { + nullable: false, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, + }, + }, + entries: { valueObject: { Address: { fields: { street: field } } } }, }), ); } @@ -16,20 +31,38 @@ const countryEnumParts = { enum: { Country: { codecId: 'pg/text@1', members: [{ name: 'DE', value: 'DE' }] } }, } as const; export const countryValueSet = { - plane: 'domain', - entityKind: 'enum', + plane: 'storage', + entityKind: 'valueSet', namespaceId: 'public', entityName: 'Country', } as const; -/** A contract whose value object `Address` has the given members, beside the domain enum `Country`. */ -export function withCountryMembers(fields: Record) { +/** A contract whose value object `Address` has the given members, beside the domain enum `Country` and its value set, and the given other value sets. */ +export function withCountryMembers( + fields: Record, + valueSets: Record = {}, +) { return deserialize( widgetContract({ columns: { address: { dataType: 'pg/jsonb', codecId: 'pg/jsonb@1', nullable: false } }, - fields: { address: { nullable: false, type: { kind: 'valueObject', name: 'Address' } } }, - domain: { ...countryEnumParts, valueObjects: { Address: { fields } } }, - entries: { valueSet: { Country: { kind: 'valueSet', values: ['DE'] } } }, + table: UNCHECKED_TABLE, + fields: { + address: { + nullable: false, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', + }, + }, + }, + domain: countryEnumParts, + entries: { + valueSet: { Country: { kind: 'valueSet', values: ['DE'] }, ...valueSets }, + valueObject: { Address: { fields } }, + }, }), ); } diff --git a/packages/3-targets/postgres/1-target/test/psl-print/members.test.ts b/packages/3-targets/postgres/1-target/test/psl-print/members.test.ts index 0bb97775c644..2af4a30be7bc 100644 --- a/packages/3-targets/postgres/1-target/test/psl-print/members.test.ts +++ b/packages/3-targets/postgres/1-target/test/psl-print/members.test.ts @@ -45,9 +45,21 @@ describe('value-object members are written as the type their domain type reads b deserialize( widgetContract({ columns: { price: { dataType: 'pg/jsonb', codecId: 'pg/jsonb@1', nullable: false } }, - fields: { price: { nullable: false, type: { kind: 'valueObject', name: 'Price' } } }, - domain: { - valueObjects: { + table: { control: 'external' }, + fields: { + price: { + nullable: false, + type: { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Price', + }, + }, + }, + entries: { + valueObject: { Price: { fields: { amount: { nullable: false, type: numeric }, diff --git a/packages/3-targets/postgres/1-target/test/psl-print/refusals-entries.test.ts b/packages/3-targets/postgres/1-target/test/psl-print/refusals-entries.test.ts index e36df50b1036..167901206c3a 100644 --- a/packages/3-targets/postgres/1-target/test/psl-print/refusals-entries.test.ts +++ b/packages/3-targets/postgres/1-target/test/psl-print/refusals-entries.test.ts @@ -5,7 +5,7 @@ describe('names PSL writes as identifiers', () => { it('refuses a value object name that is not an identifier', () => { expect( printingWidget({ - domain: { valueObjects: { 'Postal Address': { fields: { street: TEXT_FIELD } } } }, + entries: { valueObject: { 'Postal Address': { fields: { street: TEXT_FIELD } } } }, }), ).toThrow(refusal({ kind: 'value object', name: 'Postal Address' })); }); @@ -19,7 +19,7 @@ describe('names PSL writes as identifiers', () => { it('refuses a value-object field name that is not an identifier', () => { expect( printingWidget({ - domain: { valueObjects: { Address: { fields: { 'street line': TEXT_FIELD } } } }, + entries: { valueObject: { Address: { fields: { 'street line': TEXT_FIELD } } } }, }), ).toThrow(refusal({ kind: 'field', name: 'street line' })); }); diff --git a/packages/3-targets/postgres/1-target/test/psl-print/refusals-storage.test.ts b/packages/3-targets/postgres/1-target/test/psl-print/refusals-storage.test.ts index fe365df2b102..4cd6016833b6 100644 --- a/packages/3-targets/postgres/1-target/test/psl-print/refusals-storage.test.ts +++ b/packages/3-targets/postgres/1-target/test/psl-print/refusals-storage.test.ts @@ -268,27 +268,29 @@ describe('value objects', () => { it('refuses a value object outside the default namespace, which the PSL source would move', () => { expect( printingWidget({ - domainNamespaces: { - auth: { models: {}, valueObjects: { Address: { fields: { street: TEXT_FIELD } } } }, + storageNamespaces: { + auth: { + id: 'auth', + entries: { table: {}, valueObject: { Address: { fields: { street: TEXT_FIELD } } } }, + }, }, - storageNamespaces: { auth: { id: 'auth', entries: { table: {} } } }, }), ).toThrow(refusal({ namespaceId: 'auth', names: ['Address'] })); }); - it('refuses a value-object member whose type is a union', () => { - expect( - withAddress({ + it('never reaches a value-object member that is a union, which deserializing refuses', () => { + expect(() => + addressContract({ many: false, nullable: false, type: { kind: 'union', members: [{ kind: 'scalar', codecId: 'pg/text@1' }] }, }), - ).toThrow(refusal({ coordinate: '"public".Address.street', kind: 'union' })); + ).toThrow('value object "public:Address" member "street" is a union'); }); - it('refuses a value-object member that is a dictionary', () => { - expect(withAddress({ ...TEXT_FIELD, dict: true })).toThrow( - refusal({ coordinate: '"public".Address.street' }), + it('never reaches a value-object member that is a dictionary, which deserializing refuses', () => { + expect(() => addressContract({ ...TEXT_FIELD, dict: true })).toThrow( + 'value object "public:Address" member "street" is a dictionary', ); }); @@ -328,25 +330,22 @@ describe('value objects', () => { ); }); - it('refuses a value-object member whose value set is not a domain enum of the default namespace', () => { + it('refuses a value set no enum derives, in the namespace that holds it', () => { expect( - withAddress({ - ...TEXT_FIELD, - valueSet: { - plane: 'domain', - entityKind: 'enum', - namespaceId: 'public', - entityName: 'Label', - }, - }), - ).toThrow(refusal({ coordinate: '"public".Address.street' })); + printing( + withCountryMembers( + { label: { ...TEXT_FIELD, valueSet: { ...countryValueSet, entityName: 'Label' } } }, + { Label: { kind: 'valueSet', values: ['x'] } }, + ), + ), + ).toThrow(refusal({ namespaceId: 'public', name: 'Label' })); }); - it('refuses a value-object member whose value set names an enum the domain does not declare', () => { + it('refuses a value-object member whose value set is not the one the enum of its name derives', () => { expect( printing( withCountryMembers({ - country: { ...TEXT_FIELD, valueSet: { ...countryValueSet, entityName: 'Missing' } }, + country: { ...TEXT_FIELD, valueSet: { ...countryValueSet, spaceId: 'other' } }, }), ), ).toThrow(refusal({ coordinate: '"public".Address.country' })); diff --git a/packages/3-targets/postgres/2-adapter/test/lower-to-execute-request.test.ts b/packages/3-targets/postgres/2-adapter/test/lower-to-execute-request.test.ts index 09e4ca9297cb..55ebcdb4ad0f 100644 --- a/packages/3-targets/postgres/2-adapter/test/lower-to-execute-request.test.ts +++ b/packages/3-targets/postgres/2-adapter/test/lower-to-execute-request.test.ts @@ -48,11 +48,14 @@ const transformingCodecDescriptor: AnyCodecDescriptor = { isParameterized: false, factory: () => () => transformingCodec, }; -const transformingDescriptor = postgresCodec(transformingCodecDescriptor, { - dataType: pgText, - jsonProjection: (expression: ProjectionExpr) => expression, - factory: () => () => transformingCodec, -}); +const transformingDescriptor = postgresCodec( + Object.assign(transformingCodecDescriptor, { jsonType: 'any' as const }), + { + dataType: pgText, + jsonProjection: (expression: ProjectionExpr) => expression, + factory: () => () => transformingCodec, + }, +); const transformingCodecRegistry = createPostgresCodecRegistryWithBuiltins([transformingDescriptor]); describe('PostgresControlAdapter.lowerToExecuteRequest — DDL literal defaults', () => { @@ -322,11 +325,14 @@ class ExtTransformDescriptor extends CodecDescriptorImpl { } const extDescriptor = new ExtTransformDescriptor(); -const extTransformDescriptor = postgresCodec(extDescriptor, { - dataType: pgText, - jsonProjection: (expression: ProjectionExpr) => expression, - factory: () => extDescriptor.factory(), -}); +const extTransformDescriptor = postgresCodec( + Object.assign(extDescriptor, { jsonType: 'any' as const }), + { + dataType: pgText, + jsonProjection: (expression: ProjectionExpr) => expression, + factory: () => extDescriptor.factory(), + }, +); function buildExtContractAndTable() { const tableColumns: StorageTableInput['columns'] = { diff --git a/packages/3-targets/postgres/2-adapter/test/migrations/alter-column-type.integration.test.ts b/packages/3-targets/postgres/2-adapter/test/migrations/alter-column-type.integration.test.ts index 7f9aa04d8f34..5a98d8ec1d32 100644 --- a/packages/3-targets/postgres/2-adapter/test/migrations/alter-column-type.integration.test.ts +++ b/packages/3-targets/postgres/2-adapter/test/migrations/alter-column-type.integration.test.ts @@ -51,6 +51,7 @@ function elementChecks(): CheckConstraint[] { columnName: 'tags', many: { elementNullable: false }, memberValues: undefined, + valueObject: undefined, }).map( (candidate) => new CheckConstraint({ diff --git a/packages/3-targets/postgres/2-adapter/test/migrations/check-lifecycle-e2e.integration.test.ts b/packages/3-targets/postgres/2-adapter/test/migrations/check-lifecycle-e2e.integration.test.ts index 95f646859739..39e2581e6b00 100644 --- a/packages/3-targets/postgres/2-adapter/test/migrations/check-lifecycle-e2e.integration.test.ts +++ b/packages/3-targets/postgres/2-adapter/test/migrations/check-lifecycle-e2e.integration.test.ts @@ -99,6 +99,7 @@ function checksForColumn( columnName, many: options.many, memberValues: options.memberValues, + valueObject: undefined, }).map( (candidate) => new CheckConstraint({ diff --git a/packages/3-targets/postgres/2-adapter/test/migrations/native-array-columns.integration.test.ts b/packages/3-targets/postgres/2-adapter/test/migrations/native-array-columns.integration.test.ts index 1e539d5d285f..a28336e11c0e 100644 --- a/packages/3-targets/postgres/2-adapter/test/migrations/native-array-columns.integration.test.ts +++ b/packages/3-targets/postgres/2-adapter/test/migrations/native-array-columns.integration.test.ts @@ -4,7 +4,6 @@ import { APP_SPACE_ID } from '@internal/framework-components/control'; import { UNBOUND_NAMESPACE_ID } from '@internal/framework-components/ir'; import { CheckConstraint, SqlStorage } from '@internal/sql-contract/types'; import { composeCheckWirePrefix, computeCheckContentHash } from '@internal/sql-schema-ir/naming'; - import { parsePostgresListText } from '@internal/target-postgres/control'; import { postgresCreateNamespace, @@ -43,6 +42,7 @@ function declaredArrayElementChecks(): CheckConstraint[] { columnName, many: { elementNullable: false }, memberValues: undefined, + valueObject: undefined, }).map( (candidate) => new CheckConstraint({ diff --git a/packages/3-targets/postgres/2-adapter/test/postgres-codec-registry-composition.test.ts b/packages/3-targets/postgres/2-adapter/test/postgres-codec-registry-composition.test.ts index 79f96088b1d2..f8461a9f768a 100644 --- a/packages/3-targets/postgres/2-adapter/test/postgres-codec-registry-composition.test.ts +++ b/packages/3-targets/postgres/2-adapter/test/postgres-codec-registry-composition.test.ts @@ -130,7 +130,7 @@ function postgresDescriptor( ): AnyPostgresCodecDescriptor { const type = fixtureSqlDataType(codecId, typeText); const template = genericDescriptor(codecId, type); - return postgresCodec(template, { + return postgresCodec(Object.assign(template, { jsonType: 'any' as const }), { dataType: type, jsonProjection(expression: ProjectionExpr): ProjectionExpr { onProjection?.(); @@ -162,7 +162,7 @@ function transformingPostgresDescriptor( return codec; }, }; - return postgresCodec(descriptor, { + return postgresCodec(Object.assign(descriptor, { jsonType: 'any' as const }), { dataType: type, jsonProjection: (expression: ProjectionExpr) => expression, factory: (_descriptor, _type, params) => descriptor.factory(params), diff --git a/packages/3-targets/postgres/2-adapter/test/sql-renderer.cast-policy.test.ts b/packages/3-targets/postgres/2-adapter/test/sql-renderer.cast-policy.test.ts index 285d010b0fdd..ad75c16a6dd6 100644 --- a/packages/3-targets/postgres/2-adapter/test/sql-renderer.cast-policy.test.ts +++ b/packages/3-targets/postgres/2-adapter/test/sql-renderer.cast-policy.test.ts @@ -76,7 +76,7 @@ const dataTypes = createDataTypeLookup([ const verbatimTemplate = genericDescriptor('demo/verbatim@1', verbatimDataType); const verbatimRegistry = buildPostgresCodecDescriptorRegistry([ - postgresCodec(verbatimTemplate, { + postgresCodec(Object.assign(verbatimTemplate, { jsonType: 'any' as const }), { dataType: verbatimDataType, jsonProjection: (expression: ProjectionExpr) => expression, factory: (_descriptor, _type, params) => verbatimTemplate.factory(params), @@ -86,7 +86,7 @@ const verbatimRegistry = buildPostgresCodecDescriptorRegistry([ function descriptorFor(codecId: string, name: string): AnyPostgresCodecDescriptor { const type = fixtureDataType(name); const template = genericDescriptor(codecId, type); - return postgresCodec(template, { + return postgresCodec(Object.assign(template, { jsonType: 'any' as const }), { dataType: type, jsonProjection: (expression: ProjectionExpr) => expression, factory: (_descriptor, _type, params) => template.factory(params), diff --git a/packages/3-targets/postgres/4-codec-testkit/test/value-round-trip.integration.test.ts b/packages/3-targets/postgres/4-codec-testkit/test/value-round-trip.integration.test.ts index c74e0e881217..6ada9700f453 100644 --- a/packages/3-targets/postgres/4-codec-testkit/test/value-round-trip.integration.test.ts +++ b/packages/3-targets/postgres/4-codec-testkit/test/value-round-trip.integration.test.ts @@ -30,6 +30,7 @@ class MarkingTextCodec extends CodecImpl<'test/marking-text@1', readonly [], str } class MarkingTextDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return expression; } @@ -59,6 +60,7 @@ class TextCodec extends CodecImpl<'test/upper-casing-text@1', readonly [], strin /** A text codec whose projection changes the text, so the projected value reads to another value than the row. */ class UpperCasingTextDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'any' as const; protected override jsonProjection(expression: ProjectionExpr): ProjectionExpr { return FunctionCallExpr.of('upper', [expression]); } diff --git a/packages/3-targets/postgres/9-facade/test/fixtures/generated/contract.d.ts b/packages/3-targets/postgres/9-facade/test/fixtures/generated/contract.d.ts index 7a2058a0a1fe..d3d2ec5974d6 100644 --- a/packages/3-targets/postgres/9-facade/test/fixtures/generated/contract.d.ts +++ b/packages/3-targets/postgres/9-facade/test/fixtures/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -773,7 +775,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/packages/9-public/@prisma/orm-family-sql/package.json b/packages/9-public/@prisma/orm-family-sql/package.json index bd090e8572ae..762316a0f735 100644 --- a/packages/9-public/@prisma/orm-family-sql/package.json +++ b/packages/9-public/@prisma/orm-family-sql/package.json @@ -57,6 +57,8 @@ "./contract": "./dist/contract.mjs", "./contract/authored-check-naming": "./dist/contract__authored-check-naming.mjs", "./contract/canonicalization-hooks": "./dist/contract__canonicalization-hooks.mjs", + "./contract/check-expressions": "./dist/contract__check-expressions.mjs", + "./contract/codec-json-type": "./dist/contract__codec-json-type.mjs", "./contract/contract-view": "./dist/contract__contract-view.mjs", "./contract/data-type": "./dist/contract__data-type.mjs", "./contract/data-type-support": "./dist/contract__data-type-support.mjs", diff --git a/packages/9-public/@prisma/orm-framework/package.json b/packages/9-public/@prisma/orm-framework/package.json index 2f7108c73c1c..4a544b69023f 100644 --- a/packages/9-public/@prisma/orm-framework/package.json +++ b/packages/9-public/@prisma/orm-framework/package.json @@ -75,6 +75,7 @@ "./contract/types": "./dist/contract__types.mjs", "./contract/validate-domain": "./dist/contract__validate-domain.mjs", "./contract/validate-once": "./dist/contract__validate-once.mjs", + "./contract/validate-storage": "./dist/contract__validate-storage.mjs", "./contract-authoring": "./dist/contract-authoring.mjs", "./errors": "./dist/errors.mjs", "./errors/control": "./dist/errors__control.mjs", diff --git a/packages/9-public/@prisma/orm-mongo/package.json b/packages/9-public/@prisma/orm-mongo/package.json index 90d4817cfef4..f6d86c350d61 100644 --- a/packages/9-public/@prisma/orm-mongo/package.json +++ b/packages/9-public/@prisma/orm-mongo/package.json @@ -69,6 +69,7 @@ "./contract/types": "./dist/contract__types.mjs", "./contract/validate-domain": "./dist/contract__validate-domain.mjs", "./contract/validate-once": "./dist/contract__validate-once.mjs", + "./contract/validate-storage": "./dist/contract__validate-storage.mjs", "./contract-builder": "./dist/contract-builder.mjs", "./control": "./dist/control.mjs", "./driver": "./dist/driver.mjs", diff --git a/packages/9-public/@prisma/orm-mysql/package.json b/packages/9-public/@prisma/orm-mysql/package.json index 73136a1bc388..329699d3e5a9 100644 --- a/packages/9-public/@prisma/orm-mysql/package.json +++ b/packages/9-public/@prisma/orm-mysql/package.json @@ -70,6 +70,7 @@ "./contract/types": "./dist/contract__types.mjs", "./contract/validate-domain": "./dist/contract__validate-domain.mjs", "./contract/validate-once": "./dist/contract__validate-once.mjs", + "./contract/validate-storage": "./dist/contract__validate-storage.mjs", "./contract-builder": "./dist/contract-builder.mjs", "./control": "./dist/control.mjs", "./driver": "./dist/driver.mjs", @@ -88,6 +89,8 @@ "./family-contract": "./dist/family-contract.mjs", "./family-contract/authored-check-naming": "./dist/family-contract__authored-check-naming.mjs", "./family-contract/canonicalization-hooks": "./dist/family-contract__canonicalization-hooks.mjs", + "./family-contract/check-expressions": "./dist/family-contract__check-expressions.mjs", + "./family-contract/codec-json-type": "./dist/family-contract__codec-json-type.mjs", "./family-contract/contract-view": "./dist/family-contract__contract-view.mjs", "./family-contract/data-type": "./dist/family-contract__data-type.mjs", "./family-contract/data-type-support": "./dist/family-contract__data-type-support.mjs", diff --git a/packages/9-public/@prisma/orm-postgres/package.json b/packages/9-public/@prisma/orm-postgres/package.json index 0aaffc6298eb..ed539b78bcc7 100644 --- a/packages/9-public/@prisma/orm-postgres/package.json +++ b/packages/9-public/@prisma/orm-postgres/package.json @@ -79,6 +79,7 @@ "./contract/types": "./dist/contract__types.mjs", "./contract/validate-domain": "./dist/contract__validate-domain.mjs", "./contract/validate-once": "./dist/contract__validate-once.mjs", + "./contract/validate-storage": "./dist/contract__validate-storage.mjs", "./contract-builder": "./dist/contract-builder.mjs", "./control": "./dist/control.mjs", "./driver": "./dist/driver.mjs", @@ -98,6 +99,8 @@ "./family-contract": "./dist/family-contract.mjs", "./family-contract/authored-check-naming": "./dist/family-contract__authored-check-naming.mjs", "./family-contract/canonicalization-hooks": "./dist/family-contract__canonicalization-hooks.mjs", + "./family-contract/check-expressions": "./dist/family-contract__check-expressions.mjs", + "./family-contract/codec-json-type": "./dist/family-contract__codec-json-type.mjs", "./family-contract/contract-view": "./dist/family-contract__contract-view.mjs", "./family-contract/data-type": "./dist/family-contract__data-type.mjs", "./family-contract/data-type-support": "./dist/family-contract__data-type-support.mjs", diff --git a/packages/9-public/@prisma/orm-sqlite/package.json b/packages/9-public/@prisma/orm-sqlite/package.json index 2629170d01b7..46aed2e49877 100644 --- a/packages/9-public/@prisma/orm-sqlite/package.json +++ b/packages/9-public/@prisma/orm-sqlite/package.json @@ -74,6 +74,7 @@ "./contract/types": "./dist/contract__types.mjs", "./contract/validate-domain": "./dist/contract__validate-domain.mjs", "./contract/validate-once": "./dist/contract__validate-once.mjs", + "./contract/validate-storage": "./dist/contract__validate-storage.mjs", "./contract-builder": "./dist/contract-builder.mjs", "./control": "./dist/control.mjs", "./driver": "./dist/driver.mjs", @@ -92,6 +93,8 @@ "./family-contract": "./dist/family-contract.mjs", "./family-contract/authored-check-naming": "./dist/family-contract__authored-check-naming.mjs", "./family-contract/canonicalization-hooks": "./dist/family-contract__canonicalization-hooks.mjs", + "./family-contract/check-expressions": "./dist/family-contract__check-expressions.mjs", + "./family-contract/codec-json-type": "./dist/family-contract__codec-json-type.mjs", "./family-contract/contract-view": "./dist/family-contract__contract-view.mjs", "./family-contract/data-type": "./dist/family-contract__data-type.mjs", "./family-contract/data-type-support": "./dist/family-contract__data-type-support.mjs", diff --git a/projects/migration-statements/deferred.md b/projects/migration-statements/deferred.md index e7cc4a6a4c60..a25cb832d4b5 100644 --- a/projects/migration-statements/deferred.md +++ b/projects/migration-statements/deferred.md @@ -144,6 +144,12 @@ The re-run advice has a second gap of the same kind, found in the slice 3b PR 1 Slice 3b plans each row rewrite as one plain operation: an `UPDATE` for an enum value rename, a discriminator conversion or a value object key rename, and a `DELETE` for a removed variant's rows. A migration runs in one transaction, so on a large production table the rewrite blocks writes to every row it touches until it commits, and next to DDL such as a check rebuild it blocks the whole table. Nothing batches it or runs it in the background. 3a's `--backfill` and type conversions have the same cost. Will ruled that 3b's goal is that the planner sees these changes; making the rewrites cheap is a later optimization. +## Derived checks are added with a full table read under lock + +**Found:** whole-branch review of the value object storage PR (2026-10-23). + +The planner adds each derived check, value object shape checks included, with a plain `ALTER TABLE … ADD CONSTRAINT … CHECK (…)`. Postgres holds a lock on the table while it reads every row. The check's name holds a hash of its expression, so any change to a value object drops the check and adds it again, and the rows are read again. Adding the check `NOT VALID` and then running `VALIDATE CONSTRAINT` takes a weaker lock while the rows are read. It changes how every derived check is planned, enum checks included, so it belongs with the row rewrite cost above. + ## `--delete billing` is not offered as a next action, and the CLI copies the engine's unused-flag error **Found:** slice 3b PR 1 system-design review (2026-10-21), S04. @@ -230,3 +236,22 @@ In `enum-value-planning.ts`, `namespaceId` is the table's namespace while `enumV **Found:** fixing PR 2's manual QA (enum values), 2026-10-23; corrected after the verification review the same day. An enum `A = "a", B = "b"` becomes `B = "a", A = "b"`: each member keeps its name and takes the other's stored value, and the list of stored values in storage is still `a, b`. The storage hash does not change, so `migration plan` prints "No changes detected" and `db update` plans nothing, on Postgres and SQLite. The rows still hold `a`, which now reads as member `B`. This is a change to the domain part only (which member names which stored value), so it is the same class as TML-3720 (the snapshot and the planner see nothing when only the domain changes). When the same plan also changes something else, the planner does ask, and on Postgres the printed edit, which swaps through a temporary value, can fail against the kept check without changing anything. A single `UPDATE … SET c = CASE c WHEN 'a' THEN 'b' WHEN 'b' THEN 'a' END` would need no temporary value. Resolve together with TML-3720, and consider it in PR 3, which makes more domain facts part of storage. + +## Column value-set references are not checked against storage + +A column's `valueSet: { plane: 'storage', entityKind: 'valueSet', entityName }` is not checked to name an existing `storage.namespaces..entries.valueSet` entry, in either family. A hand-edited or corrupt `contract.json` with a dangling reference passes validation and fails later. PR 3 of the remaining-nouns slice adds the check for value object members only, in `validateStorageValueObjects` (`@internal/contract/validate-storage`), where the column check belongs too. Found in the review of that PR, 2026-10-23. + +## Composite types are not namespaced in SQL PSL + +SQL PSL resolves a composite type by its bare name across all namespaces and writes every value object to the default storage namespace. Two composite types with the same name in different PSL namespaces are refused with `PSL_DUPLICATE_DECLARATION` (before PR 3 of the remaining-nouns slice they silently overwrote each other). Letting each namespace declare its own needs namespace-qualified type references in PSL and writing each value object to its namespace's storage. When that changes, `generate-contract-dts.ts` must also emit one `Output` alias per storage namespace; today it emits one per name, from the first namespace. Found in the review of that PR, 2026-10-23. + +## The SQL ORM cannot write some value object members + +Found in the review of the value object storage PR, 2026-10-23. Both limits are older than that PR. + +- A `BigInt` member of a value object cannot be written: the `pg/jsonb@1` codec throws "Do not know how to serialize a BigInt". The same holds for a `pg/interval@1` member, whose value holds a `bigint`. Their codecs declare the JSON type their value would have: `pg/int8@1` and `pg/unboundedint@1` declare `number`, and `pg/interval@1` declares `object`. +- A `Bytes` member is written as an object of byte indexes, such as `{"0":1,"1":2}`, not as bytes. Its codec declares `object`, the type it is written as today. + +## The SQL TypeScript builder has no value objects + +`defineContract` in `@internal/postgres/contract-builder` cannot declare a value object, so a SQL contract written in TypeScript cannot have one. The main SQL ORM test contract (`test/integration/test/sql-orm-client/fixtures/contract.ts`) used to patch a value object into its built contract; it now stores `User.address` as a plain `jsonb` column, and the value object type tests read a PSL fixture (`fixtures/value-objects`). That contract cannot move to PSL, because PSL cannot write its to-one relation without a foreign key (`Article.reviewer`). diff --git a/projects/migration-statements/manual-qa-reports/2026-10-23-qa-3b-pr3.md b/projects/migration-statements/manual-qa-reports/2026-10-23-qa-3b-pr3.md new file mode 100644 index 000000000000..72c9745ea2c6 --- /dev/null +++ b/projects/migration-statements/manual-qa-reports/2026-10-23-qa-3b-pr3.md @@ -0,0 +1,86 @@ +# Manual QA — slice 3b PR 3, value objects in storage — 2026-10-23 + +Detached checkout `wip/wt-qa` at `22fd7fb6bf`. Method: `projects/migration-statements/manual-qa.md`. Scope: PR 3 of `slices/remaining-nouns/spec.md` (value objects as storage entities, derived checks on Postgres, plans with no operations, `jsonType`, the upgrade script and instructions), read against ADR 309. + +## Summary + +| # | Scenario | Verdict | +| - | -------- | ------- | +| 1 | New Postgres project: emit, plan, migrate, verify, ORM writes, bad raw rows | ✅ | +| 2 | Change the value object with rows in the table | ⚠️ defect (F2, F3) | +| 3 | `db update` for the same changes | ⚠️ defect (F2, F3) | +| 4 | A change with no operations (`@@control(external)`) | ✅ (F5) | +| 5 | `contract print` and `contract infer` | ✅ | +| 6 | Upgrade path, `prisma-8-demo` and `mongo-demo` | ⚠️ defect (F1, F4, F6) | +| 7 | MongoDB validator follows a member change | ✅ (F7) | +| 8 | Error quality | ⚠️ unclear (F3, F5, F8) | + +Tally: 4 works, 3 defect, 1 unclear. No blocker. Every failure I caused left the database unchanged. The checks refused every bad row I tried and accepted every row the ORM wrote. + +## Defects + +1. ⚠️ **F1 (medium) — Following the upgrade instructions leaves a stale `db` ref, and the next `migration plan` writes a second migration with the same endpoints.** Step 1 runs `prisma db sign`, which writes `migrations/app/refs/db.json` (the old demo had none). Step 3's `prisma db migrate` does not advance that ref. Steps: + 1. Old `prisma-8-demo` (merge base `f543fb4173`), old CLI: `prisma db migrate`. + 2. Current CLI: run the script, `prisma db sign --db "$DATABASE_URL"` (`Advanced ref "db" → ecd77e4…`), `prisma contract emit`, `prisma migration plan --name value-object-checks`, `prisma db migrate`. + 3. `prisma migration plan` again, with no change: + ``` + ⚠ The default origin ref 'db' points at ecd77e4…, which already has a migration leading to e1916bd…. Planning from it forks the migration graph; pass --from to choose the origin explicitly. + ✔ Planned 2 operation(s) + migrations/app/20261023T1839_migration + ``` + The new migration adds the same two checks as the applied one. `migration status` then shows two edges `ecd77e4 → e1916bd`, one pending. The same happened on `mongo-demo` with the next real change. The `postgres-value-object-checks` section also says "A project with no `db` ref names the origin … `--from`", which never applies when the steps are followed in order, because step 1 just wrote a ref. The fix belongs in the instructions: `db migrate --advance-ref db` in step 3, or `db sign --no-advance-ref` in step 1 with `--from` in step 3. The fork itself is the older behaviour of `db migrate` and refs. +2. ⚠️ **F2 (medium) — Adding a required member to a value object that rows already hold has no route that keeps the data, and the advice is a dead end.** Postgres, `Address` held by `user.address` (required) and `user.previous` (nullable list), three rows. + 1. Add `country String` to `Address`. `contract emit`, `migration plan --from `: four operations (two check drops, two check adds), no question, no warning. + 2. `prisma db migrate`: + ``` + ✘ [MIGRATION.EXECUTION_FAILED] Operation checkConstraint.user.user_address_shape_5c02601b failed during execution: add check constraint "user_address_shape_5c02601b" + why: check constraint "user_address_shape_5c02601b" of relation "user" is violated by some row. The failed apply changed nothing. + → Fix the issue and re-run `prisma db migrate --to ` + ``` + Checks and marker unchanged, as promised. + 3. Fix the rows first: `UPDATE "public"."user" SET address = address || '{"country":"NO"}'` is refused by the old check `user_address_shape_9a7e984c`, which forbids the unknown key `country`. + So the user can neither fix the rows before the apply nor apply without fixing them. What worked: hand-write a raw operation (an `UPDATE` for each column) in `migration.ts` between the drops and the adds, run `node migration.ts`, `db migrate`. Nothing tells the user this. The same holds for `db update` (scenario 3), where there is no `migration.ts`: the only route was to drop the old checks by hand in SQL, update the rows, then `db update`. Renaming a member hits the same wall; the spec gives rename and delete statements to PR 4, but not adding a required member. +3. ⚠️ **F3 (low) — The failure for a check that rows break does not say which rows or how to find them.** It names the check, which is good. It does not name the column, the count of rows, or the query the upgrade instructions give. `db update` says `Inspect the reported conflict, reconcile schema drift if needed`, which reads as if the schema drifted. `db migrate` says `--to `, a placeholder the user does not need. The docs link uses `error-reference#MIGRATION.EXECUTION_FAILED`, while other errors use `error-reference/CODE`. The advice text is not new in this PR, but derived checks make this failure far more common. +4. ⚠️ **F4 (low) — The upgrade instructions never say when to upgrade the Prisma packages.** They say "Before you upgrade, apply every pending migration … with the release you are upgrading from", then run the script, then `db sign`. A reader can infer that the package upgrade goes between the two, but it is not written. I did it there and it worked. +5. ⚠️ **F6 (low, not this PR) — The old `mongo-demo` migration history does not reach its own contract.** At the merge base, old CLI, empty database: `prisma db migrate` fails with `MIGRATION.SCHEMA_VERIFY_FAILED … missing: posts/index:summary:1; missing: posts/validator` on `20260626T1916_add_posts_indexes`. I used old `db update` to bring the database to the last migration's hash before the upgrade. Not caused by this PR. + +## Other findings + +6. **F5 (minor) — The no-operation summary uses the internal kind name.** `What changed: valueObject "public.Home".` A user writes `type Home`, so `value object "public.Home"` would read better. `db update` on the same change says `✔ Database already matches contract` and `(no operations)` while it moves the marker, and does not say what changed. +7. **F7 (low, not this PR) — MongoDB: making a member required is planned and applied without a question, and existing documents that lack it stay.** `region String?` → `region String` with a document that has no `region`: one `collMod`, applied, `db verify --strict` clean. MongoDB does not check existing documents on `collMod`, so the stored document no longer fits the validator. Same as before this PR. +8. **F8 (minor) — ADR 309 names the check `

__shape`.** The built name is `
__shape_`, as the upgrade instructions say. The ADR's "Enforcement on Postgres" paragraph should say the same. +9. **F9 (not this PR) — The SQL ORM cannot create a variant through the variant's own model.** `orm.Bug.create(...)` (single-table) fails with `null value in column "type"`; `orm.Feature.create(...)` (own table) fails with `ORM.COLUMN_UNKNOWN Unknown column "title" in table "feature"`. `orm.Task.variant('bug').create(...)` works. The merge-base code gives the same two errors. +10. **F10 (not checked against main) — A `BigInt` member cannot be written through the ORM.** `RUNTIME.ENCODE_FAILED … Do not know how to serialize a BigInt`. The check expects a JSON number for `pg/int8@1`, and a plain number is accepted. A `Bytes` member is written as `{"0":1,"1":2,"2":3}` and the check expects an object, so they agree. + +## Scenario notes + +1. **New Postgres project.** PSL: enum `Kind` (`@@type("pg/text@1")`), `Geo { lat Float, lng Float }`, `Address { street, city, tags String[], kind Kind, geo Geo, note String? }`, `User.address Address`, `User.previous Address[]?`, base `Task` with `Bug` (single-table) and `Feature` (`@@map("feature")`). + - `contract emit`: `domain.namespaces.public` has only `enum` and `models`. `storage.namespaces.public.entries` has `table`, `valueObject` (`Address`, `Geo`) and `valueSet` (`Kind`, `Task.type` = `["bug","feature"]`). Domain fields and the nested `geo` member are `{ kind: 'valueObject', plane: 'storage', namespaceId: 'public', entityKind: 'valueObject', entityName }`. The enum member carries `valueSet` to `Kind`. `task.type` names `Task.type`. + - Checks: `task_type_check_cda3f5e7` = `"type" IN ('bug', 'feature')`, on `task` only. `user_address_shape_a878857d` (required, no `IS NULL`) and `user_previous_shape_61b5dc0d` (`"previous" IS NULL OR …`, array of objects). Nested `Geo` and the enum's values are inlined. + - `migration plan --name init` (4 operations, DDL preview prints each check), `db migrate`, `db verify` and `db verify --strict`: clean. A second plan: no changes. + - ORM: three users with `note` null, absent and set, `tags` empty and full, `previous` null, `[]` and two elements; read back the same. `Task.variant('bug')` and `Task.variant('feature')` creates worked (see F9). + - Raw SQL, each refused naming the check: extra key, missing key, wrong type (`street: 5`), bad enum (`office`), JSON `null` in the required column, bad nested type, extra nested key, `tags: [1]`, `tags: "a"`, `note: 3`, an array in the scalar column, a bare string; on `previous`: an object, an element missing keys, `[null]`. Accepted: `note: null` with JSON `null` in `previous`. SQL `NULL` in the required column hits `NOT NULL`. `type = 'epic'` refused by `task_type_check_cda3f5e7`. An ORM create with an extra key reaches the database and is refused by the check. +2. **Change the value object.** Adding `zip String?` with rows: two drops, two adds, applied, verify clean, rows kept. Adding `country String`: see F2. The bad-rows query from the instructions, with the expression copied from the DDL preview between `CHECK (` and `)`, returned exactly the rows lacking `country` (4). With a hand-written backfill operation in `migration.ts`, the migration applied, every row and list element holds `country`, and `db verify --strict` is clean. +3. **`db update`.** First apply, adding `zip`, and adding a variant (`Epic`, single-table: `task_type_check` dropped and re-added, `epic` accepted) all worked. `db update --dry-run` afterwards: `No operations`. Adding `country` with rows: same failure as F2, nothing changed. Removing `Bug` with a `bug` row: asks only about `Bug.severity`; with `--delete Bug.severity` the new `task_type_check` fails and nothing changes. Variant removal is PR 4. +4. **No operations.** `auth_profiles` created by hand, `@@control(external)`, holding `Home`. Adding `zip String?`: + ``` + ⚠ Planned 0 operation(s). The storage the contract describes changed, but the database needs no operation for it, so the migration has none. What changed: valueObject "public.Home". + ``` + Exit 0. `ops.json` is `[]`. `--json`: `noOp: false`, `operations: []`, `changedStorageEntities: [{ namespaceId: 'public', plane: 'storage', entityKind: 'valueObject', entityName: 'Home' }]`, the same summary. `db migrate`: `Applied 1 migration(s) (0 operation(s))`, `(no operations)`, marker at the new hash. Next plan `No changes detected`, nothing written. `db verify --strict` and `migration status` clean. No check was added to the external table; a row with any JSON was accepted. `db update` on a further change: dry run `No operations`, apply moves the marker (F5). SQLite (no check renderer): a value object change plans 0 operations with the same summary (`valueObject "Home"`, no `namespaceId` in JSON, no `__unbound__`), and `db migrate` moves the marker. +5. **Print and infer.** `contract print --output printed.prisma`, emitted in a fresh project: same `storageHash`, and `domain` and `storage` byte for byte equal. The print keeps `@@discriminator(type)`, `@@base(Task, "bug")` without `@@map`, and `@@base(Task, "feature")` with `@@map("feature")`; value objects print inside `namespace public`. `contract infer`: the derived checks appear as `@@check(expression: sql\`…\`, map: "task_type_check_cda3f5e7")` and the two `user_*_shape_*` names, with the columns as `Jsonb`, as the Data Contract docs say. The inferred PSL emitted and `db verify --schema-only` passed against the same database. +6. **Upgrade path.** Built the merge base `f543fb4173` in a second worktree and used its CLI for the "before" state. + - `prisma-8-demo`, PGlite: old `db migrate` applied all six migrations. Seeded a user with an extra address key and a task with `type = 'legacy'` by SQL. + - Script: `Rewrote 12 files and renamed 7 snapshot directories`, seven ` -> ` lines. Run again: `nothing changed`, exit 0. Snapshots hold `valueObject` and the `Task.type` value set. + - Step 1 `db sign --db`: `signed ecd77e4… (was 0ce1b6c…)`. Step 2 `contract emit`. Step 3 `migration plan`: two operations (`task_type_check_cda3f5e7`, `user_address_shape_69432dc1`), destination `e1916bd…`, the same hash as the branch's own demo. The instructions' two queries found exactly the two seeded rows. Their `UPDATE … SET address = address - 'extra'` and a `DELETE` of the legacy task fixed them. Before the fix `db migrate` failed and changed nothing; after it, applied, `db verify --strict` clean. Then F1. + - Database behind the last migration (old CLI `db migrate --to 20260917T0818_add_post_expires_at`): `db sign --db` failed with `CONTRACT.SCHEMA_VERIFICATION_FAILED`, as the instructions say. `db sign --contract c61ab00… --no-advance-ref`, `db migrate --db`, `db verify --strict --db`: all worked, ref untouched. + - Current CLI on an un-upgraded project: `CONTRACT.VALIDATION_FAILED` explaining the move, naming `value-objects-in-storage.ts`, and linking the app upgrade instructions. Clear. + - `mongo-demo`, mongodb-memory-server: see F6 for the before state. Script: 9 files, 3 snapshot directories, and `migrations/app/refs/db.json` rewritten to the new hash. `db sign`, `contract emit`, `migration plan`: `No changes detected`, as step 3 says for MongoDB. Collection validators byte for byte unchanged. `src/contract.json`, `contract.d.ts` and every snapshot equal the branch's own upgraded `mongo-demo`. +7. **MongoDB.** On the upgraded `mongo-demo`, adding `region String?` to `Address`: `Update validator on users (changed: address)`, applied, the live validator lists `region` as `["null","string"]`, and a document with `region: 5` is refused (`Document failed validation`). Making it required: one more `collMod`, applied, `required` includes `region` (F7). Verify clean, next plan empty. +8. **Error quality.** New or changed messages hit: `CONTRACT.VALIDATION_FAILED` for an old contract (clear); the 0-operation summary (F5); `MIGRATION.EXECUTION_FAILED` for a derived check (F3). No message named `__unbound__` or another internal id. `PSL_ENUM_CANNOT_INFER_TYPE` for an enum without `@@type` was clear. I could not reach the missing-`jsonType` error: every shipped codec I tried declares one (`pg/*`, pgvector `Vector` as `array`, `Json` as any value). + +## Setup + +- `mise exec -- pnpm install` and `pnpm build` in `wip/wt-qa` (Node v24.22.0 from mise). The merge base was checked out with `git worktree add --detach` at `wip/wt-qa/wip/old` and built the same way. +- CLI `packages/1-framework/3-tooling/cli/dist/bin.mjs` through a wrapper, `--no-interactive`. ORM through `@internal/postgres/runtime` with `contractFromJson`. +- Projects under `wip/wt-qa/wip/qa/`, `node_modules` linked to `test/integration/node_modules` (or each demo's own), configs from the journey fixtures. Postgres: six `@prisma/dev` (PGlite) servers from `createDevDatabase`. MongoDB: one `MongoMemoryReplSet`. SQLite: one file. +- All servers stopped afterwards. `git status --short` in `wip/wt-qa` clean at start and end. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/10-value-object-storage-shape.md b/projects/migration-statements/slices/remaining-nouns/dispatches/10-value-object-storage-shape.md new file mode 100644 index 000000000000..4e5ca5e5e491 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/10-value-object-storage-shape.md @@ -0,0 +1,43 @@ +# Dispatch 10 — value objects in the storage plane: contract shape and validators + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** `docs/architecture docs/adrs/ADR 309 - Value objects are storage entities, and a discriminator column names a value set.md` · **Grounding:** `wip/pr3-grounding.md` § A, B · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +The contract types and validators accept a value object as a storage entity, at `storage.namespaces..entries.valueObject.`, in both families, with the same definition it has in the domain plane today. A domain field that holds a value object refers to it by a storage coordinate, and so does a value object member that holds another. Validation checks the reference across the two planes. The domain plane no longer has a `valueObjects` key. Nothing writes the new shape yet; authoring and emit change in dispatches 12 and 13, so this dispatch keeps the old fixtures passing only where a test needs them and says which tests it left for dispatch 12. + +## What the grounding found + +- Nothing in the domain plane refers to storage today. An enum field's `valueSet` refers to the domain enum (`value-set-ref.ts`). This reference is the first from domain to storage, which ADR 221 allows (a domain entity may refer to a storage entity, never the reverse). +- Domain validation (`validate-domain.ts`) sees only the domain plane and needs read access to storage for this check. +- No existing type fits the reference as it is. `EntityCoordinate` lives in framework-components, which foundation cannot import. Decide the reference type from the code: a storage coordinate in foundation, consistent with the existing cross-plane references (`plane`, `namespaceId`, `entityKind`, `entityName`), reused rather than duplicated. Report the name and shape you chose, and why. + +## Do + +1. Read ADR 221, ADR 309, ADR 178, `domain-types.ts`, `value-set-ref.ts`, `validate-domain.ts`, the SQL and Mongo storage types and their validators, the JSON schema `data-contract-sql-v1.json`, and the domain accessors (`domainValueObjectsAtDefaultNamespace` and friends). +2. **Tests first, red before each change:** a contract with a value object in storage and a field referring to it validates; a reference to a missing storage value object is refused with a message naming the field and the coordinate; a nested reference resolves; a value object that refers to itself validates; the old domain `valueObjects` key is refused; both families. +3. Implement the types, the validators and the accessors. Keep every name from the existing vocabulary (`valueObject` is the entity kind, as ADR 309 says). Do not invent new nouns; if a new name is unavoidable, say so in the report with candidates. +4. Update the JSON schema. + +## Do not + +- Do not change authoring, emit, runtime or the MongoDB validator derivation; those are dispatches 12 to 14. +- Do not re-emit fixtures; that is dispatch 16. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Only named files inside `test/integration` with `pnpm test `. +- Use the shell's Node and `pnpm`. Never `npx`. +- No comments where the code can say it; no bare `as` casts in production code; no `any`; arktype, not zod; no file extensions in imports; no backwards-compatibility exports; framework packages name no family or target. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. +- Save slow command output under `wip/` and read the file. The machine may be loaded: run tests by file where you can. + +## Done when + +- Typecheck of the touched packages; lint and `test --coverage` in the contract foundation, framework-components, SQL contract and Mongo contract packages; `lint:deps`; `lint:ratchets`. +- The report lists every test outside those packages that now fails because nothing writes the new shape yet, for dispatch 12. + +## Report + +Plain English, short sentences: the reference type and why, the validation rules and messages, the test that was red first for each change, the gates, the tests left failing for dispatch 12, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/11-plans-with-no-operations.md b/projects/migration-statements/slices/remaining-nouns/dispatches/11-plans-with-no-operations.md new file mode 100644 index 000000000000..f29c6210bf70 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/11-plans-with-no-operations.md @@ -0,0 +1,43 @@ +# Dispatch 11 — a plan with no operations is written and applied + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309, "Why every change is an operation" · **Grounding:** `wip/pr3-grounding.md` § F · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +When the storage hash changes but the planner produces no operation, `migration plan` writes the package with no operations instead of refusing, and says so, naming what changed. `db migrate` applies it by moving the marker to the new hash. `db update` does the same. This holds on Postgres, SQLite and MongoDB. Will ruled on 2026-10-21 that migrations are expected to have operations, but edge cases will happen, and the system must handle them, not refuse them. + +## What the grounding found + +- `migration plan` refuses at `migration-plan.ts` (around line 215, `errorPlanProducedNoOperations`, guarded by `noOperationsExpected`). +- The Postgres runner already moves the marker for a migration with no operations between two different hashes. The SQLite and MongoDB runners and `db update` were not checked. +- The integrity check refuses a migration whose start and end hash are equal unless it has a `data` operation (`check-integrity.ts`); that rule stays, since this dispatch is about two different hashes. + +## Do + +1. **Tests first, red before each change:** `migration plan` with a changed storage hash and no operations writes the package (`ops.json` `[]`, `migration.json` with both hashes) and prints that the plan has no operations and which storage entities changed; `db migrate` applies it and the marker holds the new hash, on PGlite, SQLite and mongodb-memory-server; `db update` does the same; `db verify` is clean afterwards. To make a storage change with no operation today, use a change the planner does not turn into DDL on that target (find one per target from the code, for example a SQLite value-set change); say which you used. +2. Remove the refusal for this case. Keep `errorPlanProducedNoOperations` only if another case still needs it; if none does, delete it and its error-reference entry. +3. Name what changed: compare the two contracts' storage and list the entity coordinates that differ, in the message and the `--json` output. Reuse an existing comparison if one exists. +4. Docs: Migration System (the `migration plan` emission cases table and the no-operations rule), the error reference, the CLI README. + +## Do not + +- Do not change how the planner plans anything else. +- Do not touch value objects or discriminators; that is the rest of PR 3. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Only named files inside `test/integration` with `pnpm test `; journeys with `pnpm test:journeys `. +- Use the shell's Node and `pnpm`. Never `npx`. +- No comments where the code can say it; no bare `as` casts in production code; no file extensions in imports; framework packages name no family or target. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. +- Save slow command output under `wip/` and read the file. The machine may be loaded. + +## Done when + +- Typecheck; lint and `test --coverage` in the CLI, the migration package, and the Postgres, SQLite and MongoDB targets; `lint:deps`; `lint:ratchets`; `check:error-reference`. +- Every integration file that mentions `errorPlanProducedNoOperations`, `PLAN_PRODUCED_NO_OPERATIONS` or "no operations", by name. + +## Report + +Plain English, short sentences: the change per command and target, the message and JSON shape, the storage change used per target in tests, the test that was red first for each change, the gates, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/12-authoring-writes-storage-value-objects.md b/projects/migration-statements/slices/remaining-nouns/dispatches/12-authoring-writes-storage-value-objects.md new file mode 100644 index 000000000000..1cfd011c9f3d --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/12-authoring-writes-storage-value-objects.md @@ -0,0 +1,50 @@ +# Dispatch 12 — authoring writes value objects and discriminator value sets into storage + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Grounding:** `wip/pr3-grounding.md` § A (SQL family, Mongo family), C, E · **Builds on:** dispatch 10 (`d607bde854`, `1792de2a67`, `965c0cf100`) · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +Every authoring path writes the dispatch 10 shape, and the storage hash covers it: + +- SQL PSL and the SQL TypeScript build write each value object to `storage.namespaces..entries.valueObject.`, and each field that holds one refers to it by the storage coordinate. A value object member typed by an enum refers to the storage value set. +- Mongo PSL, the Mongo TypeScript builder and the Prisma 6 reader do the same through the one Mongo builder. `build-mongo-storage.ts` hashes `valueObject` entries. +- SQL PSL builds the discriminator value set before the hash. The value set is `storage.namespaces..entries.valueSet..`, holding the variant values, and the discriminator column refers to it. This needs the discriminator and the variant values in the build definition, so `resolvePolymorphism` no longer patches them in after the hash (grounding risk 2). +- An enum-typed discriminator keeps the enum's value set (plan decision 2); no second value set is written. +- An enum whose name equals a discriminator's `.` key is refused, naming both and saying to rename the enum. Nothing else about enum names changes (plan decision 3). +- The MongoDB validator derivation reads value objects from storage. Its output does not change. + +The Postgres check for the discriminator and value object columns is dispatch 15, not this one. + +## Do + +1. **Tests first, red before each change:** + - SQL PSL: a composite type lands in storage with the coordinate on the field; nested and list members; an enum member refers to the storage value set; the storage hash changes when a member changes. + - SQL PSL: a polymorphic model writes the `.` value set and the column refers to it; adding a variant changes the hash; an enum-typed discriminator keeps the enum's value set. + - SQL: the clash refusal through `enumType()`. + - Mongo: TypeScript builder, PSL and Prisma 6 each write storage value objects; the hash changes with a member; the derived collection validator is unchanged for an existing fixture. +2. Implement. Reuse the names dispatch 10 chose. Do not invent nouns; if one is unavoidable, list it in the report with candidates. +3. Move `@repo/test-utils` `createContract` and `applicationDomainOf`'s `valueObjects` option to storage. +4. Fix the source type errors dispatch 10 left in `sql-contract-ts`, `sql-contract-psl`, `mongo-contract-ts`, `mongo-contract-psl` and `mongo-contract-prisma6`. + +## Do not + +- Do not change the emitter or `contract.d.ts` (dispatch 13), runtimes, the CLI resolver or `contract print` (dispatch 14), or Postgres checks (dispatch 15). +- Do not re-emit committed fixtures or examples (dispatch 16). Tests in other packages that load committed fixtures may stay red; list them. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Only named files inside `test/integration` with `pnpm test `. +- Use `mise exec --` for node and pnpm. Never `npx`. +- No comments where the code can say it; no bare `as` casts in production code; no `any`; arktype, not zod; no file extensions in imports; no backwards-compatibility exports; framework packages name no family or target. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. +- Save slow command output under `wip/` and read the file. Run tests by file where you can. + +## Done when + +- Typecheck, lint and `test --coverage` pass in every package this dispatch touches; `lint:deps`; `lint:ratchets`. +- The report lists what is still red outside those packages and which dispatch owns it. + +## Report + +Plain English, short sentences: what each authoring path now writes, how the discriminator facts reach the build, the clash message, the test that was red first for each change, the checks, what is still red, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/13-emit-and-types-read-storage.md b/projects/migration-statements/slices/remaining-nouns/dispatches/13-emit-and-types-read-storage.md new file mode 100644 index 000000000000..b1843dcdd616 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/13-emit-and-types-read-storage.md @@ -0,0 +1,41 @@ +# Dispatch 13 — `contract.d.ts` reads value objects from storage + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Grounding:** `wip/pr3-grounding.md` § A (foundation `field-value-types.ts`, framework emitter, SQL and Mongo emitters), C · **Builds on:** dispatch 10 · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +The emitted `contract.d.ts` describes value objects in the storage plane, and field types resolve a value object through its storage coordinate: + +- The framework emitter renders a value object field type as its coordinate, not `{ kind: "valueObject"; name }`. It drops the domain `valueObjects` line and writes value objects from storage. +- The SQL and Mongo emitters write `entries.valueObject` in the storage namespaces type. +- `FieldTypesFromDomain` (foundation `field-value-types.ts`) finds value objects in the storage namespaces, matched by `namespaceId` and `entityName`. The new type parameter goes last and has a default, so a contract without value objects emits the same `contract.d.ts` text as today (grounding C: 329 committed files otherwise change). +- Whether the top-level flat `valueObjects` block in `contract.d.ts` stays: decide from what reads it, and say why in the report. + +## Do + +1. **Tests first, red before each change:** emitter golden or snapshot tests for a contract with nested, list and enum-member value objects in both families; a type test that a field holding a value object gets the member types, including a nested one and a self-referring one; a contract without value objects emits unchanged text. +2. Implement in foundation, the framework emitter and both family emitters. +3. Fix the source type errors dispatch 10 left in `emitter`. + +## Do not + +- Do not change authoring (dispatch 12), runtimes, the CLI or `contract print` (dispatch 14), or Postgres checks (dispatch 15). +- Do not re-emit committed fixtures or examples (dispatch 16). + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Only named files inside `test/integration` with `pnpm test `. +- Use `mise exec --` for node and pnpm. Never `npx`. +- No comments where the code can say it; no bare `as` casts in production code; no `any`; no file extensions in imports; no backwards-compatibility exports; framework packages name no family or target. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. +- Save slow command output under `wip/` and read the file. + +## Done when + +- Typecheck, lint and `test --coverage` pass in foundation, the framework emitter and both family emitters; `lint:deps`; `lint:ratchets`. +- Emitting one SQL and one Mongo fixture without value objects gives byte-identical `contract.d.ts`. + +## Report + +Plain English, short sentences: the new type parameter and its default, the rendered field type, the decision on the flat block, the test that was red first for each change, the checks, what is still red, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/14-runtime-and-tools-read-storage.md b/projects/migration-statements/slices/remaining-nouns/dispatches/14-runtime-and-tools-read-storage.md new file mode 100644 index 000000000000..6c3b9378495b --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/14-runtime-and-tools-read-storage.md @@ -0,0 +1,40 @@ +# Dispatch 14 — runtimes and tools read value objects through the field's storage reference + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Grounding:** `wip/pr3-grounding.md` § A (CLI, Postgres target, Mongo query builders) · **Builds on:** dispatches 12 and 13 · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +Nothing reads `domain.namespaces..valueObjects`, the removed accessor `domainValueObjectsAtDefaultNamespace`, or the old field type `{ kind: 'valueObject', name }`. Each reader finds a value object through the field's storage coordinate, with `valueObjectAt` from `@internal/contract`: + +- The Mongo ORM (`mongo-orm`: `collection.ts`, `compile.ts`, `field-accessor.ts`) and the Mongo query builder (`builder.ts`, `resolve-path.ts`, `result-shape.ts`), at run time and at type level. Remove the dead `MergedContractValueObjects` fallback that read the dropped flat `valueObjects` block of `contract.d.ts`. +- The CLI statement resolver (`resolve-statements.ts`, `hasValueObject`): it still refuses a statement that names a value object (PR 4 replaces that), but finds value objects in storage. Fix the test type error at `test/control-api/statements/statement-fixtures.ts:68`. +- `contract print` in the Postgres target (`psl-print/domain-types.ts`, `scalar-fields.ts`, `refusals.ts`): prints `type` blocks from storage value objects. The print round trip of a contract with nested, list and enum-member value objects must be lossless. +- `contract infer` produces no value objects; no change, unless a type error says otherwise. +- `db verify` compares tables and checks by name; confirm it needs no change. +- The SQL ORM has no run-time value object reader; confirm, and fix only what its type tests need. + +## Do + +1. **Tests first, red before each change:** a Mongo ORM read and write of a nested and a list value object through `mongodb-memory-server`; a query builder path into a value object member; the CLI refusal of a statement naming a value object, now found in storage; the Postgres print round trip. +2. Implement. Grep the whole repo, both families, for `valueObjects`, `domainValueObjectsAtDefaultNamespace` and `kind: 'valueObject'` with `name`, and leave none outside committed fixtures and snapshots (dispatch 16). + +## Do not + +- Do not change authoring, emit or derived checks (dispatch 15). Do not re-emit fixtures or examples (dispatch 16). Leave the derived-check parts of `psl-print/model-attributes.ts` and `psl-infer` to dispatch 15. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Only named files inside `test/integration` with `pnpm test `. +- Use `mise exec --` for node and pnpm. Never `npx`. Do not write to `/tmp` or any system temp directory; save slow output under `wip/` and read the file. +- No comments where the code can say it; no bare `as` casts in production code; no `any`; no file extensions in imports; no backwards-compatibility exports; framework packages name no family or target. No invented nouns. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. + +## Done when + +- Typecheck, lint and `test --coverage` pass in `mongo-orm`, `mongo-query-builder`, `target-mongo`, `target-postgres`, the CLI, and every other package this dispatch touches; `lint:deps`; `lint:ratchets`. +- The report lists what is still red and why (committed fixtures for dispatch 16). + +## Report + +Plain English, short sentences: each reader and how it now finds value objects, the grep result, the test that was red first for each change, the checks, what is still red, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/15-postgres-derived-checks.md b/projects/migration-statements/slices/remaining-nouns/dispatches/15-postgres-derived-checks.md new file mode 100644 index 000000000000..f1dc29ec37b3 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/15-postgres-derived-checks.md @@ -0,0 +1,49 @@ +# Dispatch 15 — Postgres derives a check for the discriminator column and for each value object column + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 decisions 2 and 3, and the plan's decisions on the `jsonpath` predicate and on value objects that contain themselves · **Grounding:** `wip/pr3-grounding.md` § D · **Builds on:** dispatch 12 · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +On a managed Postgres table: + +- **Discriminator column.** A `membership` check with the values of the `.` value set, rendered and named as an enum's membership check is. An enum-typed discriminator keeps only the enum's check (plan decision 2). +- **Value object column.** A new check kind whose name differs from the membership check on the same column (add it to `CHECK_KIND_SUFFIX` in `schema-ir/src/naming.ts`; pick the suffix from existing vocabulary and say why). Its expression is one strict `jsonpath` predicate that must hold, written so a NULL result fails: `"col" IS NULL OR jsonb_path_match("col", 'strict …') IS TRUE` for a nullable column, without the `IS NULL` arm for a required one. The predicate states, for each member, what the SQL ORM's JSON can hold: + - the column is an object (or, for a list column, an array of objects); + - each required member is present; a nullable member may be absent or JSON `null`; + - no other key; + - each scalar member's JSON type, from a codec-to-JSON-type table in the Postgres target (text, timestamps and UUIDs are `string`; integers and floats are `number`; booleans are `boolean`; JSON members are anything). For a codec the table does not know, check presence only, and list those codecs in the report; + - lists, with element nullability; + - enum members: the storage value set's values; + - nested value objects, inlined. A value object that contains itself is checked in full down to where the cycle starts, and any object is accepted below, as the MongoDB validator does. +- The family resolves nested value objects from storage and hands the target a resolved definition, so the target needs no contract access. Extend the renderer input type in both places it is declared (`target-authoring-hooks.ts` and `check-expressions.ts`), or better, declare it once where both can import it. +- Non-managed tables get none of these checks, as for enum checks today (`stripDerivedChecksFromNonManagedTables` picks up a new kind through `CHECK_KIND_SUFFIX`). +- `contract print` does not print the new checks as `@@check` (`psl-print/model-attributes.ts`), so the print round trip stays lossless. `contract infer` on a live table with such a check prints it as an authored `@@check`, since infer produces no value objects; say so in the Data Contract docs or the infer docs. +- Check whether the SQL ORM writes SQL `NULL` or JSON `null` for an absent nullable value object column, and make the predicate accept both for a nullable column and neither for a required one. + +## Do + +1. **Tests first, red before each change, on PGlite:** for each shape (flat, nullable member, list member with and without nullable elements, enum member, nested, list of nested, nullable column, list column, a value object that contains itself), a row the ORM writes is accepted and a row with a missing key, an extra key, a wrong JSON type or a value outside an enum is refused. The discriminator check refuses a value no variant has. Unit tests for the rendered SQL text and the check names. +2. A new SQL fixture with nested and list members for these tests (`test/integration/test/sql-orm-client/fixtures/` or next to the Postgres target tests, whichever the existing derived-check tests use). +3. Implement. +4. Planner and verify: confirm `db update` from an empty database creates the checks, and `db verify` is clean afterwards, on PGlite. + +## Do not + +- Do not re-emit committed fixtures or examples, and do not change the example migrations (dispatch 16). +- Do not change MySQL or SQLite: they derive no checks today. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Only named files inside `test/integration` with `pnpm test `. +- Use `mise exec --` for node and pnpm. Never `npx`. Do not write to `/tmp` or any system temp directory; save slow output under `wip/` and read the file. +- No comments where the code can say it; no bare `as` casts in production code; no `any`; no file extensions in imports; no backwards-compatibility exports; framework packages name no family or target; do not branch on target in family code. No invented nouns; a new name goes in the report with candidates. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. + +## Done when + +- Typecheck, lint and `test --coverage` pass in `target-postgres`, `sql-contract-ts`, `schema-ir`, and every other package this dispatch touches; `lint:deps`; `lint:ratchets`; `fixtures:check` is expected to fail until dispatch 16, so list what it would change. + +## Report + +Plain English, short sentences: the check kind and suffix, an example predicate for a nested value object, the codec table and the codecs it does not know, the NULL finding, the test that was red first for each change, the checks, what is still red, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/16-re-emit-migrations-upgrade-docs.md b/projects/migration-statements/slices/remaining-nouns/dispatches/16-re-emit-migrations-upgrade-docs.md new file mode 100644 index 000000000000..6c31d6e2028b --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/16-re-emit-migrations-upgrade-docs.md @@ -0,0 +1,87 @@ +# Dispatch 16 — re-emit, example migrations, the upgrade script, and docs + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Grounding:** `wip/pr3-grounding.md` § C and risks 6–8 · **Builds on:** dispatches 11, 14 and 15, and the merge of main (`4aaa36d9bc`) · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +The whole repository is on the new contract shape, users can move their projects to it, and the docs describe it. + +### 1. Re-emit + +- Every committed `contract.json`, `contract.d.ts` and `expected.contract.json` is re-emitted with the repository's own scripts (`pnpm fixtures:emit`), and `pnpm fixtures:check` is clean afterwards. +- Generated test fixtures are regenerated by their generator, never by hand. In particular, `packages/2-mongo-family/3-tooling/emitter/test/generate-orm-fixture.ts` writes both `mongo-contract/test/fixtures/orm-contract.d.ts` and `9-family/test/fixtures/orm-contract.d.ts`. A dispatch 13 hand edit put the first copy out of step with the second. Also regenerate `mongo-orm/test/fixtures/stored-names-contract.*`. +- Hand-written test contracts with `valueObjects` in the domain (grounding C: about 77 test files) move to storage. +- These tests are red today and must be green afterwards: + - every sql-orm-client test that loads the generated fixtures; + - mongo-orm `collection.stored-names.test.ts` and `stored-names.test-d.ts`; + - target-postgres `full-text-index-contract-load.test.ts`; + - family-mongo `mongo-contract-view` and `mongo-migration`; + - the `authored-contract-roundtrip` cases for value objects, polymorphism and "refuses a to-one relation"; + - the `retail-store` build and the typecheck of every example. + +### 2. Example migrations + +- Each `examples/prisma-8-demo` migration that introduces a value object or a variant gains the operations that create the new checks (plan decision 7; Will, 2026-10-23). That means the value object check, the discriminator membership check, and the discriminator value set. +- `scripts/regen-example-migrations.mjs` re-serializes `ops.json` from `migration.ts`, so the operations go into `migration.ts`. Write them as a user would by hand, with the same factory calls the planner renders. +- Check on PGlite: `db migrate` runs the whole chain from empty, and then `db verify` with strict schema verification is clean. +- Do the same for every other example with a Postgres migration chain that holds value objects or variants. The Mongo examples (`mongo-demo`, `retail-store`) need no new operations, since Mongo gets no new checks; confirm with `db verify` on mongodb-memory-server. + +### 3. Upgrade instructions and script + +- Add fragments for the app and for extensions, following the `record-upgrade-instructions` skill (`skills-contrib/record-upgrade-instructions/SKILL.md`). The precedent is the rc.14 to rc.15 `data-type-in-contract` fragment and script under `skills/prisma-8/upgrading/{app,extension}/upgrades/8.0.0-rc.14-to-8.0.0-rc.15/`. Use the upgrade folder for the current release line; the skill says how to name it. +- **The app script** rewrites a user's committed contract snapshots into the new shape. That changes their storage hashes. So the script must also rename each snapshot directory and update every manifest and `migration.json` that names it, keeping the migration graph whole. If that cannot be done safely, it must leave the old snapshots readable instead. Decide from the precedent, and say which you chose and why. +- Run the script on a copy of a project with a real migration history, inside `wip/`. A good candidate is the `prisma-8-demo` chain as it was at `origin/main`. Afterwards, `migration status` and `db migrate` on a fresh PGlite database must work. Commit a test for the script if the precedent has one. +- **The app fragment says:** + - Re-emit the contract. An old `contract.json` is refused with the re-emit message. + - Run the script for the committed snapshots. + - `ValueObjectTypesFromDomain`, which was public through `@prisma/orm-mongo/contract/types`, is now `ValueObjectTypesFromStorage`. A committed `contract.d.ts` that imports it compiles again once re-emitted. + - On Postgres, the next migration adds a check to every value object column and every discriminator column of a managed table. Rows that do not fit fail the apply. That includes rows with extra keys, which the SQL ORM never refused. Say how to find such rows first, with a query using the same predicate. +- **The extension fragment says:** + - `contractModelToMongoResultShape` and `contractFieldToMongoFieldShape` take `storage` instead of `valueObjects`. + - `compileMongoQuery`'s fifth argument is the storage. + - `MongoValueObjects` is removed. + - The removed Postgres exports `PostgresCheckKind`, `PostgresCheckExpressionInput` and `PostgresCheckExpressionCandidate` are replaced by `CheckExpressionInput` and `CheckExpressionCandidate` from the family's check expression module. + - This replaces the advice in the rc.14 release notes (`docs/releases/v8.0.0-rc.14.md:65-76`, `CHANGELOG.md:887-898`). +- `check:upgrade-coverage --mode pr --prev $(git merge-base HEAD origin/main) --head HEAD` passes. Dispatch 15 saw it fail on diffs in `examples/prisma-8-demo-mysql` that the dispatch did not touch. Find out why, and fix it or explain it. + +### 4. Docs + +- **ADR 309:** check that it matches what was built. Notes on what changed from its first text, for example the jsonpath predicate and the discriminator value set on SQL only, go in the ADR's own words, with no ticket or dispatch ids. +- **ADR 172 and ADR 178:** add a short note where they say value objects live in the domain plane. +- **Data Contract subsystem doc and MongoDB Family doc:** update them for the new shape. +- **Contract Emitter & Types subsystem doc**, around lines 262 and 272: + - Remove the domain `valueObjects` line and `ValueObjectTypesFromDomain`. + - Describe `ValueObjectTypesFromStorage`, `entries.valueObject`, and the optional sixth parameter of `FieldTypesFromDomain`. +- **The prisma-8 skill** (`skills/prisma-8/`): update its references to value objects. +- **READMEs** of the packages whose public surface changed. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Run only named files inside `test/integration`, with `pnpm test `. Run journeys with `pnpm test:journeys `. +- Use `mise exec --` for node and pnpm. Never `npx`. Do not write to `/tmp` or any system temp directory. Save slow output under `wip/` and read the file. Never use bare `git stash`. +- Prose in Markdown is never hard-wrapped. ADRs carry no ticket ids, dispatch ids or slice names. +- No comments where the code can say it. No bare `as` casts in production code, including the upgrade script. No `any`. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. Commit re-emitted files apart from hand-written changes. +- Do not push. Do not open a PR. + +## Done when + +- `pnpm fixtures:check` is clean, and so is `pnpm build`. +- `pnpm test:packages` passes, apart from failures you show have other causes, such as the tarball tests' `@vercel/detect-agent` trust warning. +- Typecheck passes in every example. +- Typecheck, lint and `test --coverage` pass in every package this dispatch touches. +- `lint:deps`, `lint:ratchets`, `check:error-reference` and `check:upgrade-coverage` pass. +- The example migration chains pass on PGlite, as section 2 describes. +- The upgrade script is validated on a real history, as section 3 describes. + +## Report + +Plain English, short sentences. Cover: +- what was re-emitted; +- each example migration's new operations; +- the script's approach to hashes and manifests, and the validation run; +- the fragments; +- the docs changed; +- the checks; +- anything still red, and why; +- the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/17-journeys-docs-pr-text.md b/projects/migration-statements/slices/remaining-nouns/dispatches/17-journeys-docs-pr-text.md new file mode 100644 index 000000000000..212b768a8ac8 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/17-journeys-docs-pr-text.md @@ -0,0 +1,77 @@ +# Dispatch 17: journeys, the ADR number, loose ends, and the PR text + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **Spec:** [`../spec.md`](../spec.md) "Done when" · **Builds on:** dispatch 16 · **Branch:** `tml-3537-value-objects-storage` + +## Outcome + +PR 3 is ready for the two-reviewer `/drive-code-review`. Its journeys show the new behaviour from the CLI, the ADR has a free number, and the PR text is drafted. + +### 1. The ADR number + +Main already has an ADR 278 ("Each database's packages live in one folder"), and open PRs use 307 and 308. Do these: +- Rename ours to **ADR 309**: `docs/architecture docs/adrs/ADR 309 - Value objects are storage entities, and a discriminator column names a value set.md`. +- Update every reference to our ADR across code comments, docs, the skill, the upgrade fragments and the project files. That is about 44 references. Leave references to the other ADR 278 as they are. +- Add the index row in `docs/architecture docs/ADR-INDEX.md`. +- Before you commit, check again that 309 is free on `origin/main` and in open PRs (`gh pr list --repo prisma/orm --state open --json files`). + +### 2. Journeys + +Add CLI journeys under `test/integration/test/cli-journeys/`, following the existing journeys and their helpers. Each journey runs through both `migration plan` (then `migrate`) and `db update`, and a later plan is empty (spec "Done when"). Run them on Postgres (PGlite): +- **A value object column on a managed table.** + - `migration plan` writes the value object check. + - `migrate` applies it. + - A row the ORM writes is accepted, and a row with an extra key is refused. + - `db verify` is clean. +- **Changing a value object.** Add a member, and then make a member nullable. Each one is a storage change. The plan drops the old check and adds the new one. On a table with rows that fit the new shape, the apply works. +- **A row that does not fit.** Adding the check to a table that already holds a bad row fails. The error names the constraint and changes nothing. +- **Adding a variant.** The discriminator check is replaced, and the new variant's value is accepted. +- **A storage change with no operation.** Change a value object used only by an external table's column. `migration plan` writes a migration with no operations, and `migrate` moves the marker. + +On MongoDB (mongodb-memory-server), add one journey: a value object change updates the collection validator, as before this PR. This shows that moving value objects into storage did not break Mongo. + +### 3. Loose ends from dispatch 16 + +- `test/integration/test/sql-orm-client/fixtures/contract.ts` patches a built contract to add `Address` to storage. That leaves its `storageHash` stale. Author the value object through a user-facing surface instead (`.agents/rules/no-contract-data-patching-in-tests.mdc`). Or, if no SQL TypeScript surface exists, use the emitted fixture. +- The upgrade script does not write discriminator value set types into snapshot `contract.d.ts` files. Find out whether anything reads them: typecheck an upgraded copy of `prisma-8-demo` under `wip/d16-upgrade/`. If something does, write them. If nothing does, say so in the app fragment. +- On the upgraded copy, `migration plan` asked for `--from`. Find out why. If the project's `db` ref or graph is left inconsistent by the script, fix the script. If the request is expected, say why in the fragment. +- The `mongo-demo` and `retail-store` chains fail `db migrate` on mongodb-memory-server. Check out `origin/main` into a new worktree under `wip/`, and run the same commands there to find out whether this happens on main too. Report the result with the commands. Do not fix it here. + +### 4. The PR text + +Draft the PR title and description at `wip/pr3-body.md`. Follow `skills-contrib/create-pr/SKILL.md`: +- The title is "TML-3537: " followed by a plain sentence saying what a user can now do or see. No conventional prefix. +- Open with a concrete example: a PSL value object, the `contract.json` before and after, and the Postgres check it derives. +- Then build the explanation step by step. +- State each decision before its alternatives. Put the alternatives last. +- List the upgrade steps. +- Close with the line `Agent: radagast-20` and no AI attribution line. +- Never hard-wrap the prose. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Run only named files inside `test/integration`, with `pnpm test `, and journeys with `pnpm test:journeys `. +- Use `mise exec --` for node and pnpm. Never `npx`. +- Do not write to `/tmp` or any system temp directory. Save slow output under `wip/` and read the file. +- Never use bare `git stash`. +- Write tests first. +- No comments where the code can say it. No bare `as` casts in production code. No `any`. +- Never hard-wrap Markdown prose. ADRs carry no ticket ids or dispatch names. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. +- Do not push. Do not open a PR. + +## Done when + +- The new journeys pass. +- The enum value journeys, the namespace journeys and the plans-with-no-operations journeys still pass. +- `fixtures:check`, `lint:deps`, `lint:ratchets`, `check:error-reference` and `check:upgrade-coverage --mode pr --prev $(git merge-base HEAD origin/main) --head HEAD` pass. +- Typecheck, lint and `test --coverage` pass in every touched package. + +## Report + +Plain English, short sentences. Cover: +- the ADR renumbering; +- each journey and what it shows; +- each loose end and its answer; +- where the PR text is; +- the checks; +- the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/18-pr3-review-fixes.md b/projects/migration-statements/slices/remaining-nouns/dispatches/18-pr3-review-fixes.md new file mode 100644 index 000000000000..e6843ca31ba4 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/18-pr3-review-fixes.md @@ -0,0 +1,101 @@ +# Dispatch 18 — fixes from the whole-branch review of PR 3 + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Reviews:** `../reviews/pr3/system-design-review.md` (findings A01–A17) and `../reviews/pr3/code-review.md` (findings F1–F5) · **Branch:** `tml-3537-value-objects-storage` + +F1 is fixed (`059a3e6a8f`). Each item below records the decision; do what it says. Write a failing test first for every behaviour change. One commit per item or per small group of items. + +## Contract shape and types + +1. **A01 — `valueObject` is a framework storage entity kind.** + - Record this in ADR 309. Add a matching note to ADR 221's section on pack-contributed entity kinds: value objects and the value sets their members name are kinds the framework defines and reads, because domain fields refer to them. + - Give the value object field schema one definition that both families use. If a shared schema cannot be imported by both, add a test that one input gives the same entity in both families. + - Decide `nullable` and `dict` once: + - `nullable` is required in the JSON, as SQL has it. + - `dict` is `true` or absent. The Mongo schema currently accepts `false`. + - Update the JSON schema and any fixture this changes. +2. **A03 — one name for "domain plus storage namespaces."** + - Widen `ContractWithDomain` to carry `storage: Pick`. Remove `StatementContract` and `ContractPlanes`, and replace the inline `Pick` uses with that type or one exported name for it. + - Rename the local `StorageNamespaces` aliases, so the word means one thing across the codebase. +3. **A04 and A05 — the names of the storage value object helpers.** + - Today there are three different functions named `storageValueObjects`, plus an export that only tests use. Give each a name that says what it returns, and remove the test-only export. + - Make `ValueObjectFieldType` and `ContractValueObjectField` read as what they are: the field type that refers to a value object, and a field of a value object. Rename one of them if a reader cannot tell them apart. + - Use existing vocabulary. List the names you chose in the report. +4. **A06 — the sixth type parameter of `FieldTypesFromDomain` is required.** + - Always emit `ContractBase['storage']['namespaces']`, and remove the `hasValueObjects` branch. Every `contract.d.ts` changes, so re-emit. + - Say in the type's doc comment that it reads both planes. + - Update the extension upgrade fragment. +5. **A07 — the extension fragment's override example.** Correct the namespace in the example. Say that under `valueObjects` the key is the storage namespace, while under `models` it is the domain namespace. +6. **A14 — names in the column check input.** + - The column check input reuses the member type. Give the column its own type if a member and a column can differ. + - Rename `openObject` to say what the predicate accepts there (any object), not why. Use existing vocabulary. +7. **A15 — the Mongo validator.** It looks up value objects by name only. Look them up by the field's coordinate, as every other reader now does. Add a test with two value objects of the same name in different storage namespaces, built by hand as a contract. +8. **A16 and F4 — no unions or dictionaries in SQL.** The SQL storage schema accepts union and dictionary value object members, but SQL code treats them as impossible. Refuse them in the SQL schema, with a message that names the member. Add a test that a hand-written contract with one is refused at validation, not by an internal error in `contract print`. + +## Postgres checks + +9. **A08 and F2 — a codec's JSON type is declared on the codec or data type, not in a table in the target.** + - Declare it the way the Mongo family declares `bsonTypes` on its data types (`mongo-data-type.ts`, `bsonTypesOfCodec`). The Postgres renderer reads it through the codec lookup the build already has. Remove `value-object-json-types.ts`. + - Every Postgres and SQL family codec declares one of these: its JSON type; that it holds any JSON value; or that only presence is checked. This includes the three codecs that came with the merge of main: `pg/timestamp-date@1`, `pg/timestamp-instant@1` and `pg/interval-string@1`. + - Add a test that fails when a codec declares none of them. + - Keep the presence-only rules from dispatch 15: a required member is not JSON `null`, and `Json` members hold anything. +10. **A11 — the check kind is `shape`, and its constraint suffix is `shape`.** + - The sibling kinds name what they check (`membership`, `elementNotNull`). A value object check checks the column's shape. The suffix ships in users' constraint names, so it changes now, before release. + - Update `CHECK_KIND_SUFFIX`, the docs, the error reference, the upgrade fragment and the `prisma-8-demo` migration, then re-emit. +11. **A13 — one derivation for the build and for print.** + - Put the discriminator value set derivation and the value object check derivation in one function each, in `@internal/sql-contract/check-expressions` beside `resolveValueObject`. + - The build and `contract print` both call them. The print round-trip tests then check the comparison, not two copies of the rule. +12. **F3 — the lock.** + - Add one sentence to the app upgrade fragment and to the Data Contract doc. Adding a check takes a lock on the table while Postgres reads every row. Any later change to a value object renames the check, so the check is dropped and added again, and the rows are read again. + - Add a `deferred.md` entry: adding these checks as `NOT VALID` and then running `VALIDATE CONSTRAINT`. + +## CLI and messages + +13. **A12 — `changedStorageEntities`.** + - Fill it for every migration `migration plan` writes, not only one with no operations. + - The CLI decides "written with no operations" from the operations, not from whether the field is present. + - Use the same noun as `changedElementCoordinates` where that reads better. + - Check that it does not clash with the existing `noOp` field, and say how in the README. +14. **A02 — the domain validator checks storage entities.** + - Today `validateContractDomain` checks that a value object member's storage value set exists. Move the checks of storage entries to a storage validator: in the framework if there is one, otherwise beside `validateContractDomain` under a name that says it checks storage. + - Leave the checks of the domain-to-storage reference in the domain validator. +15. **F5 — the refusal for an old snapshot.** Name the upgrade script, as the `noCheck` refusal does. A snapshot cannot be re-emitted. + +## ADR 309 and other docs + +16. **A09.** + - Add a short paragraph on why a value object does not have both a domain definition and a storage entity, the way models and tables do. Name the cost: there are no stored names without a later shape change. + - Change the Prisma 6 reader's diagnostic (`contract-prisma6/src/interpreter.ts` around line 880), so that it no longer promises `@map` support on composite type fields. + - Say that enum member names are outside the principle, and link TML-3720. +17. **A10.** + - Limit "every change is an operation" to targets that derive enforcement. + - List the known no-operation cases: + - targets with no check renderer (SQLite and MySQL); + - on Postgres, a member codec or type parameter change that keeps the same JSON type. + - Point to the no-operations report as how the system handles them. +18. **A17.** Fix the small documentation drift the review lists. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Run only named files inside `test/integration`, with `pnpm test `, and journeys with `pnpm test:journeys `. `pnpm test:packages` is allowed. +- Use `mise exec --` for node and pnpm. Never `npx`. Do not write to `/tmp` or any system temp directory. Save slow output under `wip/` and read the file. Never use bare `git stash`. +- No comments where the code can say it. No bare `as` casts in production code. No `any`. No file extensions in imports. No backwards-compatibility exports. Framework packages name no family or target. +- No invented nouns. Never hard-wrap Markdown prose. ADRs carry no ticket ids except where this brief says so (TML-3720), and no dispatch names. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. Commit re-emitted files apart from hand-written changes. +- Do not push. Do not open a PR. + +## Done when + +- `pnpm build` and `fixtures:check` are clean. +- `pnpm test:packages` passes, apart from the tarball tests' known `@vercel/detect-agent` trust warning. +- Typecheck passes in every example. +- `lint:deps`, `lint:ratchets`, `check:error-reference` and `check:upgrade-coverage --mode pr --prev $(git merge-base HEAD origin/main) --head HEAD` pass. +- Typecheck, lint and `test --coverage` pass in every touched package. +- These pass: + - the journeys `value-object-checks.e2e.test.ts`, `value-object-validator.mongo.e2e.test.ts`, `plans-with-no-operations*.e2e.test.ts` and `enum-value-statements*.e2e.test.ts`; + - `test/integration/test/sql-orm-client/value-object-checks.test.ts`; + - `authored-contract-roundtrip.integration.test.ts`; + - the upgrade script test. + +## Report + +Plain English, short sentences: each item and what you did, the names you chose, the tests that were red first, the checks, anything you could not do and why, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/19-pr3-review-round-2-fixes.md b/projects/migration-statements/slices/remaining-nouns/dispatches/19-pr3-review-round-2-fixes.md new file mode 100644 index 000000000000..5da3a5af4331 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/19-pr3-review-round-2-fixes.md @@ -0,0 +1,83 @@ +# Dispatch 19: fixes from the second whole-branch review of PR 3 + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Reviews:** `../reviews/pr3/system-design-review.md` (A18–A26) and `../reviews/pr3/code-review.md` (F6–F9) · **Branch:** `tml-3537-value-objects-storage` + +Each item records the decision; do what it says. Write a failing test first for every behaviour change. + +## Codecs and their JSON type + +1. **A19: name the fact, not the consumer.** + - Rename `valueObjectJsonType` to `jsonType`. It is the JSON type the codec's value is written as inside a JSON document. + - The values are `'string' | 'number' | 'boolean' | 'object' | 'array' | 'any'`. `'any'` means the value can be any JSON value, including `null`, as for `Json`. `'presenceOnly'` goes away. + - Read each codec's encode path, and declare what it actually writes. For example, `pg/enum` writes a string, so it is `'string'`, and `Bytes` writes an object. + - The check enforces the declared type. A required member is never JSON `null` unless its codec is `'any'`. + - Some codecs have no JSON form the SQL ORM can write today. `BigInt` is one: the jsonb codec throws when it writes one. For such a codec, declare the type its value would have, and keep the `deferred.md` entry. List each one in the report. + - Update the codec authoring guide, the error reference and the extension fragment. +2. **F6: a codec without `jsonType` is an error only where a value object member uses it.** + - At runtime, the registries in the runtime adapter and in the control adapter accept a descriptor without `jsonType`. A codec package compiled before this release keeps working in apps that do not put it in a value object. + - In TypeScript the field stays required, so authors who rebuild get a compile error. + - When the build derives a value object check for a member whose codec has no `jsonType`, it refuses with a structured error, not an invariant. The error names: + - the codec id; + - the value object and the member; + - the missing property; + - what to do: the codec's author adds `jsonType`. + + Add the error to the error reference. + - Fix the extension fragment's detection, so it also finds descriptors that implement `AnyPostgresCodecDescriptor` directly. +3. **A20: the family codecs declare their own `jsonType`.** The SQL family codecs (`sql/int` and the others) declare it on the family codec, not through options in the Postgres adapter. +4. **A25: the family resolves the JSON type.** + - The family resolves each member's JSON type when it resolves the value object, into `ResolvedValueObject`. + - The renderer reads that, and drops its `codecLookup` argument. + - Remove `NO_CODECS`. + +## Contract types and schemas + +5. **A18: storage-only functions take storage.** About 30 functions now take `ContractWithDomain['storage']` but read only storage. These include `elementCoordinates`, `changedElementCoordinates`, `entityAt` and SQL's `storageOf`. Type their input `Pick`. Keep `ContractWithDomain` only where the domain is read. +6. **A21: one field type schema.** + - Export one run-time schema for a field's type from the framework. + - The framework's value object member schema, the SQL domain field schema and the Mongo domain field schema all use it. + - SQL's acceptance of a union inside a union then follows whatever the shared schema decides. A union inside a union is refused, as Mongo does. +7. **A22: the published SQL JSON schema matches the refusal.** `data-contract-sql-v1.json` must not allow union or dictionary value object members. Make the generator produce that, or state it in the source schema the generator reads, then regenerate the file. +8. **A24: one name corrected.** + - Rename `StorageValueObjectSchema` to `ContractValueObjectSchema`, since it validates `ContractValueObject`. + - Keep `ValueObjectReference`. Its earlier name read as the opposite of `ContractValueObjectField`, which round 1 decided to fix. + - Keep `anyObject`. Round 1 decided on it. +9. **F7: the SQL union and dictionary refusal names the value object.** The message names the value object and the member: `value object ":" member "" is a dictionary; SQL value objects hold no unions or dictionaries, since their check cannot enforce them`. It does not print the namespace as JSON. + +## CLI + +10. **A26: the JSON key says what users read.** Rename the `--json` key `changedElementCoordinates` to `changedStorageEntities`. That matches the README, the doc comment and the summary text. It stays filled for every written migration. Internal functions keep `changedElementCoordinates`. + +## Upgrade script and docs + +11. **F8: the script normalizes old Mongo value object members.** It adds `nullable: false` where a member has no `nullable`, and removes `dict: false`. The old Mongo schema allowed both forms. Add tests. +12. **A23: ADR wording.** In ADR 221 and ADR 309, say the framework *reads* the `valueSet` slot where a member names one, and *defines* `valueObject`. +13. **F9: ADR 309's two sentences.** Correct the first Consequence, and the reason given for rejecting the "record without enforcing" alternative, so they agree with the list of cases that have no operations. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Run only named files inside `test/integration`, with `pnpm test `, and journeys with `pnpm test:journeys `. `pnpm test:packages` is allowed. +- Use `mise exec --` for node and pnpm. Never `npx`. Do not write to `/tmp` or any system temp directory. Save slow output under `wip/` and read the file. Never use bare `git stash`. +- No comments where the code can say it. No bare `as` casts in production code. No `any` type. No file extensions in imports. No backwards-compatibility exports. Framework packages name no family or target. +- No invented nouns. Never hard-wrap Markdown prose. ADRs carry no ticket ids beyond those already there, and no dispatch names. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. Commit re-emitted files apart from hand-written changes. +- Do not push. Do not open a PR. + +## Done when + +The same checks as dispatch 18: + +- `pnpm build` and `fixtures:check` are clean. +- `pnpm test:packages` passes, apart from the known tarball trust warning. +- Typecheck passes in every example. +- `lint:deps`, `lint:ratchets`, `check:error-reference` and `check:upgrade-coverage --mode pr --prev $(git merge-base HEAD origin/main) --head HEAD` pass. +- Typecheck, lint and `test --coverage` pass in every touched package. +- These pass: + - the journeys `value-object-checks`, `value-object-validator.mongo`, `plans-with-no-operations*` and `enum-value-statements*`; + - `sql-orm-client/value-object-checks`; + - `authored-contract-roundtrip`; + - the upgrade script test. + +## Report + +Plain English, short sentences: each item and what you did, each codec's `jsonType` where it was not obvious, the tests that were red first, the checks, anything you could not do and why, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/dispatches/20-pr3-qa-fixes.md b/projects/migration-statements/slices/remaining-nouns/dispatches/20-pr3-qa-fixes.md new file mode 100644 index 000000000000..4043a0363ef2 --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/dispatches/20-pr3-qa-fixes.md @@ -0,0 +1,72 @@ +# Dispatch 20: fixes from manual QA and the round 2 verification of PR 3 + +**Plan:** [`../plan-pr3.md`](../plan-pr3.md) · **ADR:** ADR 309 · **Sources:** the QA report `projects/migration-statements/manual-qa-reports/2026-10-23-qa-3b-pr3.md` (F1–F10) and the verification report `wip/reviews/verify-round2.md` (three minor findings) · **Branch:** `tml-3537-value-objects-storage` + +Each item records the decision; do what it says. Write a failing test first for every behaviour change. + +## Upgrade instructions + +1. **QA F1: the steps leave a stale `db` ref.** The app instructions say to run `db sign --db`, which writes `migrations/app/refs/db.json`. Then `db migrate` does not move that ref, so the next `migration plan` forks the graph and writes a duplicate migration. Change the steps so they leave the ref correct. Work out from the CLI which form is right, for example `db migrate --advance-ref db`, or `db sign --no-advance-ref` together with `--from`. Then run the corrected steps end to end on PGlite against a copy of `examples/prisma-8-demo` from `git merge-base HEAD origin/main`: + - Use that merge base's CLI to migrate, and the current CLI to upgrade. + - The second `migration plan` with no change must write nothing. + - Do the same on `mongo-demo` with mongodb-memory-server, as far as its history allows (TML-3749 covers its broken history). + - Remove the sentence about projects with no `db` ref if the steps never reach that case. Otherwise say when it applies. +2. **QA F4: say when to upgrade the packages.** Add the step "upgrade the Prisma packages" in its place, between applying pending migrations and running the script. + +## A check that existing rows break + +3. **QA F2 and F3: the failure says what to do.** When applying a migration fails because a derived check refuses existing rows, the error must say: + - which check, table and column; + - how many rows fail, if the runner can count them cheaply with the check's own expression. If it cannot, give the query that finds them, built from the check's expression, as in the upgrade instructions; + - the route that keeps the data: plan with `migration plan`, then add a step to the `migration.ts` it writes that updates those rows, placed between the operation that drops the old check and the one that adds the new check, then apply with `db migrate`. For `db update`, say that it cannot keep this data, and give that `migration plan` route. + + Drop the advice that does not fit this case: "reconcile schema drift" from `db update`, and `--to ` from `db migrate`. + + Follow how PR 2 reported rows holding an enum value. Read `RowsHoldingValue` and the runner failure advice in `packages/1-framework/3-tooling/cli/src/utils/runner-failure-advice.ts`. Reuse that mechanism, and keep target-specific detection in the target. The framework reads only a typed interface. + + Test on PGlite: add a required member to a value object while rows lack it. Check that `db migrate` and `db update` both fail with the new advice and change nothing. Check that following the advice with a hand-written update in `migration.ts` applies cleanly and `db verify --strict` is clean. Add this as a case in `test/integration/test/cli-journeys/value-object-checks.e2e.test.ts`. + +## Messages + +4. **QA F5: the plan summary for a migration with no operations.** + - Write the entity kind in words: `value object "public.Home"`, `value set "public.Role"`, `table "public.users"`. Keep the raw kind only in `--json`. + - When `db update` moves the marker with no operations, say that, and name what changed, as `migration plan` does. Do not say "Database already matches contract" alone. +5. **QA F8: ADR 309 check names.** Give the full built name, `
__shape_`, where the ADR names the check, and match the upgrade instructions. + +## Verification minor findings + +6. **The orphaned doc comment.** Move the doc comment of `rewrittenMember` in the upgrade script back above its function (near line 523). +7. **The registry type.** The Postgres codec registry accepts a descriptor without `jsonType`, but its type still says `jsonType` is present. Make the registry's returned type say `jsonType` may be missing, or read it only through `declaredJsonType`. No code may read `descriptor.jsonType` as certainly present. +8. **The script and a malformed `nullable`.** Fill in `nullable: false` only when `nullable` is absent. Leave a malformed value as it is, so validation refuses it. + +## Not in this dispatch + +- QA F7: on MongoDB, making a member required leaves old documents. Same as before this PR, and a later slice covers MongoDB statements. +- QA F9: filed as TML-3750. +- QA F10: `BigInt` members are in `deferred.md`. +- QA F6: TML-3749. + +## Rules + +- Never run `pnpm test:integration`, `pnpm test:e2e` or `pnpm test:all`. Run only named files inside `test/integration`, with `pnpm test `, and journeys with `pnpm test:journeys `. `pnpm test:packages` is allowed. +- Use `mise exec --` for node and pnpm. Never `npx`. Do not write to `/tmp` or any system temp directory. Save slow output under `wip/` and read the file. Never use bare `git stash`, and never `git stash` at all. +- No comments where the code can say it. No bare `as` casts in production code. No `any`. No file extensions in imports. No backwards-compatibility exports. Framework packages name no family or target. +- No invented nouns. Never hard-wrap Markdown prose. +- Commit with `git commit -s --trailer "Signed-off-by: Will Madden "`. No AI attribution lines. Stage files explicitly. Commit `projects/migration-statements/slices/remaining-nouns/spec.md` too; it has an edit of mine that is not committed yet. +- Do not push. Do not open a PR. + +## Done when + +- `pnpm build` and `fixtures:check` are clean. +- `pnpm test:packages` passes, apart from the known tarball trust warning. +- Typecheck passes in every example. +- `lint:deps`, `lint:ratchets`, `check:error-reference` and `check:upgrade-coverage --mode pr --prev $(git merge-base HEAD origin/main) --head HEAD` pass. +- Typecheck, lint and `test --coverage` pass in every touched package. +- These pass: + - the journeys `value-object-checks`, `value-object-validator.mongo`, `plans-with-no-operations*` and `enum-value-statements*`; + - the upgrade script test; + - `sql-orm-client/value-object-checks`. + +## Report + +Plain English, short sentences: each item, the corrected upgrade steps and the end-to-end run, the new failure message as printed, the tests that were red first, the checks, and the commits. diff --git a/projects/migration-statements/slices/remaining-nouns/plan-pr3.md b/projects/migration-statements/slices/remaining-nouns/plan-pr3.md new file mode 100644 index 000000000000..a2b6686a82ca --- /dev/null +++ b/projects/migration-statements/slices/remaining-nouns/plan-pr3.md @@ -0,0 +1,36 @@ +# Slice plan — 3b, PR 3: value objects are storage entities, and a discriminator column names a value set + +**Spec:** [`spec.md`](./spec.md) decision 1 · **ADR:** ADR 309 (approved by Will, 2026-10-21) · **Linear:** [TML-3537](https://linear.app/prisma-company/issue/TML-3537) · **Branch:** `tml-3537-value-objects-storage`, from `origin/main` · **Grounding:** `wip/pr3-grounding.md` (gitignored, in this worktree; code references at main 9979a2d6ca) + +This PR changes the contract shape. It adds no statements; PR 4 adds the value object and variant statements on top of it. + +## Decisions taken while planning (record each in ADR 309 in this PR) + +- **The discriminator value set is SQL only.** On MongoDB the collection validator already pins each variant's discriminator value, so a change there already changes the storage hash. +- **An enum-typed discriminator keeps the enum's value set and check.** The column has one `valueSet` slot, and the enum already restricts it; a variant value outside the enum is already refused. +- **A clash between an enum's value set and a discriminator's is refused, and nothing else.** Enum value sets are keyed by the enum's name and a discriminator's by `.`. Only an enum named exactly like a discriminator's key, in the same storage namespace, clashes. No authoring surface produces that today: PSL names are identifiers, and the SQL TypeScript builder has no polymorphism, so only a hand-written contract definition reaches it. The build still refuses it as a safeguard, naming both and saying to rename the enum. Enum names and members are otherwise unrestricted (Will, 2026-10-23: do not limit enum names to avoid a rare clash). +- **The Postgres value object check is one strict `jsonpath` predicate**, written so a NULL result fails (`IS TRUE`). The ADR's `?&` and `-` operators are not used. +- **A value object that contains itself** is checked as the MongoDB validator does: fully down to where the cycle starts, any object below. +- **Value object fields have no separate stored names**; they are stored under their own names in both families. +- **The example migrations change** (Will, 2026-10-23): each `examples/prisma-8-demo` migration that introduces a value object or a variant gains the operations that create the new checks. Users get an upgrade script for their own projects (Will, 2026-10-23). + +## Gate for every dispatch + +As for PR 2: typecheck; lint and `test --coverage` in every touched package; `lint:deps`; `lint:ratchets`; `fixtures:check` when contracts or rendering change; `check:error-reference`; `check:upgrade-coverage --mode pr --prev $(git merge-base HEAD origin/main) --head HEAD` when a fragment is added. Integration and journeys by named file only. The reviewer runs code on PGlite and mongodb-memory-server, not only reads it. + +## Dispatches + +| # | Outcome | Builds on | +| --- | --- | --- | +| 10 | Contract shape and validators: a storage-plane value object entity in both families, a domain field referring to it by a storage coordinate (the first reference from domain to storage), validation across the two planes, the JSON schema, and the framework accessors | — | +| 11 | A plan whose storage hash changes but that has no operations is written and applied: `migration plan` writes it, `db migrate` and `db update` move the marker, on Postgres, SQLite and MongoDB | — | +| 12 | Authoring and build in both families write value objects into storage (SQL PSL, Mongo PSL, Mongo TS builder, Prisma 6 reader), and SQL PSL writes the discriminator value set and its column reference before the storage hash is computed; a clash between an enum's value set and a discriminator's is refused; the MongoDB validator derivation reads value objects from storage | 10 | +| 13 | Emit and types: `contract.d.ts` types read value objects from the storage plane, with a defaulted type parameter so contracts without value objects emit the same text | 10 | +| 14 | Runtime and tools read value objects through the domain field's reference: the SQL and Mongo ORMs and query builders, the CLI statement resolver, `contract print`, `contract infer`, verification | 12, 13 | +| 15 | Postgres derived checks: the discriminator membership check, and the value object `jsonpath` check for every shape SQL authoring produces, starting from a test per shape on PGlite; a new SQL fixture with nested and list members; contract print and infer recognise the new checks | 12 | +| 16 | Re-emit every fixture and committed contract; the `prisma-8-demo` migrations gain the operations that create the new checks; the app and extension upgrade fragments with a script that rewrites a user's contract snapshots and keeps their manifests' hashes consistent, validated by running it on a project with a real migration history; ADR 309, ADR 172 and ADR 178 notes, Data Contract and MongoDB Family docs, the skill | 11, 14, 15 | +| 17 | Journeys, docs and the PR text | 16 | + +**Order:** 10 and 11 first (11 is independent); then 12 and 13; then 14 and 15; then 16; then 17. Then the two-reviewer `/drive-code-review`, fixes, manual QA, the PR. + +**Risk notes from grounding:** the SQL PSL interpreter resolves polymorphism after the storage hash is computed (dispatch 12 must move the discriminator facts into the build); every `contract.d.ts` may change if the field-types expression changes (dispatch 13); existing rows with extra keys fail the new check at apply (upgrade notes, dispatch 16); the snapshot rewrite in the upgrade script changes storage hashes, so the script must rewrite the snapshot directories and every manifest that names them, or leave old snapshots readable (dispatch 16 decides, from the `data-type-in-contract` fragment of the rc.14 to rc.15 release, which rewrote contracts before). diff --git a/projects/migration-statements/slices/remaining-nouns/plan.md b/projects/migration-statements/slices/remaining-nouns/plan.md index 4b68caa0a62a..3403fd289963 100644 --- a/projects/migration-statements/slices/remaining-nouns/plan.md +++ b/projects/migration-statements/slices/remaining-nouns/plan.md @@ -6,7 +6,7 @@ Four pull requests, in the order the spec gives. This plan covers PR 1. Each lat ## PR 1 — namespaces, and the access question (merged, prisma/orm#30823) -**Branch:** `tml-3537-namespaces`, from `tml-3537-remaining-nouns-spec` (the spec and ADR 278 ride in this PR). Postgres only for the namespace work; the access rename touches every target. +**Branch:** `tml-3537-namespaces`, from `tml-3537-remaining-nouns-spec` (the spec and ADR 309 ride in this PR). Postgres only for the namespace work; the access rename touches every target. ### Gate for every dispatch @@ -36,7 +36,7 @@ Four pull requests, in the order the spec gives. This plan covers PR 1. Each lat - **Renaming the target's default namespace (`public`).** The spec allows it when no other contract space declares it. Dispatch 4 checks whether Postgres lets an ordinary database owner run `ALTER SCHEMA public RENAME` on PGlite and on a managed Postgres; if not, the planner refuses it with advice, and the spec's edge case row is corrected. - **Names of the three facade methods.** Named after the SQL they run, as `createSchema` and `renameTable` are. The reviewer checks them against the existing facade. -- **For PR 3: three points on ADR 278 from PR 1's system-design review.** (1) The storage value object keeps domain field names; state that value object fields have no mapped names, or store the stored key. (2) The discriminator value set is keyed `.` and relies on enum names never containing a dot; use a key the column's coordinate determines, or state the rule where entity names are validated. (3) The ADR has no **Status** or **Date** header, unlike its neighbours. +- **For PR 3: three points on ADR 309 from PR 1's system-design review.** (1) The storage value object keeps domain field names; state that value object fields have no mapped names, or store the stored key. (2) The discriminator value set is keyed `.` and relies on enum names never containing a dot; use a key the column's coordinate determines, or state the rule where entity names are validated. (3) The ADR has no **Status** or **Date** header, unlike its neighbours. ## PR 2 — enum values diff --git a/projects/migration-statements/slices/remaining-nouns/spec.md b/projects/migration-statements/slices/remaining-nouns/spec.md index 737cb0f4074e..3edaced1f078 100644 --- a/projects/migration-statements/slices/remaining-nouns/spec.md +++ b/projects/migration-statements/slices/remaining-nouns/spec.md @@ -37,10 +37,10 @@ Namespace items are Postgres only: SQLite has no namespaces. Everything else wor ## Decisions to check -1. **Value objects become storage entities, and a discriminator column names a value set** (agreed with Will, 2026-10-21; [ADR 278](../../../../docs/architecture%20docs/adrs/ADR%20278%20-%20Value%20objects%20are%20storage%20entities,%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)). Today a value object field rename, a discriminator value change and a variant removal with no own columns change only the domain plane. The storage hash covers only the storage plane, so it stays the same, and the migration system cannot see the change: `migration plan` reports "No changes detected", a same-hash migration fails the integrity check, and snapshots are keyed by storage hash with the first write kept. A value object's fields and a discriminator's values are stored data the planner must see, so they belong in storage: +1. **Value objects become storage entities, and a discriminator column names a value set** (agreed with Will, 2026-10-21; [ADR 309](../../../../docs/architecture%20docs/adrs/ADR%20309%20-%20Value%20objects%20are%20storage%20entities,%20and%20a%20discriminator%20column%20names%20a%20value%20set.md)). Today a value object field rename, a discriminator value change and a variant removal with no own columns change only the domain plane. The storage hash covers only the storage plane, so it stays the same, and the migration system cannot see the change: `migration plan` reports "No changes detected", a same-hash migration fails the integrity check, and snapshots are keyed by storage hash with the first write kept. A value object's fields and a discriminator's values are stored data the planner must see, so they belong in storage: - **Value objects** move from `domain.namespaces..valueObjects` to `storage.namespaces..entries.valueObject`, in both families, keeping their definition. A domain field refers to one by entity coordinate, as a field typed by an enum refers to its value set. No copy. - **Discriminator values** are a value set keyed `.`, which the base table's discriminator column names, with a derived check on Postgres as an enum column has. - - **Both are enforced:** on Postgres a value object column gets a derived check on its keys (`?&` and `-` for a flat shape, `jsonpath` for nesting and lists); on MongoDB the validator already lists them. So a change is normally a check or validator change. Will, 2026-10-21: migrations are expected never to be empty, because two storage hashes are two database states, but edge cases will happen, and the system handles them: `migration plan` writes the package with no operations and says what changed, and `db migrate` moves the marker. The "no operations" refusal goes for a plan whose storage hash changed. + - **Both are enforced:** on Postgres a value object column gets a derived check on its shape, one strict `jsonpath` predicate written so an unknown result fails; on MongoDB the validator already lists them. So a change is normally a check or validator change. Will, 2026-10-21: migrations are expected never to be empty, because two storage hashes are two database states, but edge cases will happen, and the system handles them: `migration plan` writes the package with no operations and says what changed, and `db migrate` moves the marker. The "no operations" refusal goes for a plan whose storage hash changed. **Cost:** every contract with value objects or variants is re-emitted with a new storage hash, with an upgrade fragment. The move touches every reader of value objects: both emitters, both type renderers, the ORMs and the MongoDB validator derivation. @@ -103,8 +103,8 @@ Every operation the planner writes is also something a user could write in `migr | --- | --- | --- | | 1 | Namespaces: move, rename, delete, and a removed namespace becomes a question. The access question wording and rename | — | | 2 | Enum values: rename and delete in scalar and list columns, on Postgres and SQLite | 1 merged | -| 3 | Value objects move to the storage plane and discriminator columns name a value set, both with derived checks on Postgres (decision 1), with ADR 278. First step: confirm a Postgres check can enforce every value object shape SQL authoring produces, nesting and lists included; raise any shape it cannot. A plan whose hash changed but has no operations is written and applied, not refused. Contracts re-emitted, with an upgrade fragment. No statements yet | 2 merged | -| 4 | Value object field rename and delete, including enum values inside JSON. Variant convert and delete | 3 merged | +| 3 | Value objects move to the storage plane and discriminator columns name a value set, both with derived checks on Postgres (decision 1), with ADR 309. First step: confirm a Postgres check can enforce every value object shape SQL authoring produces, nesting and lists included; raise any shape it cannot. A plan whose hash changed but has no operations is written and applied, not refused. Contracts re-emitted, with an upgrade fragment. No statements yet | 2 merged | +| 4 | Value object field rename and delete, including enum values inside JSON. A value of a new required value object member for the rows that lack it (`--backfill`), since PR 3's Postgres check refuses those rows. Variant convert and delete | 3 merged | PR 3 is separate because it changes the contract shape and every contract with value objects or variants, and needs its own review, as slice 4p's first PR did. Slice 4b reuses PR 4's value object statement support for MongoDB. diff --git a/skills/prisma-8/references/contract.md b/skills/prisma-8/references/contract.md index 322b10acdf4b..01da72685aaf 100644 --- a/skills/prisma-8/references/contract.md +++ b/skills/prisma-8/references/contract.md @@ -162,7 +162,7 @@ Values the client generates are written `@onCreate(fn())` and `@onUpdate(fn())`: In the TS builder, an alias is a constant passed as the field, because field builders are immutable: `const Slug = field.column(type.VarChar(64))`, `const UpdatedAt = field.column(type.TimestampString(3)).onCreate(gen.now()).onUpdate(gen.now())`, then `slug: Slug, updatedAt: UpdatedAt.default(now())`. `type.*` has a helper for every PSL type constructor, and the contributed aliases are `type.temporal.CreatedAt()`, `type.temporal.UpdatedAt()` and the identifier aliases such as `type.Ulid()` (`type.sql.Ulid()` on every SQL target), used as `field.column(type.temporal.UpdatedAt())` and `field.column(type.Ulid()).id().onCreate(gen.ulid())`. A definition has no `types` key (`CONTRACT.TYPES_KEY_REMOVED`), and `field.namedType(...)` takes only an enum handle. -Note: scalar lists (e.g. `String[]`) are gated on the target's `sql.scalarList` capability — PostgreSQL declares it, so `tags String[]` lowers to an array column (with a generated no-NULL-elements check; write `tags String?[]` for a list whose elements may be null, which gets no such check); SQLite does not, and rejects the field with `PSL_SCALAR_LIST_UNSUPPORTED_TARGET`. Implicit Prisma-ORM many-to-many (list navigation on both sides without a join model) is rejected by the SQL interpreter — declare the join model explicitly and the ORM traverses it for you (`references/queries.md`). Composite/embeddable types (`type Address { ... }` with `address Address` on a model) are supported: the interpreter lowers them to `valueObjects` in the domain and stores them as `jsonb` columns. See *Workflow — Value objects* below. +Note: scalar lists (e.g. `String[]`) are gated on the target's `sql.scalarList` capability — PostgreSQL declares it, so `tags String[]` lowers to an array column (with a generated no-NULL-elements check; write `tags String?[]` for a list whose elements may be null, which gets no such check); SQLite does not, and rejects the field with `PSL_SCALAR_LIST_UNSUPPORTED_TARGET`. Implicit Prisma-ORM many-to-many (list navigation on both sides without a join model) is rejected by the SQL interpreter — declare the join model explicitly and the ORM traverses it for you (`references/queries.md`). Composite/embeddable types (`type Address { ... }` with `address Address` on a model) are supported: the interpreter lowers them to storage value objects, at `storage.namespaces..entries.valueObject`, and stores them as `jsonb` columns. See *Workflow — Value objects* below. ## Workflow — Edit a model / field / relation (TS builder) @@ -299,7 +299,7 @@ Querying the variants is a runtime concern — see `references/queries.md`. ## Workflow — Value objects (composite types) -The concept: `type Foo { ... }` blocks declare value-object shapes. The interpreter lowers them to `valueObjects` in the contract domain and stores them as `jsonb` columns. Nested value-object references are supported. +The concept: `type Foo { ... }` blocks declare value-object shapes. The interpreter lowers them to storage value objects, at `storage.namespaces..entries.valueObject`, and stores them as `jsonb` columns; a domain field refers to one by coordinate. Nested value-object references are supported. ```prisma type Address { @@ -316,7 +316,9 @@ model User { } ``` -Emitted `contract.json` carries `domain.namespaces..valueObjects.Address` with its field descriptors, and the `address` column lands as `codecId: "pg/jsonb@1"` / `dataType: "pg/jsonb"` in `storage`. +Emitted `contract.json` carries the value object in storage, at `storage.namespaces..entries.valueObject.Address` with its field descriptors, and the domain field `address` refers to it by coordinate: `{ "kind": "valueObject", "plane": "storage", "namespaceId": "public", "entityKind": "valueObject", "entityName": "Address" }`. The `address` column lands as `codecId: "pg/jsonb@1"` / `dataType: "pg/jsonb"`. Because the value object is in storage, renaming, adding or removing one of its fields changes the storage hash, and `prisma migration plan` writes a migration for it. + +On Postgres, a managed table gets a derived check on each value object column, `
__shape_`: the value must be an object (an array of them for a list) with every required member, each of its JSON type, and no other key. A nullable column also accepts `NULL`. On SQL, a polymorphic base's discriminator column refers to a value set `.` holding the variants' values, and Postgres derives a check from it, as for an enum. Canonical worked example: `examples/prisma-8-demo/src/prisma/contract.prisma`. diff --git a/skills/prisma-8/references/debug.md b/skills/prisma-8/references/debug.md index f911cc780a09..5153cc62db05 100644 --- a/skills/prisma-8/references/debug.md +++ b/skills/prisma-8/references/debug.md @@ -84,7 +84,7 @@ The single source of truth: read the envelope, find the row by `code`, follow th | `CONTRACT.VALIDATION_FAILED` | `contract emit`, `db *` | Re-run `pnpm prisma contract emit` after fixing the contract source named in `where.path`; `meta.errors` lists the issues. See `references/contract.md`. | | `CONFIG.DB_CONNECTION_REQUIRED` | `db *`, `db migrate`, `migration status` | Pass `--db ` or set `db.connection` in `prisma.config.ts`. | | `CONFIG.MISSING_EXTENSION_PACKS` | `contract emit` (e.g. contract uses `pgvector.Vector(...)` but config does not list the pgvector descriptor) | Add the descriptors named in `meta.missingExtensionPacks` to `extensions` in `prisma.config.ts`. See `references/contract.md`. | -| `MIGRATION.PLANNING_FAILED` | `db init`, `db update`, `migration plan` | Inspect `meta.conflicts`. Recovery is per-conflict — chain to `references/migrations.md`. When `migration plan` raises it because the planner produced no operations, the conflict's `why` gives the next step (`migration new --from `, or `db sign` for a database managed with `db update`). | +| `MIGRATION.PLANNING_FAILED` | `db init`, `db update`, `migration plan` | Inspect `meta.conflicts`. Recovery is per-conflict — chain to `references/migrations.md`. When `migration plan` raises it for a first migration whose contract describes nothing to create (conflict kind `nothingToCreate`), the conflict's `why` gives the next step: `migration new`, with the operations written by hand. A plan from an earlier contract with no operations is not refused: it is written, and `db migrate` moves the marker. | | `CLI.INIT_MISSING_FLAGS` / `CLI.INIT_INVALID_FLAG_VALUE` / `CLI.INIT_REINIT_NEEDS_FORCE` / `CLI.INIT_*` | `prisma orm init` | Re-run with the flags listed in `meta.missingFlags` or `meta.allowed`; a re-init needs `--confirm `. `CLI.INIT_INSTALL_FAILED` (exit 4) and `CLI.INIT_EMIT_FAILED` (exit 5) are findings on a completed scaffold — the files are on disk; fix and re-run the named step. | | `MIGRATION.UNFILLED_PLACEHOLDER` | `node /migration.ts` (self-emit) or `db migrate` | Edit `migration.ts`, replace the `placeholder("")` named by `meta.slot` with a real query closure, self-emit. See `references/migrations.md`. | | `MIGRATION.FILE_MISSING` | Reading a migration package | Restore from version control or scaffold a fresh package with `migration plan` / `migration new`. | diff --git a/test/e2e/framework/test/fixtures/generated/contract.d.ts b/test/e2e/framework/test/fixtures/generated/contract.d.ts index 6969447cbf1f..9bf452fc00a1 100644 --- a/test/e2e/framework/test/fixtures/generated/contract.d.ts +++ b/test/e2e/framework/test/fixtures/generated/contract.d.ts @@ -290,14 +290,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -1357,7 +1359,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts b/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts index f571f8b997fb..e53d7794df3f 100644 --- a/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts +++ b/test/e2e/framework/test/sqlite/fixtures/generated/contract.d.ts @@ -139,14 +139,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -694,7 +696,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/scripts/emit-fixture-configs.mjs b/test/integration/scripts/emit-fixture-configs.mjs index c78325fa2e54..f38f95d3dfdc 100644 --- a/test/integration/scripts/emit-fixture-configs.mjs +++ b/test/integration/scripts/emit-fixture-configs.mjs @@ -28,6 +28,8 @@ const fixtureRoots = [ 'test/mongo/list-filters', 'test/mongo/temporal-generators', 'test/mongo/prisma6-source', + 'test/mongo/recursive-value-objects', + 'test/value-objects/fixtures', ]; function findConfigs(dir) { diff --git a/test/integration/test/authoring/extension-text-type.test.ts b/test/integration/test/authoring/extension-text-type.test.ts index 9cecbde0d49e..50d3d58759cb 100644 --- a/test/integration/test/authoring/extension-text-type.test.ts +++ b/test/integration/test/authoring/extension-text-type.test.ts @@ -59,6 +59,7 @@ class CitextCodec extends CodecImpl> { + override readonly jsonType = 'any' as const; override readonly dataType = testtextCitext.id; override readonly codecId = CITEXT_CODEC_ID; override readonly traits = ['equality'] as const; diff --git a/test/integration/test/authoring/mongo-parity/enums-and-lists/expected.contract.json b/test/integration/test/authoring/mongo-parity/enums-and-lists/expected.contract.json index 2eafb96709a7..434fc1ec29f9 100644 --- a/test/integration/test/authoring/mongo-parity/enums-and-lists/expected.contract.json +++ b/test/integration/test/authoring/mongo-parity/enums-and-lists/expected.contract.json @@ -56,8 +56,11 @@ "badge": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Badge", "kind": "valueObject", - "name": "Badge" + "namespaceId": "__unbound__", + "plane": "storage" } }, "badges": { @@ -66,8 +69,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Badge", "kind": "valueObject", - "name": "Badge" + "namespaceId": "__unbound__", + "plane": "storage" } }, "id": { @@ -164,32 +170,6 @@ } } } - }, - "valueObjects": { - "Badge": { - "fields": { - "label": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "level": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - }, - "valueSet": { - "entityKind": "enum", - "entityName": "Level", - "namespaceId": "__unbound__", - "plane": "domain" - } - } - } - } } } } @@ -279,6 +259,32 @@ } } }, + "valueObject": { + "Badge": { + "fields": { + "label": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "level": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + }, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Level", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + } + }, "valueSet": { "Level": { "kind": "valueSet", @@ -294,7 +300,7 @@ "kind": "mongo-database" } }, - "storageHash": "0b97ad584118f2bce663b3289d44b665cfe365baf2d164b1fc70f4d694993963" + "storageHash": "f15cc7ed8744412382801fa2f9e249273fedc015ddfa58c6ca1c0416baa0fb9c" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/authoring/side-by-side/mongo/contract.json b/test/integration/test/authoring/side-by-side/mongo/contract.json index cd83086f8657..f1d33d14d573 100644 --- a/test/integration/test/authoring/side-by-side/mongo/contract.json +++ b/test/integration/test/authoring/side-by-side/mongo/contract.json @@ -211,8 +211,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "bio": { @@ -298,26 +301,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "street": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -502,6 +485,26 @@ } } }, + "valueObject": { + "Address": { + "fields": { + "street": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "Role": { "kind": "valueSet", @@ -516,7 +519,7 @@ "kind": "mongo-database" } }, - "storageHash": "9d0d8d271546a3ca3180095b1adaf549c22edf1cc8f8d2b63f92fe219df7d677" + "storageHash": "70cb520a3df87e0fd81be63d8801438a417be178c7bd6635a96830cbb62cc0ba" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/cli-journeys/README.md b/test/integration/test/cli-journeys/README.md index 9bccc010b349..d2be35f4f077 100644 --- a/test/integration/test/cli-journeys/README.md +++ b/test/integration/test/cli-journeys/README.md @@ -43,6 +43,8 @@ pnpm test:journeys | `namespace-statements.e2e.test.ts` | **Namespace statements (Postgres)**: N1 renames namespace `billing` to `finance` with `migration plan --rename billing:finance`, re-runs `migration.ts` byte for byte and keeps the rows; N2 removes `billing` and `db update` asks about each model, then `--delete billing` drops the tables and the schema; N3 plans the same delete with `migration plan` and re-runs its `migration.ts` | | `rename-statements-migration.e2e.test.ts` | **Rename statements on the command line (Postgres)**: seed `Profile` with rows, a unique field, a secondary index, a check, row-level security with a policy, and a foreign key from `Post`; the destination renames the model to `User` and its field `name` to `fullName`. Journey S1 plans `migration plan --rename Profile:User --rename User.name:User.fullName`: only widening renames of the table, the column and the objects named after them, both statements reported as applied, and re-running the written `migration.ts` reproduces `ops.json` and `migration.json` byte for byte; after `migrate` the rows and every object are under the new names, a further plan is empty and `db verify --schema-only` is clean. Journey S2 applies the same statements with `db update` and no consent, checks that `db update --dry-run` without statements then plans nothing, and refuses the statements on a second run (`MIGRATION.STATEMENT_UNRESOLVED`). Journey S3 covers the errors: unresolved names, a field named through its old model (the message gives the corrected statement), and `db update --db ` with no snapshot to resolve against (`MIGRATION.STATEMENT_ORIGIN_UNKNOWN`, advising `--advance-ref`) | | `rename-statements-migration.sqlite.e2e.test.ts` | The SQLite twin of journeys S1 and S2, driven through a file database and the SQLite facade config: the table and column renames, the index on the column dropped and created under its destination name, the foreign key from `Post` following the table, the byte-identical re-emit, the empty `db update --dry-run` after the renames, and the second `db update` refused | +| `value-object-checks.e2e.test.ts` | **Value object and discriminator checks (Postgres)**, each journey through `migration plan` and `migrate`, and through `db update`: V1 creates the check on a value object column; a row the ORM writes is accepted, and a row with an extra key or a missing member is refused naming the check. V2 adds a member, then makes one nullable: each drops the old check and adds the new one on a table whose rows fit. V3 adds a required member to a table holding a row without it: the apply fails naming the check and changes nothing. V4 adds a variant: the discriminator check is replaced and the new value is accepted. V5 changes a value object only an external table holds: the storage hash changes, the migration has no operations, and the apply moves the marker. Each ends with `db verify --schema-only` clean and a further plan empty | +| `value-object-validator.mongo.e2e.test.ts` | **A value object change on MongoDB**: adding a member to a value object updates the collection validator, through `migration plan` and `migrate` (M1) and through `db update` (M2); `db verify` is clean and a further plan is empty | ### Graph features and refs diff --git a/test/integration/test/cli-journeys/plans-with-no-operations.e2e.test.ts b/test/integration/test/cli-journeys/plans-with-no-operations.e2e.test.ts new file mode 100644 index 000000000000..ce09b18f98f5 --- /dev/null +++ b/test/integration/test/cli-journeys/plans-with-no-operations.e2e.test.ts @@ -0,0 +1,161 @@ +/** + * A contract change that changes the storage hash but needs no database operation (Postgres). + * + * `auth_users` is a table the application does not manage (`@@control(external)`): the planner plans nothing for it, but its columns are part of the contract's storage. Adding a column the table already has changes the storage hash and needs no operation. + * + * Journey N1 plans the change: `migration plan` writes a migration with no operations, names the table that changed, and `migrate` moves the marker to the new hash. Journey N2 applies the same change with `db update`. + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'pathe'; +import { describe, expect, it } from 'vitest'; +import { withTempDir } from '../utils/cli-test-helpers'; +import { + type JourneyContext, + latestMigrationDirName, + parseJsonOutput, + planMigrationAndSelfEmit, + readEmittedContractStorageHash, + runContractEmit, + runDbUpdate, + runDbVerify, + runMigrate, + runMigrationPlan, + setupJourney, + sql, + timeouts, + useDevDatabase, +} from '../utils/journey-test-helpers'; + +const AUTH_USERS_SQL = 'CREATE TABLE public.auth_users (id integer PRIMARY KEY, email text)'; + +const FROM_PSL = `// use prisma-8 + +model User { + id Int @id + email String +} + +model AuthUser { + id Int @id + + @@map("auth_users") + @@control(external) +} +`; + +const TO_PSL = FROM_PSL.replace(' id Int @id\n\n', ' id Int @id\n email String?\n\n'); + +interface NoOperationsPlan { + readonly operations: readonly unknown[]; + readonly changedStorageEntities: readonly unknown[]; + readonly summary: string; +} + +async function emitContract(ctx: JourneyContext, psl: string, label: string): Promise { + writeFileSync(join(ctx.testDir, 'contract.prisma'), psl, 'utf-8'); + const emit = await runContractEmit(ctx); + expect(emit.exitCode, `${label}: emit: ${emit.stderr}`).toBe(0); +} + +async function markerHash(connectionString: string): Promise { + const { rows } = await sql( + connectionString, + "SELECT core_hash FROM prisma_contract.marker WHERE space = 'app'", + ); + return rows[0]?.['core_hash']; +} + +withTempDir(({ createTempDir }) => { + describe('Journey N1 (Postgres): migration plan writes a migration with no operations', () => { + const db = useDevDatabase({ onReady: (cs) => sql(cs, AUTH_USERS_SQL) }); + + it( + 'names the changed table, and migrate moves the marker to the new hash', + async () => { + const ctx = setupJourney({ + connectionString: db.connectionString, + createTempDir, + contractMode: 'psl', + }); + await emitContract(ctx, FROM_PSL, 'N1.01'); + const initial = await planMigrationAndSelfEmit(ctx, ['--name', 'initial']); + expect(initial.exitCode, `N1.02: plan initial: ${initial.stderr}`).toBe(0); + expect((await runMigrate(ctx)).exitCode, 'N1.03: migrate initial').toBe(0); + const origin = latestMigrationDirName(ctx); + await emitContract(ctx, TO_PSL, 'N1.04'); + + const plan = await runMigrationPlan(ctx, ['--from', origin, '--name', 'email', '--json']); + expect(plan.exitCode, `N1.05: plan: ${plan.stderr}`).toBe(0); + expect(parseJsonOutput(plan), 'N1.05: no operations').toMatchObject({ + operations: [], + changedStorageEntities: [ + { + plane: 'storage', + namespaceId: 'public', + entityKind: 'table', + entityName: 'auth_users', + }, + ], + summary: expect.stringContaining('What changed: table "public.auth_users".'), + }); + const dir = join(ctx.testDir, 'migrations/app', latestMigrationDirName(ctx)); + expect(JSON.parse(readFileSync(join(dir, 'ops.json'), 'utf-8')), 'N1.05: ops.json').toEqual( + [], + ); + + const applied = await runMigrate(ctx); + expect(applied.exitCode, `N1.06: migrate: ${applied.stderr}`).toBe(0); + expect(await markerHash(db.connectionString), 'N1.06: the marker').toBe( + readEmittedContractStorageHash({ ...ctx, outputDir: ctx.testDir }), + ); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `N1.07: db verify: ${verify.stderr}`).toBe(0); + }, + timeouts.spinUpPpgDev, + ); + }); + + describe('Journey N2 (Postgres): db update applies a change with no operations', () => { + const db = useDevDatabase({ onReady: (cs) => sql(cs, AUTH_USERS_SQL) }); + + it( + 'names the changed table and moves the marker to the new hash', + async () => { + const ctx = setupJourney({ + connectionString: db.connectionString, + createTempDir, + contractMode: 'psl', + }); + await emitContract(ctx, FROM_PSL, 'N2.01'); + const initial = await runDbUpdate(ctx); + expect(initial.exitCode, `N2.02: db update initial: ${initial.stderr}`).toBe(0); + await emitContract(ctx, TO_PSL, 'N2.03'); + + const update = await runDbUpdate(ctx, ['--json']); + expect(update.exitCode, `N2.04: db update: ${update.stderr}`).toBe(0); + expect( + parseJsonOutput>(update), + 'N2.04: names the changed table', + ).toMatchObject({ + plan: { operations: [] }, + changedStorageEntities: [ + { + plane: 'storage', + namespaceId: 'public', + entityKind: 'table', + entityName: 'auth_users', + }, + ], + summary: expect.stringContaining('What changed: table "public.auth_users".'), + }); + expect(await markerHash(db.connectionString), 'N2.04: the marker').toBe( + readEmittedContractStorageHash({ ...ctx, outputDir: ctx.testDir }), + ); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `N2.05: db verify: ${verify.stderr}`).toBe(0); + }, + timeouts.spinUpPpgDev, + ); + }); +}); diff --git a/test/integration/test/cli-journeys/plans-with-no-operations.mongo.e2e.test.ts b/test/integration/test/cli-journeys/plans-with-no-operations.mongo.e2e.test.ts new file mode 100644 index 000000000000..2ce979d7942c --- /dev/null +++ b/test/integration/test/cli-journeys/plans-with-no-operations.mongo.e2e.test.ts @@ -0,0 +1,163 @@ +/** + * A contract change that changes the storage hash but needs no database operation (MongoDB). + * + * `auth_users` is a collection the application does not manage (`controlPolicy: 'external'`): the planner plans nothing for it, but its indexes are part of the contract's storage. Adding an index the collection already has changes the storage hash and needs no operation. + * + * Journey N1 plans the change: `migration plan` writes a migration with no operations, names the collection that changed, and `migrate` moves the marker to the new hash. Journey N2 applies the same change with `db update`. + */ + +import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { timeouts } from '@repo/test-utils'; +import { type Db, MongoClient } from 'mongodb'; +import { MongoMemoryReplSet } from 'mongodb-memory-server'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { fixtureAppDir } from '../utils/cli-test-helpers'; +import { + type JourneyContext, + latestMigrationDirName, + parseJsonOutput, + readEmittedContractStorageHash, + runContractEmit, + runDbUpdate, + runDbVerify, + runMigrate, + runMigrationPlan, +} from '../utils/journey-test-helpers'; + +const FIXTURES_DIR = join(fixtureAppDir, 'fixtures/mongo-cli-journeys'); + +interface NoOperationsPlan { + readonly operations: readonly unknown[]; + readonly changedStorageEntities: readonly unknown[]; + readonly summary: string; +} + +function withDatabase(baseUri: string, dbName: string): string { + const url = new URL(baseUri); + url.pathname = `/${dbName}`; + return url.toString(); +} + +function setupProject(connectionString: string): JourneyContext { + const testDir = join( + fixtureAppDir, + `test-mongo-no-operations-${Date.now()}-${Math.random().toString(36).slice(2)}`, + ); + const outputDir = join(testDir, 'output'); + mkdirSync(outputDir, { recursive: true }); + mkdirSync(join(testDir, 'migrations'), { recursive: true }); + writeFileSync( + join(testDir, 'package.json'), + JSON.stringify({ name: 'mongo-no-operations-journey', private: true, type: 'module' }), + 'utf-8', + ); + const config = readFileSync(join(FIXTURES_DIR, 'prisma.config.with-db.ts'), 'utf-8').replace( + /\{\{DB_URL\}\}/g, + () => connectionString, + ); + const configPath = join(testDir, 'prisma.config.ts'); + writeFileSync(configPath, config, 'utf-8'); + return { testDir, configPath, outputDir }; +} + +async function emitContract(ctx: JourneyContext, fixture: string, label: string): Promise { + copyFileSync(join(FIXTURES_DIR, fixture), join(ctx.testDir, 'contract.ts')); + const emitted = await runContractEmit(ctx); + expect(emitted.exitCode, `${label}: emit: ${emitted.stderr}`).toBe(0); +} + +async function seedAuthUsers(db: Db): Promise { + await db.createCollection('auth_users'); + await db.collection('auth_users').createIndex({ email: 1 }, { unique: true }); +} + +async function markerHash(db: Db): Promise { + const marker = await db + .collection('_prisma_migrations') + .findOne({ space: 'app', storageHash: { $exists: true } }); + return marker?.['storageHash']; +} + +describe('Journeys (MongoDB): a plan with no operations', { + timeout: timeouts.spinUpMongoMemoryServer, +}, () => { + let replSet: MongoMemoryReplSet; + let client: MongoClient; + const created = new Set(); + + beforeAll(async () => { + replSet = await MongoMemoryReplSet.create({ + instanceOpts: [ + { launchTimeout: timeouts.spinUpMongoMemoryServer, storageEngine: 'wiredTiger' }, + ], + replSet: { count: 1, storageEngine: 'wiredTiger' }, + }); + client = new MongoClient(replSet.getUri()); + await client.connect(); + }, timeouts.spinUpMongoMemoryServer); + + afterEach(async () => { + for (const dir of created) { + await rm(dir, { recursive: true, force: true }).catch(() => {}); + } + created.clear(); + }); + + afterAll(async () => { + await client?.close().catch(() => {}); + await replSet?.stop().catch(() => {}); + }, timeouts.spinUpMongoMemoryServer); + + it('N1: migration plan names the changed collection, and migrate moves the marker', async () => { + const dbName = 'mongo_no_operations_plan'; + const db = client.db(dbName); + await seedAuthUsers(db); + const ctx = setupProject(withDatabase(replSet.getUri(), dbName)); + created.add(ctx.testDir); + await emitContract(ctx, 'contract-external-auth-users.ts', 'N1.01'); + const initial = await runMigrationPlan(ctx, ['--name', 'initial']); + expect(initial.exitCode, `N1.02: plan initial: ${initial.stderr}`).toBe(0); + const applyInitial = await runMigrate(ctx); + expect(applyInitial.exitCode, `N1.03: migrate initial: ${applyInitial.stderr}`).toBe(0); + const origin = latestMigrationDirName(ctx); + await emitContract(ctx, 'contract-external-auth-users-indexed.ts', 'N1.04'); + + const plan = await runMigrationPlan(ctx, ['--from', origin, '--name', 'index', '--json']); + expect(plan.exitCode, `N1.05: plan: ${plan.stderr}`).toBe(0); + expect(parseJsonOutput(plan), 'N1.05: no operations').toMatchObject({ + operations: [], + changedStorageEntities: [ + expect.objectContaining({ entityKind: 'collection', entityName: 'auth_users' }), + ], + summary: expect.stringContaining('collection "'), + }); + const dir = join(ctx.testDir, 'migrations/app', latestMigrationDirName(ctx)); + expect(JSON.parse(readFileSync(join(dir, 'ops.json'), 'utf-8')), 'N1.05: ops.json').toEqual([]); + + const applied = await runMigrate(ctx); + expect(applied.exitCode, `N1.06: migrate: ${applied.stderr}`).toBe(0); + expect(await markerHash(db), 'N1.06: the marker').toBe(readEmittedContractStorageHash(ctx)); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `N1.07: db verify: ${verify.stderr}`).toBe(0); + }); + + it('N2: db update moves the marker to the new hash', async () => { + const dbName = 'mongo_no_operations_update'; + const db = client.db(dbName); + await seedAuthUsers(db); + const ctx = setupProject(withDatabase(replSet.getUri(), dbName)); + created.add(ctx.testDir); + await emitContract(ctx, 'contract-external-auth-users.ts', 'N2.01'); + const initial = await runDbUpdate(ctx); + expect(initial.exitCode, `N2.02: db update initial: ${initial.stderr}`).toBe(0); + await emitContract(ctx, 'contract-external-auth-users-indexed.ts', 'N2.03'); + + const update = await runDbUpdate(ctx); + expect(update.exitCode, `N2.04: db update: ${update.stderr}`).toBe(0); + expect(await markerHash(db), 'N2.04: the marker').toBe(readEmittedContractStorageHash(ctx)); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `N2.05: db verify: ${verify.stderr}`).toBe(0); + }); +}); diff --git a/test/integration/test/cli-journeys/plans-with-no-operations.sqlite.e2e.test.ts b/test/integration/test/cli-journeys/plans-with-no-operations.sqlite.e2e.test.ts new file mode 100644 index 000000000000..f07ecfc06068 --- /dev/null +++ b/test/integration/test/cli-journeys/plans-with-no-operations.sqlite.e2e.test.ts @@ -0,0 +1,154 @@ +/** + * A contract change that changes the storage hash but needs no database operation (SQLite). + * + * SQLite derives no check for an enum, but the enum's value set is part of the contract's storage. Adding a member changes the storage hash and needs no operation. + * + * Journey N1 plans the change: `migration plan` writes a migration with no operations, names the value set that changed, and `migrate` moves the marker to the new hash. Journey N2 applies the same change with `db update`. + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { DatabaseSync } from 'node:sqlite'; +import { join } from 'pathe'; +import { describe, expect, it } from 'vitest'; +import { withTempDir, writeProjectManifest } from '../utils/cli-test-helpers'; +import { + type JourneyContext, + latestMigrationDirName, + parseJsonOutput, + planMigrationAndSelfEmit, + readEmittedContractStorageHash, + runContractEmit, + runDbUpdate, + runDbVerify, + runMigrate, + runMigrationPlan, + sqlitePslConfigFixture, + timeouts, +} from '../utils/journey-test-helpers'; + +const FROM_PSL = `// use prisma-8 + +enum Role { + @@type("sqlite/text@1") + Admin = "admin" + Member = "member" +} + +model User { + id Int @id + role Role +} +`; + +const TO_PSL = FROM_PSL.replace( + ' Member = "member"\n', + ' Member = "member"\n Guest = "guest"\n', +); + +interface NoOperationsPlan { + readonly operations: readonly unknown[]; + readonly changedStorageEntities: readonly unknown[]; + readonly summary: string; +} + +type SqliteJourney = JourneyContext & { readonly dbPath: string }; + +function setupSqliteJourney(createTempDir: () => string): SqliteJourney { + const testDir = createTempDir(); + const dbPath = join(testDir, 'journey.db'); + const configPath = join(testDir, 'prisma.config.ts'); + const config = readFileSync(sqlitePslConfigFixture, 'utf-8').replace('{{DB_PATH}}', () => dbPath); + writeFileSync(configPath, config, 'utf-8'); + writeProjectManifest(testDir); + return { testDir, configPath, outputDir: testDir, dbPath }; +} + +async function emitContract(ctx: JourneyContext, psl: string, label: string): Promise { + writeFileSync(join(ctx.testDir, 'contract.prisma'), psl, 'utf-8'); + const emit = await runContractEmit(ctx); + expect(emit.exitCode, `${label}: emit: ${emit.stderr}`).toBe(0); +} + +function markerHash(dbPath: string): unknown { + const db = new DatabaseSync(dbPath); + try { + return db.prepare(`SELECT core_hash FROM "_prisma_marker" WHERE space = 'app'`).get()?.[ + 'core_hash' + ]; + } finally { + db.close(); + } +} + +withTempDir(({ createTempDir }) => { + describe('Journey N1 (SQLite): migration plan writes a migration with no operations', () => { + it( + 'names the changed value set, and migrate moves the marker to the new hash', + async () => { + const ctx = setupSqliteJourney(createTempDir); + await emitContract(ctx, FROM_PSL, 'N1.01'); + const initial = await planMigrationAndSelfEmit(ctx, ['--name', 'initial']); + expect(initial.exitCode, `N1.02: plan initial: ${initial.stderr}`).toBe(0); + expect((await runMigrate(ctx)).exitCode, 'N1.03: migrate initial').toBe(0); + const origin = latestMigrationDirName(ctx); + await emitContract(ctx, TO_PSL, 'N1.04'); + + const plan = await runMigrationPlan(ctx, ['--from', origin, '--name', 'guest', '--json']); + expect(plan.exitCode, `N1.05: plan: ${plan.stderr}`).toBe(0); + const planned = parseJsonOutput(plan); + expect( + { + operations: planned.operations, + changedStorageEntities: planned.changedStorageEntities, + summary: planned.summary, + }, + 'N1.05: no operations', + ).toEqual({ + operations: [], + changedStorageEntities: [ + { plane: 'storage', entityKind: 'valueSet', entityName: 'Role' }, + ], + summary: expect.stringContaining('What changed: value set "Role".'), + }); + const dir = join(ctx.testDir, 'migrations/app', latestMigrationDirName(ctx)); + expect(JSON.parse(readFileSync(join(dir, 'ops.json'), 'utf-8')), 'N1.05: ops.json').toEqual( + [], + ); + + const applied = await runMigrate(ctx); + expect(applied.exitCode, `N1.06: migrate: ${applied.stderr}`).toBe(0); + expect(markerHash(ctx.dbPath), 'N1.06: the marker').toBe( + readEmittedContractStorageHash(ctx), + ); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `N1.07: db verify: ${verify.stderr}`).toBe(0); + }, + timeouts.spinUpPpgDev, + ); + }); + + describe('Journey N2 (SQLite): db update applies a change with no operations', () => { + it( + 'names the changed value set and moves the marker to the new hash', + async () => { + const ctx = setupSqliteJourney(createTempDir); + await emitContract(ctx, FROM_PSL, 'N2.01'); + const initial = await runDbUpdate(ctx); + expect(initial.exitCode, `N2.02: db update initial: ${initial.stderr}`).toBe(0); + await emitContract(ctx, TO_PSL, 'N2.03'); + + const update = await runDbUpdate(ctx); + expect(update.exitCode, `N2.04: db update: ${update.stderr}`).toBe(0); + expect(`${update.stdout}${update.stderr}`, 'N2.04: names the changed value set').toContain( + 'The storage the contract describes changed, but the database needs no operation for it, so none ran and the database was signed with the new contract. What changed: value set "Role".', + ); + expect(markerHash(ctx.dbPath), 'N2.04: the marker').toBe( + readEmittedContractStorageHash(ctx), + ); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `N2.05: db verify: ${verify.stderr}`).toBe(0); + }, + timeouts.spinUpPpgDev, + ); + }); +}); diff --git a/test/integration/test/cli-journeys/value-object-checks.e2e.test.ts b/test/integration/test/cli-journeys/value-object-checks.e2e.test.ts new file mode 100644 index 000000000000..2b18055a3530 --- /dev/null +++ b/test/integration/test/cli-journeys/value-object-checks.e2e.test.ts @@ -0,0 +1,554 @@ +/** + * Value objects and discriminators are storage entities, and Postgres enforces them with derived checks. + * + * `User.address` holds an `Address` value object in a `jsonb` column. On a managed table the build derives a check on the column, `user_address_shape_`, which accepts the documents the ORM writes and refuses any other shape. `Task` is a base model whose variants are stored in their own tables; its discriminator column `type` gets a check on the variants' values, `task_type_check_`. + * + * Every journey runs twice: once through `migration plan` and `migrate`, once through `db update`. Each ends with `db verify --schema-only` clean and a further plan empty. + * + * - V1: the first migration creates the value object check. A row the ORM writes is accepted; a row with a key `Address` does not declare is refused, naming the check. + * - V2: adding a member, and then making a member nullable, each drop the old check and add the new one, on a table whose rows fit the new shape. + * - V3: adding a required member to a table that holds a row without it fails at apply, names the check, the table and the column, gives the query that finds the rows and the route that keeps them, and changes nothing. Following that route applies the change and keeps the row. + * - V4: adding a variant replaces the discriminator check, and the new variant's value is accepted. + * - V5: changing a value object that only an external table's column holds changes the storage hash and needs no operation: `migration plan` writes a migration with no operations, and `migrate` moves the marker. + */ + +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'pathe'; +import { describe, expect, it } from 'vitest'; +import { withTempDir } from '../utils/cli-test-helpers'; +import { + engineError, + getMigrationDirs, + type JourneyContext, + latestMigrationDirName, + parseJsonOutput, + planMigrationAndSelfEmit, + readEmittedContractStorageHash, + runContractEmit, + runDbUpdate, + runDbVerify, + runMigrate, + runMigrationPlan, + selfEmitMigration, + setupJourney, + sql, + timeouts, + useDevDatabase, +} from '../utils/journey-test-helpers'; +import { createRowThroughOrm } from '../utils/journey-workspace-helpers'; + +type Via = 'migrate' | 'db update'; +const VIAS: readonly Via[] = ['migrate', 'db update']; + +function userContract(addressMembers: string): string { + return `// use prisma-8 + +type Address { +${addressMembers}} + +model User { + id Int @id + address Address? + @@map("user") +} +`; +} + +const ADDRESS = userContract(' street String\n city String\n'); +const ADDRESS_WITH_ZIP = userContract(' street String\n city String\n zip String?\n'); +const ADDRESS_WITH_OPTIONAL_CITY = userContract( + ' street String\n city String?\n zip String?\n', +); +const ADDRESS_WITH_COUNTRY = userContract(' street String\n city String\n country String\n'); + +function taskContract(variants: readonly string[]): string { + const models = variants.map( + (name) => ` +model ${name} { + note String? + @@base(Task, "${name.toLowerCase()}") + @@map("${name.toLowerCase()}") +} +`, + ); + return `// use prisma-8 + +model Task { + id Int @id + title String + type String + @@discriminator(type) + @@map("task") +} +${models.join('')}`; +} + +const ONE_VARIANT = taskContract(['Bug']); +const TWO_VARIANTS = taskContract(['Bug', 'Feature']); + +const AUTH_PROFILES_SQL = 'CREATE TABLE public.auth_profiles (id integer PRIMARY KEY, home jsonb)'; + +function externalContract(homeMembers: string): string { + return `// use prisma-8 + +type Home { +${homeMembers}} + +model User { + id Int @id + @@map("user") +} + +model AuthProfile { + id Int @id + home Home? + @@map("auth_profiles") + @@control(external) +} +`; +} + +const HOME = externalContract(' city String\n'); +const HOME_WITH_ZIP = externalContract(' city String\n zip String?\n'); + +interface PlannedOperation { + readonly id: string; + readonly label: string; +} + +async function emit(ctx: JourneyContext, psl: string, label: string): Promise { + writeFileSync(join(ctx.testDir, 'contract.prisma'), psl, 'utf-8'); + const emitted = await runContractEmit(ctx); + expect(emitted.exitCode, `${label}: emit: ${emitted.stderr}`).toBe(0); +} + +function journey(db: { readonly connectionString: string }, createTempDir: () => string) { + return setupJourney({ + connectionString: db.connectionString, + createTempDir, + contractMode: 'psl', + }); +} + +/** Plans from the latest migration, or from an empty database before the first one. */ +function fromLatest(ctx: JourneyContext): readonly string[] { + return getMigrationDirs(ctx).length === 0 ? [] : ['--from', latestMigrationDirName(ctx)]; +} + +/** Applies the emitted contract through `via` and returns the labels of the operations it planned. */ +async function apply(ctx: JourneyContext, via: Via, label: string): Promise { + if (via === 'migrate') { + const plan = await planMigrationAndSelfEmit(ctx, [ + ...fromLatest(ctx), + '--name', + label.replace(/\W/g, '-'), + '--json', + ]); + expect(plan.exitCode, `${label}: plan: ${plan.stderr}`).toBe(0); + const migrate = await runMigrate(ctx); + expect(migrate.exitCode, `${label}: migrate: ${migrate.stderr}`).toBe(0); + return plannedLabels(ctx); + } + const update = await runDbUpdate(ctx, ['--json']); + expect(update.exitCode, `${label}: db update: ${JSON.stringify(engineError(update))}`).toBe(0); + return parseJsonOutput<{ plan: { operations: readonly PlannedOperation[] } }>( + update, + ).plan.operations.map((operation) => operation.label); +} + +function plannedLabels(ctx: JourneyContext): readonly string[] { + const ops = JSON.parse( + readFileSync( + join(ctx.testDir, 'migrations/app', latestMigrationDirName(ctx), 'ops.json'), + 'utf-8', + ), + ) as readonly PlannedOperation[]; + return ops.map((operation) => operation.label); +} + +async function expectSettled(ctx: JourneyContext, via: Via, label: string): Promise { + const verify = await runDbVerify(ctx, ['--schema-only']); + expect(verify.exitCode, `${label}: db verify --schema-only: ${verify.stderr}`).toBe(0); + if (via === 'migrate') { + const count = getMigrationDirs(ctx).length; + const fresh = await runMigrationPlan(ctx, [...fromLatest(ctx), '--json']); + expect(fresh.exitCode, `${label}: plan with no change: ${fresh.stderr}`).toBe(0); + expect(parseJsonOutput<{ noOp: boolean }>(fresh).noOp, `${label}: plan is empty`).toBe(true); + expect(getMigrationDirs(ctx), `${label}: nothing written`).toHaveLength(count); + return; + } + const dryRun = await runDbUpdate(ctx, ['--dry-run', '--json']); + expect(dryRun.exitCode, `${label}: db update --dry-run: ${dryRun.stderr}`).toBe(0); + expect( + parseJsonOutput<{ plan: { operations: readonly PlannedOperation[] } }>(dryRun).plan.operations, + `${label}: nothing left to plan`, + ).toEqual([]); +} + +const GIVE_EVERY_ADDRESS_A_COUNTRY = `rawSql({ + id: 'data.user.address.country', + label: 'Give every address a country', + operationClass: 'data', + target: { id: 'postgres' }, + precheck: [], + execute: [ + { + description: 'give every address a country', + sql: \`UPDATE "public"."user" SET address = address || '{"country": "NO"}' WHERE address IS NOT NULL\`, + }, + ], + postcheck: [], + }), + `; + +/** Keeps the rows as the failure's advice says: in the failed migration.ts, or for `db update` in the database by hand. */ +async function followTheAdvice( + ctx: JourneyContext, + via: Via, + oldCheck: string | undefined, + connectionString: string, +): Promise { + if (via === 'db update') { + await sql(connectionString, `ALTER TABLE "public"."user" DROP CONSTRAINT "${oldCheck}"`); + await sql( + connectionString, + `UPDATE "public"."user" SET address = address || '{"country": "NO"}' WHERE address IS NOT NULL`, + ); + const update = await runDbUpdate(ctx, ['--json']); + expect(update.exitCode, `V3.06: db update: ${JSON.stringify(engineError(update))}`).toBe(0); + return; + } + const dir = join(ctx.testDir, 'migrations/app', latestMigrationDirName(ctx)); + const path = join(dir, 'migration.ts'); + const written = readFileSync(path, 'utf-8'); + const filled = written + .replace(/import \{ ([^}]*) \} from '([^']+\/migration)';/, "import { $1, rawSql } from '$2';") + .replace('this.addCheckConstraint(', `${GIVE_EVERY_ADDRESS_A_COUNTRY}this.addCheckConstraint(`); + expect(filled, 'V3.06: the update sits before the new check').toContain('rawSql({'); + expect(filled, 'V3.06: rawSql is imported').toContain('rawSql }'); + writeFileSync(path, filled); + const emitted = await selfEmitMigration(ctx, ['--dir', dir, '--config', ctx.configPath]); + expect(emitted.exitCode, `V3.06: run migration.ts: ${emitted.stdout}${emitted.stderr}`).toBe(0); + const migrate = await runMigrate(ctx, ['--json']); + expect(migrate.exitCode, `V3.06: migrate: ${JSON.stringify(engineError(migrate))}`).toBe(0); +} + +async function checkNames(connectionString: string, table: string): Promise { + const { rows } = await sql( + connectionString, + `SELECT conname FROM pg_constraint WHERE conrelid = $1::regclass AND contype = 'c' ORDER BY conname`, + [`public."${table}"`], + ); + return rows.map((row) => String(row['conname'])); +} + +async function markerHash(connectionString: string): Promise { + const { rows } = await sql( + connectionString, + "SELECT core_hash FROM prisma_contract.marker WHERE space = 'app'", + ); + return rows[0]?.['core_hash']; +} + +async function insertError(connectionString: string, query: string): Promise { + try { + await sql(connectionString, query); + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + return 'the insert was accepted'; +} + +/** Writes a user through the ORM of the emitted contract. */ +function createUserThroughOrm( + ctx: JourneyContext, + connectionString: string, + row: Record, +): Promise { + return createRowThroughOrm({ + contractJson: JSON.parse(readFileSync(join(ctx.testDir, 'contract.json'), 'utf-8')), + connectionString, + model: 'User', + row, + }); +} + +withTempDir(({ createTempDir }) => { + describe.each(VIAS)('V1 (%s): a value object column gets a check', (via) => { + const db = useDevDatabase(); + + it( + 'creates the check, accepts a row the ORM writes and refuses a row with an extra key', + async () => { + const ctx = journey(db, createTempDir); + await emit(ctx, ADDRESS, 'V1.01'); + const labels = await apply(ctx, via, 'V1.02'); + expect(labels.join('\n'), 'V1.02: the table is created').toContain('Create table "user"'); + if (via === 'migrate') { + expect( + readFileSync( + join(ctx.testDir, 'migrations/app', latestMigrationDirName(ctx), 'ops.json'), + 'utf-8', + ), + 'V1.02: the migration writes the check', + ).toMatch(/CONSTRAINT \\"user_address_shape_[0-9a-f]{8}\\" CHECK/); + } + expect(await checkNames(db.connectionString, 'user'), 'V1.02: the check').toEqual([ + expect.stringMatching(/^user_address_shape_[0-9a-f]{8}$/), + ]); + + expect( + await createUserThroughOrm(ctx, db.connectionString, { + id: 1, + address: { street: 'Main St', city: 'Oslo' }, + }), + 'V1.03: the ORM writes an address', + ).toMatchObject({ id: 1, address: { street: 'Main St', city: 'Oslo' } }); + await sql(db.connectionString, `INSERT INTO "user" (id, address) VALUES (2, NULL)`); + + const [check] = await checkNames(db.connectionString, 'user'); + expect( + await insertError( + db.connectionString, + `INSERT INTO "user" (id, address) VALUES (3, '{"street": "Main St", "city": "Oslo", "floor": 2}')`, + ), + 'V1.04: an extra key is refused', + ).toContain(`violates check constraint "${check}"`); + expect( + await insertError( + db.connectionString, + `INSERT INTO "user" (id, address) VALUES (3, '{"street": "Main St"}')`, + ), + 'V1.05: a missing member is refused', + ).toContain(`violates check constraint "${check}"`); + + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `V1.06: db verify: ${verify.stderr}`).toBe(0); + await expectSettled(ctx, via, 'V1.07'); + }, + timeouts.spinUpPpgDev, + ); + }); + + describe.each(VIAS)('V2 (%s): changing a value object replaces its check', (via) => { + const db = useDevDatabase(); + + it( + 'drops the old check and adds the new one when a member is added, then when one becomes nullable', + async () => { + const ctx = journey(db, createTempDir); + await emit(ctx, ADDRESS, 'V2.01'); + await apply(ctx, via, 'V2.02'); + await sql( + db.connectionString, + `INSERT INTO "user" (id, address) VALUES (1, '{"street": "Main St", "city": "Oslo"}')`, + ); + const [first] = await checkNames(db.connectionString, 'user'); + + await emit(ctx, ADDRESS_WITH_ZIP, 'V2.03'); + expect(await apply(ctx, via, 'V2.04'), 'V2.04: a member added').toEqual([ + `Drop check constraint "${first}" on "user"`, + expect.stringMatching( + /^Add check constraint "user_address_shape_[0-9a-f]{8}" on "user"$/, + ), + ]); + const [second] = await checkNames(db.connectionString, 'user'); + expect(second, 'V2.04: a new check').not.toBe(first); + await sql( + db.connectionString, + `INSERT INTO "user" (id, address) VALUES (2, '{"street": "Elm St", "city": "Bergen", "zip": "5003"}')`, + ); + await expectSettled(ctx, via, 'V2.05'); + + await emit(ctx, ADDRESS_WITH_OPTIONAL_CITY, 'V2.06'); + expect(await apply(ctx, via, 'V2.07'), 'V2.07: a member made nullable').toEqual([ + `Drop check constraint "${second}" on "user"`, + expect.stringMatching( + /^Add check constraint "user_address_shape_[0-9a-f]{8}" on "user"$/, + ), + ]); + await sql( + db.connectionString, + `INSERT INTO "user" (id, address) VALUES (3, '{"street": "Elm St", "city": null}')`, + ); + expect( + (await sql(db.connectionString, 'SELECT id FROM "user" ORDER BY id')).rows, + 'V2.07: the rows are kept', + ).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + await expectSettled(ctx, via, 'V2.08'); + }, + timeouts.spinUpPpgDev, + ); + }); + + describe.each(VIAS)('V3 (%s): a row that does not fit the new check', (via) => { + const db = useDevDatabase(); + + it( + 'fails the apply, names the check and changes nothing', + async () => { + const ctx = journey(db, createTempDir); + await emit(ctx, ADDRESS, 'V3.01'); + await apply(ctx, via, 'V3.02'); + await sql( + db.connectionString, + `INSERT INTO "user" (id, address) VALUES (1, '{"street": "Main St", "city": "Oslo"}')`, + ); + const before = await checkNames(db.connectionString, 'user'); + const marker = await markerHash(db.connectionString); + + await emit(ctx, ADDRESS_WITH_COUNTRY, 'V3.03'); + let failed: Parameters[0]; + if (via === 'migrate') { + const plan = await planMigrationAndSelfEmit(ctx, [ + ...fromLatest(ctx), + '--name', + 'country', + ]); + expect(plan.exitCode, `V3.04: plan: ${plan.stderr}`).toBe(0); + failed = await runMigrate(ctx, ['--json']); + } else { + failed = await runDbUpdate(ctx, ['--json']); + } + expect(failed.exitCode, 'V3.04: the apply fails').not.toBe(0); + const [oldCheck] = before; + const error = engineError(failed); + expect( + error, + 'V3.04: the failure names the check, the table, the column and the route', + ).toMatchObject({ + code: 'MIGRATION.EXECUTION_FAILED', + summary: expect.stringMatching( + /^Rows of table "user" fail check "user_address_shape_[0-9a-f]{8}" on column "address", so the check cannot be added\.$/, + ), + why: expect.stringContaining('Find them with `SELECT * FROM "public"."user" WHERE NOT ('), + nextActions: [ + { + label: + via === 'migrate' + ? expect.stringMatching( + new RegExp( + `^To keep the data, add an operation that updates those rows to fit check "user_address_shape_[0-9a-f]{8}" to \`.+/migration\\.ts\`, after the operation that drops check "${oldCheck}" and before the operation that adds check`, + ), + ) + : expect.stringMatching( + new RegExp( + `cannot keep this data, and no migration ends at the contract the database is at, so .+ cannot plan from it\\. Drop check "${oldCheck}" in the database yourself`, + ), + ), + }, + ], + }); + expect(JSON.stringify(error), 'V3.04: no advice that does not fit').not.toMatch( + /drift|/, + ); + + expect(await checkNames(db.connectionString, 'user'), 'V3.05: the old check stays').toEqual( + before, + ); + expect(await markerHash(db.connectionString), 'V3.05: the marker stays').toBe(marker); + expect( + (await sql(db.connectionString, 'SELECT id, address FROM "user"')).rows, + 'V3.05: the row stays', + ).toEqual([{ id: 1, address: { street: 'Main St', city: 'Oslo' } }]); + + await followTheAdvice(ctx, via, oldCheck, db.connectionString); + expect( + (await sql(db.connectionString, 'SELECT id, address FROM "user"')).rows, + 'V3.06: the row is kept and fits', + ).toEqual([{ id: 1, address: { street: 'Main St', city: 'Oslo', country: 'NO' } }]); + const verify = await runDbVerify(ctx, ['--strict']); + expect(verify.exitCode, `V3.07: db verify --strict: ${verify.stderr}`).toBe(0); + }, + timeouts.spinUpPpgDev, + ); + }); + + describe.each(VIAS)('V4 (%s): adding a variant', (via) => { + const db = useDevDatabase(); + + it( + "replaces the discriminator check, and the new variant's value is accepted", + async () => { + const ctx = journey(db, createTempDir); + await emit(ctx, ONE_VARIANT, 'V4.01'); + await apply(ctx, via, 'V4.02'); + const [first] = await checkNames(db.connectionString, 'task'); + expect(first, 'V4.02: the discriminator check').toMatch(/^task_type_check_[0-9a-f]{8}$/); + await sql( + db.connectionString, + `INSERT INTO "task" (id, title, type) VALUES (1, 'a', 'bug')`, + ); + expect( + await insertError( + db.connectionString, + `INSERT INTO "task" (id, title, type) VALUES (2, 'b', 'feature')`, + ), + 'V4.03: a value no variant names is refused', + ).toContain(`violates check constraint "${first}"`); + + await emit(ctx, TWO_VARIANTS, 'V4.04'); + const labels = await apply(ctx, via, 'V4.05'); + expect( + labels.filter((label) => label.includes('check constraint')), + 'V4.05: the check is replaced', + ).toEqual([ + `Drop check constraint "${first}" on "task"`, + expect.stringMatching(/^Add check constraint "task_type_check_[0-9a-f]{8}" on "task"$/), + ]); + await sql( + db.connectionString, + `INSERT INTO "task" (id, title, type) VALUES (2, 'b', 'feature')`, + ); + expect( + (await sql(db.connectionString, 'SELECT id, type FROM "task" ORDER BY id')).rows, + 'V4.06: both variants are stored', + ).toEqual([ + { id: 1, type: 'bug' }, + { id: 2, type: 'feature' }, + ]); + await expectSettled(ctx, via, 'V4.07'); + }, + timeouts.spinUpPpgDev, + ); + }); + + describe.each(VIAS)('V5 (%s): a value object only an external table holds', (via) => { + const db = useDevDatabase({ onReady: (cs) => sql(cs, AUTH_PROFILES_SQL) }); + + it( + 'changes the storage hash with no operation, and the apply moves the marker', + async () => { + const ctx = journey(db, createTempDir); + await emit(ctx, HOME, 'V5.01'); + await apply(ctx, via, 'V5.02'); + expect(await checkNames(db.connectionString, 'auth_profiles'), 'V5.02: no check').toEqual( + [], + ); + const before = readEmittedContractStorageHash({ ...ctx, outputDir: ctx.testDir }); + + await emit(ctx, HOME_WITH_ZIP, 'V5.03'); + const after = readEmittedContractStorageHash({ ...ctx, outputDir: ctx.testDir }); + expect(after, 'V5.03: the storage hash changes').not.toBe(before); + if (via === 'migrate') { + const plan = await runMigrationPlan(ctx, [...fromLatest(ctx), '--name', 'zip', '--json']); + expect(plan.exitCode, `V5.04: plan: ${plan.stderr}`).toBe(0); + expect( + parseJsonOutput<{ operations: readonly unknown[]; summary: string }>(plan), + 'V5.04: no operations', + ).toMatchObject({ + operations: [], + summary: expect.stringContaining('What changed: value object "public.Home".'), + }); + const migrate = await runMigrate(ctx); + expect(migrate.exitCode, `V5.05: migrate: ${migrate.stderr}`).toBe(0); + } else { + expect(await apply(ctx, via, 'V5.04'), 'V5.04: no operations').toEqual([]); + } + expect(await markerHash(db.connectionString), 'V5.05: the marker').toBe(after); + await expectSettled(ctx, via, 'V5.06'); + }, + timeouts.spinUpPpgDev, + ); + }); +}); diff --git a/test/integration/test/cli-journeys/value-object-validator.mongo.e2e.test.ts b/test/integration/test/cli-journeys/value-object-validator.mongo.e2e.test.ts new file mode 100644 index 000000000000..05abd8a9f910 --- /dev/null +++ b/test/integration/test/cli-journeys/value-object-validator.mongo.e2e.test.ts @@ -0,0 +1,202 @@ +/** + * A value object change updates the MongoDB collection validator, now that value objects are storage entities. + * + * `User.address` holds an `Address` subdocument, and the `users` validator lists its members. Adding `zip` to `Address` changes the storage hash and the validator. Journey M1 plans the change with `migration plan` and applies it with `migrate`; journey M2 applies it with `db update`. Each ends with `db verify` clean and a further plan empty. + */ + +import { mkdirSync, writeFileSync } from 'node:fs'; +import { rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { timeouts } from '@repo/test-utils'; +import { type Db, MongoClient } from 'mongodb'; +import { MongoMemoryReplSet } from 'mongodb-memory-server'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { fixtureAppDir, writeProjectManifest } from '../utils/cli-test-helpers'; +import { + getMigrationDirs, + type JourneyContext, + latestMigrationDirName, + parseJsonOutput, + readEmittedContractStorageHash, + runContractEmit, + runDbUpdate, + runDbVerify, + runMigrate, + runMigrationPlan, +} from '../utils/journey-test-helpers'; +import { writeMongoPslConfig } from '../utils/journey-workspace-helpers'; + +function contractWith(addressMembers: string): string { + return `// use prisma-8 + +type Address { +${addressMembers}} + +model User { + id ObjectId @id @map("_id") + address Address? + @@map("users") +} +`; +} + +const ADDRESS = contractWith(' street String\n city String\n'); +const ADDRESS_WITH_ZIP = contractWith(' street String\n city String\n zip String?\n'); + +function withDatabase(baseUri: string, dbName: string): string { + const url = new URL(baseUri); + url.pathname = `/${dbName}`; + return url.toString(); +} + +function setupProject(connectionString: string): JourneyContext { + const testDir = join( + fixtureAppDir, + `test-mongo-value-object-${Date.now()}-${Math.random().toString(36).slice(2)}`, + ); + const outputDir = join(testDir, 'output'); + mkdirSync(outputDir, { recursive: true }); + mkdirSync(join(testDir, 'migrations'), { recursive: true }); + writeProjectManifest(testDir); + const configPath = writeMongoPslConfig(testDir, connectionString); + return { testDir, configPath, outputDir }; +} + +async function emit(ctx: JourneyContext, psl: string, label: string): Promise { + writeFileSync(join(ctx.testDir, 'contract.prisma'), psl, 'utf-8'); + const emitted = await runContractEmit(ctx); + expect(emitted.exitCode, `${label}: emit: ${emitted.stderr}`).toBe(0); +} + +interface AddressSchema { + readonly properties?: Record; + readonly required?: readonly string[]; +} + +/** The members the `users` validator lists for `address`, and which of them it requires. */ +async function addressInValidator(db: Db): Promise<{ members: string[]; required: string[] }> { + const [collection] = await db.listCollections({ name: 'users' }).toArray(); + const schema = ( + collection as { + options?: { validator?: { $jsonSchema?: { properties?: Record } } }; + } + )?.options?.validator?.$jsonSchema?.properties?.['address']; + const objectSchemas = [schema, ...((schema as { oneOf?: unknown[] })?.oneOf ?? [])].filter( + (entry): entry is AddressSchema => + typeof entry === 'object' && entry !== null && 'properties' in entry, + ); + const [object] = objectSchemas; + return { + members: Object.keys(object?.properties ?? {}).sort(), + required: [...(object?.required ?? [])].sort(), + }; +} + +async function markerHash(db: Db): Promise { + const marker = await db + .collection('_prisma_migrations') + .findOne({ space: 'app', storageHash: { $exists: true } }); + return marker?.['storageHash']; +} + +describe('Journeys (MongoDB): a value object change updates the collection validator', { + timeout: timeouts.spinUpMongoMemoryServer, +}, () => { + let replSet: MongoMemoryReplSet; + let client: MongoClient; + const created = new Set(); + + beforeAll(async () => { + replSet = await MongoMemoryReplSet.create({ + instanceOpts: [ + { launchTimeout: timeouts.spinUpMongoMemoryServer, storageEngine: 'wiredTiger' }, + ], + replSet: { count: 1, storageEngine: 'wiredTiger' }, + }); + client = new MongoClient(replSet.getUri()); + await client.connect(); + }, timeouts.spinUpMongoMemoryServer); + + afterEach(async () => { + for (const dir of created) { + await rm(dir, { recursive: true, force: true }).catch(() => {}); + } + created.clear(); + }); + + afterAll(async () => { + await client?.close().catch(() => {}); + await replSet?.stop().catch(() => {}); + }, timeouts.spinUpMongoMemoryServer); + + it('M1: migration plan and migrate add the member to the validator', async () => { + const dbName = 'mongo_value_object_plan'; + const db = client.db(dbName); + const ctx = setupProject(withDatabase(replSet.getUri(), dbName)); + created.add(ctx.testDir); + await emit(ctx, ADDRESS, 'M1.01'); + const initial = await runMigrationPlan(ctx, ['--name', 'initial']); + expect(initial.exitCode, `M1.02: plan initial: ${initial.stderr}`).toBe(0); + expect((await runMigrate(ctx)).exitCode, 'M1.02: migrate initial').toBe(0); + expect(await addressInValidator(db), 'M1.02: the validator').toEqual({ + members: ['city', 'street'], + required: ['city', 'street'], + }); + await db.collection('users').insertOne({ address: { street: 'Main St', city: 'Oslo' } }); + const origin = latestMigrationDirName(ctx); + + await emit(ctx, ADDRESS_WITH_ZIP, 'M1.03'); + const plan = await runMigrationPlan(ctx, ['--from', origin, '--name', 'zip', '--json']); + expect(plan.exitCode, `M1.04: plan: ${plan.stderr}`).toBe(0); + expect( + parseJsonOutput<{ operations: readonly { label: string }[] }>(plan).operations.map( + ({ label }) => label, + ), + 'M1.04: the validator is updated', + ).toEqual([expect.stringContaining('validator')]); + const migrate = await runMigrate(ctx); + expect(migrate.exitCode, `M1.05: migrate: ${migrate.stderr}`).toBe(0); + + expect(await addressInValidator(db), 'M1.05: the validator lists zip').toEqual({ + members: ['city', 'street', 'zip'], + required: ['city', 'street'], + }); + expect(await markerHash(db), 'M1.05: the marker').toBe(readEmittedContractStorageHash(ctx)); + await db + .collection('users') + .insertOne({ address: { street: 'Elm St', city: 'Bergen', zip: '5003' } }); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `M1.06: db verify: ${verify.stderr}`).toBe(0); + const count = getMigrationDirs(ctx).length; + const fresh = await runMigrationPlan(ctx, ['--from', latestMigrationDirName(ctx), '--json']); + expect(parseJsonOutput<{ noOp: boolean }>(fresh).noOp, 'M1.07: plan is empty').toBe(true); + expect(getMigrationDirs(ctx), 'M1.07: nothing written').toHaveLength(count); + }); + + it('M2: db update adds the member to the validator', async () => { + const dbName = 'mongo_value_object_update'; + const db = client.db(dbName); + const ctx = setupProject(withDatabase(replSet.getUri(), dbName)); + created.add(ctx.testDir); + await emit(ctx, ADDRESS, 'M2.01'); + const initial = await runDbUpdate(ctx); + expect(initial.exitCode, `M2.02: db update initial: ${initial.stderr}`).toBe(0); + await db.collection('users').insertOne({ address: { street: 'Main St', city: 'Oslo' } }); + + await emit(ctx, ADDRESS_WITH_ZIP, 'M2.03'); + const update = await runDbUpdate(ctx, ['--json']); + expect(update.exitCode, `M2.04: db update: ${update.stderr}`).toBe(0); + expect(await addressInValidator(db), 'M2.04: the validator lists zip').toEqual({ + members: ['city', 'street', 'zip'], + required: ['city', 'street'], + }); + expect(await markerHash(db), 'M2.04: the marker').toBe(readEmittedContractStorageHash(ctx)); + const verify = await runDbVerify(ctx); + expect(verify.exitCode, `M2.05: db verify: ${verify.stderr}`).toBe(0); + const dryRun = await runDbUpdate(ctx, ['--dry-run', '--json']); + expect( + parseJsonOutput<{ plan: { operations: readonly unknown[] } }>(dryRun).plan.operations, + 'M2.06: nothing left to plan', + ).toEqual([]); + }); +}); diff --git a/test/integration/test/contract-parity/fixtures/mongo/generated/contract.d.ts b/test/integration/test/contract-parity/fixtures/mongo/generated/contract.d.ts index d12b3b998951..1cf270dfb29d 100644 --- a/test/integration/test/contract-parity/fixtures/mongo/generated/contract.d.ts +++ b/test/integration/test/contract-parity/fixtures/mongo/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'f40b9a31525edcf6ac7faa3ae99f554ca15e128136e74743654436c4b79fb2f6'>; + StorageHashBase<'c4a473ccd6718dc606dfcda2d01643cfdcc79efc8d3ab1e1647001ea6086e133'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -78,14 +78,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -356,6 +358,51 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly kind: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly location: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Point'; + }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly tags: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + readonly many: { readonly elementNullable: false }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Point: { + readonly fields: { + readonly lat: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + readonly lng: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; + }; + }; + }; + }; }; }; }; @@ -497,7 +544,13 @@ type ContractBase = Omit< }; readonly home: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly joinedAt: { readonly nullable: false; @@ -514,7 +567,13 @@ type ContractBase = Omit< }; readonly pastHomes: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly many: { readonly elementNullable: false }; }; readonly pastRoles: { @@ -580,45 +639,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly kind: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly location: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Point' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly tags: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Point: { - readonly fields: { - readonly lat: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly lng: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - }; readonly enum: { readonly Role: { readonly codecId: 'mongo/string@1'; @@ -640,45 +660,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly kind: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly location: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Point' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly tags: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Point: { - readonly fields: { - readonly lat: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - readonly lng: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/double@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/contract-parity/fixtures/mongo/generated/contract.json b/test/integration/test/contract-parity/fixtures/mongo/generated/contract.json index 24a1a2cbba6e..5a86ccc6aef4 100644 --- a/test/integration/test/contract-parity/fixtures/mongo/generated/contract.json +++ b/test/integration/test/contract-parity/fixtures/mongo/generated/contract.json @@ -217,8 +217,11 @@ "home": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "joinedAt": { @@ -251,8 +254,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "pastRoles": { @@ -397,74 +403,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "kind": { - "nullable": true, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - }, - "valueSet": { - "entityKind": "enum", - "entityName": "Role", - "namespaceId": "__unbound__", - "plane": "domain" - } - }, - "location": { - "nullable": true, - "type": { - "kind": "valueObject", - "name": "Point" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "tags": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "Point": { - "fields": { - "lat": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - } - }, - "lng": { - "nullable": false, - "type": { - "codecId": "mongo/double@1", - "kind": "scalar" - } - } - } - } } } } @@ -822,6 +760,77 @@ } } }, + "valueObject": { + "Address": { + "fields": { + "kind": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Role", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "location": { + "nullable": true, + "type": { + "entityKind": "valueObject", + "entityName": "Point", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "tags": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "Point": { + "fields": { + "lat": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + } + }, + "lng": { + "nullable": false, + "type": { + "codecId": "mongo/double@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "Role": { "kind": "valueSet", @@ -836,7 +845,7 @@ "kind": "mongo-database" } }, - "storageHash": "f40b9a31525edcf6ac7faa3ae99f554ca15e128136e74743654436c4b79fb2f6" + "storageHash": "c4a473ccd6718dc606dfcda2d01643cfdcc79efc8d3ab1e1647001ea6086e133" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/contract-parity/fixtures/postgres/generated/contract.d.ts b/test/integration/test/contract-parity/fixtures/postgres/generated/contract.d.ts index c9f5bae1c1b6..fdd9f50e7a46 100644 --- a/test/integration/test/contract-parity/fixtures/postgres/generated/contract.d.ts +++ b/test/integration/test/contract-parity/fixtures/postgres/generated/contract.d.ts @@ -288,14 +288,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -2279,7 +2281,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/contract-parity/fixtures/sqlite/generated/contract.d.ts b/test/integration/test/contract-parity/fixtures/sqlite/generated/contract.d.ts index d55978a030b4..421ef15e0fc2 100644 --- a/test/integration/test/contract-parity/fixtures/sqlite/generated/contract.d.ts +++ b/test/integration/test/contract-parity/fixtures/sqlite/generated/contract.d.ts @@ -140,14 +140,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -1170,7 +1172,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/enum-members/_fixture-mongo/generated/contract.d.ts b/test/integration/test/enum-members/_fixture-mongo/generated/contract.d.ts index d1732754cb72..52afad942da3 100644 --- a/test/integration/test/enum-members/_fixture-mongo/generated/contract.d.ts +++ b/test/integration/test/enum-members/_fixture-mongo/generated/contract.d.ts @@ -47,14 +47,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -202,7 +204,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/enum-members/_fixture-native-enums/generated/contract.d.ts b/test/integration/test/enum-members/_fixture-native-enums/generated/contract.d.ts index 49594122fd37..1c4e44101eae 100644 --- a/test/integration/test/enum-members/_fixture-native-enums/generated/contract.d.ts +++ b/test/integration/test/enum-members/_fixture-native-enums/generated/contract.d.ts @@ -284,14 +284,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -482,7 +484,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/enum-members/_fixture-postgres/generated/contract.d.ts b/test/integration/test/enum-members/_fixture-postgres/generated/contract.d.ts index e186f4f346df..370cca4ee37e 100644 --- a/test/integration/test/enum-members/_fixture-postgres/generated/contract.d.ts +++ b/test/integration/test/enum-members/_fixture-postgres/generated/contract.d.ts @@ -298,14 +298,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -870,7 +872,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/enum-members/_fixture-sqlite/generated/contract.d.ts b/test/integration/test/enum-members/_fixture-sqlite/generated/contract.d.ts index 0854e38fb4d4..b1d42da822db 100644 --- a/test/integration/test/enum-members/_fixture-sqlite/generated/contract.d.ts +++ b/test/integration/test/enum-members/_fixture-sqlite/generated/contract.d.ts @@ -148,14 +148,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly __unbound__: { @@ -387,7 +389,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/enum-order-by/_fixture/generated/contract.d.ts b/test/integration/test/enum-order-by/_fixture/generated/contract.d.ts index e3aca11c0c88..905728a9ca17 100644 --- a/test/integration/test/enum-order-by/_fixture/generated/contract.d.ts +++ b/test/integration/test/enum-order-by/_fixture/generated/contract.d.ts @@ -283,14 +283,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -448,7 +450,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/contract-external-auth-users-indexed.ts b/test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/contract-external-auth-users-indexed.ts new file mode 100644 index 000000000000..80cda9694fb4 --- /dev/null +++ b/test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/contract-external-auth-users-indexed.ts @@ -0,0 +1,23 @@ +import { defineContract, field, index, model } from '@internal/mongo/contract-builder'; + +const User = model('User', { + collection: 'users', + fields: { + _id: field.objectId(), + email: field.string(), + }, +}); + +const AuthUser = model('AuthUser', { + collection: 'auth_users', + controlPolicy: 'external', + fields: { + _id: field.objectId(), + email: field.string(), + }, + indexes: [index({ email: 1 }, { unique: true })], +}); + +export const contract = defineContract({ + models: { User, AuthUser }, +}); diff --git a/test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/contract-external-auth-users.ts b/test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/contract-external-auth-users.ts new file mode 100644 index 000000000000..8dd620c6fd58 --- /dev/null +++ b/test/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/mongo-cli-journeys/contract-external-auth-users.ts @@ -0,0 +1,23 @@ +import { defineContract, field, model } from '@internal/mongo/contract-builder'; + +const User = model('User', { + collection: 'users', + fields: { + _id: field.objectId(), + email: field.string(), + }, +}); + +const AuthUser = model('AuthUser', { + collection: 'auth_users', + controlPolicy: 'external', + fields: { + _id: field.objectId(), + email: field.string(), + }, + indexes: [], +}); + +export const contract = defineContract({ + models: { User, AuthUser }, +}); diff --git a/test/integration/test/fixtures/contract.d.ts b/test/integration/test/fixtures/contract.d.ts index f7f04d0c7b46..846bce4b813d 100644 --- a/test/integration/test/fixtures/contract.d.ts +++ b/test/integration/test/fixtures/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -438,7 +440,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.d.ts b/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.d.ts index 2f95321326c4..c9a299a39ad6 100644 --- a/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.d.ts +++ b/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e6b8f0ca84154e70d8240cabf47951883d699f4a84e45131c60f899dd900deb4'>; + StorageHashBase<'d9eb4195e93e0635171fe59d8bc76ea81da5edb4190111b37671eeb5ffd9b39c'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -65,14 +65,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -420,6 +422,41 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64@1' }; + }; + }; + }; + readonly Point: { + readonly fields: { + readonly tags: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64@1' }; + readonly many: { readonly elementNullable: false }; + }; + readonly x: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + }; + readonly Stamp: { + readonly fields: { + readonly note: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/json@1' }; + }; + }; + }; + }; }; }; }; @@ -475,7 +512,13 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly avatar: { readonly nullable: true; @@ -573,7 +616,13 @@ type ContractBase = Omit< }; readonly stamps: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Stamp' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Stamp'; + }; readonly many: { readonly elementNullable: false }; }; }; @@ -709,7 +758,13 @@ type ContractBase = Omit< }; readonly points: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Point' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Point'; + }; readonly many: { readonly elementNullable: false }; }; readonly words: { @@ -737,41 +792,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64@1' }; - }; - }; - }; - readonly Point: { - readonly fields: { - readonly tags: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly x: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - }; - }; - readonly Stamp: { - readonly fields: { - readonly note: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/json@1' }; - }; - }; - }; - }; readonly enum: { readonly Role: { readonly codecId: 'mongo/string@1'; @@ -793,41 +813,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64@1' }; - }; - }; - }; - readonly Point: { - readonly fields: { - readonly tags: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64@1' }; - readonly many: { readonly elementNullable: false }; - }; - readonly x: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; - }; - }; - }; - readonly Stamp: { - readonly fields: { - readonly note: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/json@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.json b/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.json index 2337d88d68eb..f74aba57aa58 100644 --- a/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.json +++ b/test/integration/test/mongo/bson-scalars/_fixture/generated/contract.json @@ -92,8 +92,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "avatar": { @@ -262,8 +265,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Stamp", "kind": "valueObject", - "name": "Stamp" + "namespaceId": "__unbound__", + "plane": "storage" } } }, @@ -498,8 +504,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Point", "kind": "valueObject", - "name": "Point" + "namespaceId": "__unbound__", + "plane": "storage" } }, "words": { @@ -553,58 +562,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": true, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": false, - "type": { - "codecId": "mongo/int64@1", - "kind": "scalar" - } - } - } - }, - "Point": { - "fields": { - "tags": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "codecId": "mongo/int64@1", - "kind": "scalar" - } - }, - "x": { - "nullable": false, - "type": { - "codecId": "mongo/int32@1", - "kind": "scalar" - } - } - } - }, - "Stamp": { - "fields": { - "note": { - "nullable": false, - "type": { - "codecId": "mongo/json@1", - "kind": "scalar" - } - } - } - } } } } @@ -985,6 +942,58 @@ } } }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": false, + "type": { + "codecId": "mongo/int64@1", + "kind": "scalar" + } + } + } + }, + "Point": { + "fields": { + "tags": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "codecId": "mongo/int64@1", + "kind": "scalar" + } + }, + "x": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + } + } + } + }, + "Stamp": { + "fields": { + "note": { + "nullable": false, + "type": { + "codecId": "mongo/json@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "Role": { "kind": "valueSet", @@ -999,7 +1008,7 @@ "kind": "mongo-database" } }, - "storageHash": "e6b8f0ca84154e70d8240cabf47951883d699f4a84e45131c60f899dd900deb4" + "storageHash": "d9eb4195e93e0635171fe59d8bc76ea81da5edb4190111b37671eeb5ffd9b39c" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/mongo/fixtures/generated/contract.d.ts b/test/integration/test/mongo/fixtures/generated/contract.d.ts index eda54026dd9c..6d549884744f 100644 --- a/test/integration/test/mongo/fixtures/generated/contract.d.ts +++ b/test/integration/test/mongo/fixtures/generated/contract.d.ts @@ -37,14 +37,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -460,7 +462,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/list-filters/_fixture/generated/contract.d.ts b/test/integration/test/mongo/list-filters/_fixture/generated/contract.d.ts index 151e8c5960e1..a0ad780ab2be 100644 --- a/test/integration/test/mongo/list-filters/_fixture/generated/contract.d.ts +++ b/test/integration/test/mongo/list-filters/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'5eedd9b568f168145ab3967dfddcafb7efb16c59c1d5babaed9d71cae6a75923'>; + StorageHashBase<'3959359f149bf7501cc3d923e3f0975b8c1820f99af796db88df3fc9d138ecd7'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -53,14 +53,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -154,6 +156,16 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Entry: { + readonly fields: { + readonly value: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; @@ -191,12 +203,24 @@ type ContractBase = Omit< }; readonly optionalEntries: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Entry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Entry'; + }; readonly many: { readonly elementNullable: false }; }; readonly optionalNullableEntries: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Entry' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Entry'; + }; readonly many: { readonly elementNullable: true }; }; readonly optionalNullableStrings: { @@ -232,32 +256,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Entry: { - readonly fields: { - readonly value: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Entry: { - readonly fields: { - readonly value: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/list-filters/_fixture/generated/contract.json b/test/integration/test/mongo/list-filters/_fixture/generated/contract.json index 5f6f6bbd9df1..64ed51875721 100644 --- a/test/integration/test/mongo/list-filters/_fixture/generated/contract.json +++ b/test/integration/test/mongo/list-filters/_fixture/generated/contract.json @@ -52,8 +52,11 @@ }, "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Entry", "kind": "valueObject", - "name": "Entry" + "namespaceId": "__unbound__", + "plane": "storage" } }, "optionalNullableEntries": { @@ -62,8 +65,11 @@ }, "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Entry", "kind": "valueObject", - "name": "Entry" + "namespaceId": "__unbound__", + "plane": "storage" } }, "optionalNullableStrings": { @@ -131,19 +137,6 @@ } } } - }, - "valueObjects": { - "Entry": { - "fields": { - "value": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -264,13 +257,26 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "Entry": { + "fields": { + "value": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "5eedd9b568f168145ab3967dfddcafb7efb16c59c1d5babaed9d71cae6a75923" + "storageHash": "3959359f149bf7501cc3d923e3f0975b8c1820f99af796db88df3fc9d138ecd7" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.d.ts b/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.d.ts index eaf83df534be..7e8c665ada18 100644 --- a/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.d.ts +++ b/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'4437f81a7adbcbf818bc57a01eeae876338e847c850fca1b59a3de9110919258'>; + StorageHashBase<'a0963d77119ea552aa28834b70bc7ce8969caaae10055d4d07deec4ad11ff5be'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -53,14 +53,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -102,6 +104,19 @@ type ContractBase = Omit< readonly Counter: MongoCollection; readonly Tally: MongoCollection; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly zip: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'mongo/int64Number@1'; + }; + }; + }; + }; + }; }; }; }; @@ -161,11 +176,23 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; readonly addresses: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly many: { readonly elementNullable: false }; }; readonly id: { @@ -195,32 +222,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64Number@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int64Number@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.json b/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.json index 432b0adad393..2597ed49bd9c 100644 --- a/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.json +++ b/test/integration/test/mongo/prisma6-source/_fixture/generated/contract.json @@ -82,8 +82,11 @@ "address": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "addresses": { @@ -92,8 +95,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "__unbound__", + "plane": "storage" } }, "id": { @@ -143,19 +149,6 @@ } } } - }, - "valueObjects": { - "Address": { - "fields": { - "zip": { - "nullable": false, - "type": { - "codecId": "mongo/int64Number@1", - "kind": "scalar" - } - } - } - } } } } @@ -171,13 +164,26 @@ "Tally": { "kind": "mongo-collection" } + }, + "valueObject": { + "Address": { + "fields": { + "zip": { + "nullable": false, + "type": { + "codecId": "mongo/int64Number@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "4437f81a7adbcbf818bc57a01eeae876338e847c850fca1b59a3de9110919258" + "storageHash": "a0963d77119ea552aa28834b70bc7ce8969caaae10055d4d07deec4ad11ff5be" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.d.ts b/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.d.ts index 8bd0747724c1..c84a3a614b49 100644 --- a/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.d.ts +++ b/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.d.ts @@ -1,7 +1,10 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma contract emit'. // To regenerate, run: prisma contract emit -import type { CodecTypes as MongoCodecTypes } from '@internal/target-mongo/codec-types'; +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@internal/target-mongo/codec-types'; import type { MongoCollection, @@ -12,51 +15,61 @@ import type { import type { Contract as ContractType, ExecutionHashBase, + FieldTypesFromDomain, NamespaceId, ProfileHashBase, StorageHashBase, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'842683ee4dbc4b81d370b11a296f09c5a9ef623aea522ea76b492732f7c7a7ee'>; + StorageHashBase<'0d971a3402142eb5ee3d6dae1a8b52571ad8f06bd0bb027509f62ed409e68710'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; export type CodecTypes = MongoCodecTypes; -export type NavItemOutput = { - readonly children: ReadonlyArray; - readonly label: CodecTypes['mongo/string@1']['output']; -}; -export type NavItemInput = { - readonly children: ReadonlyArray; - readonly label: CodecTypes['mongo/string@1']['input']; -}; -export type FieldOutputTypes = { - readonly __unbound__: { - readonly Menu: { - readonly id: CodecTypes['mongo/objectId@1']['output']; - readonly items: ReadonlyArray; - readonly name: CodecTypes['mongo/string@1']['output']; - }; - }; -}; -export type FieldInputTypes = { - readonly __unbound__: { - readonly Menu: { - readonly id: CodecTypes['mongo/objectId@1']['input']; - readonly items: ReadonlyArray; - readonly name: CodecTypes['mongo/string@1']['input']; - }; - }; -}; +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type NavItemOutput = ValueObjectOutputTypes['__unbound__']['NavItem']; +export type NavItemInput = ValueObjectInputTypes['__unbound__']['NavItem']; +type FieldTypeOverrides = {}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; export namespace Models { export type unbound_Menu = { - id: CodecTypes['mongo/objectId@1']['output']; - items: ReadonlyArray; - name: CodecTypes['mongo/string@1']['output']; + id: FieldOutputTypes['__unbound__']['Menu']['id']; + items: FieldOutputTypes['__unbound__']['Menu']['items']; + name: FieldOutputTypes['__unbound__']['Menu']['name']; readonly [RelationKeys]?: never; }; } @@ -110,6 +123,27 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly NavItem: { + readonly fields: { + readonly children: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'NavItem'; + }; + readonly many: { readonly elementNullable: false }; + }; + readonly label: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; @@ -134,7 +168,13 @@ type ContractBase = Omit< }; readonly items: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'NavItem' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'NavItem'; + }; readonly many: { readonly elementNullable: false }; }; readonly name: { @@ -153,42 +193,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly NavItem: { - readonly fields: { - readonly children: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'NavItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly label: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly NavItem: { - readonly fields: { - readonly children: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'NavItem' }; - readonly many: { readonly elementNullable: false }; - }; - readonly label: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.json b/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.json index 09f60ed5fc43..0e6622d28c16 100644 --- a/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.json +++ b/test/integration/test/mongo/recursive-value-objects/_fixture/generated/contract.json @@ -28,8 +28,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "NavItem", "kind": "valueObject", - "name": "NavItem" + "namespaceId": "__unbound__", + "plane": "storage" } }, "name": { @@ -56,29 +59,6 @@ } } } - }, - "valueObjects": { - "NavItem": { - "fields": { - "children": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "NavItem" - } - }, - "label": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -135,13 +115,39 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "NavItem": { + "fields": { + "children": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "NavItem", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "label": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "842683ee4dbc4b81d370b11a296f09c5a9ef623aea522ea76b492732f7c7a7ee" + "storageHash": "0d971a3402142eb5ee3d6dae1a8b52571ad8f06bd0bb027509f62ed409e68710" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/mongo/temporal-generators/_fixture-aliases/generated/contract.d.ts b/test/integration/test/mongo/temporal-generators/_fixture-aliases/generated/contract.d.ts index eb8f24c66d11..04128f128dcc 100644 --- a/test/integration/test/mongo/temporal-generators/_fixture-aliases/generated/contract.d.ts +++ b/test/integration/test/mongo/temporal-generators/_fixture-aliases/generated/contract.d.ts @@ -38,14 +38,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -449,7 +451,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/temporal-generators/_fixture-attributes/generated/contract.d.ts b/test/integration/test/mongo/temporal-generators/_fixture-attributes/generated/contract.d.ts index eb8f24c66d11..04128f128dcc 100644 --- a/test/integration/test/mongo/temporal-generators/_fixture-attributes/generated/contract.d.ts +++ b/test/integration/test/mongo/temporal-generators/_fixture-attributes/generated/contract.d.ts @@ -38,14 +38,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -449,7 +451,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/mongo/temporal-generators/_fixture/generated/contract.d.ts b/test/integration/test/mongo/temporal-generators/_fixture/generated/contract.d.ts index eb8f24c66d11..04128f128dcc 100644 --- a/test/integration/test/mongo/temporal-generators/_fixture/generated/contract.d.ts +++ b/test/integration/test/mongo/temporal-generators/_fixture/generated/contract.d.ts @@ -38,14 +38,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -449,7 +451,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts b/test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts index e4b9cd5dccbc..55ce6cf017a0 100644 --- a/test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts +++ b/test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -660,7 +662,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/planner-golden/fixtures/postgres/generated/contract.d.ts b/test/integration/test/planner-golden/fixtures/postgres/generated/contract.d.ts index f57107eb2aae..fd195f8a52e0 100644 --- a/test/integration/test/planner-golden/fixtures/postgres/generated/contract.d.ts +++ b/test/integration/test/planner-golden/fixtures/postgres/generated/contract.d.ts @@ -296,14 +296,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -1758,7 +1760,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/planner-golden/fixtures/sqlite/generated/contract.d.ts b/test/integration/test/planner-golden/fixtures/sqlite/generated/contract.d.ts index fcdb9dbeaf85..829ebcd2280a 100644 --- a/test/integration/test/planner-golden/fixtures/sqlite/generated/contract.d.ts +++ b/test/integration/test/planner-golden/fixtures/sqlite/generated/contract.d.ts @@ -139,14 +139,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -459,7 +461,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/decimal/generated/contract.d.ts index 6c6df1fc1559..6b20ace0c417 100644 --- a/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/numeric/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/numeric/generated/contract.d.ts index f62f8dfc0029..4c6e6d081893 100644 --- a/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/numeric/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/avg/_fixture/numeric/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -447,7 +449,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/count/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/count/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/aggregation/count/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/count/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/main/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/main/generated/contract.d.ts index 0a3850f07ce1..f922f7a1db19 100644 --- a/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/main/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/main/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -548,7 +550,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/regression-21789/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/regression-21789/generated/contract.d.ts index 2f2982eabc53..dca33a93c965 100644 --- a/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/regression-21789/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/group_by/_fixture/regression-21789/generated/contract.d.ts @@ -286,14 +286,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -466,7 +468,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/common/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/common/generated/contract.d.ts index 6ec0331c00bf..937191f74c12 100644 --- a/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/common/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/common/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -459,7 +461,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/decimal/generated/contract.d.ts index 6974240b4843..7093b6778356 100644 --- a/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/group_by_having/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -435,7 +437,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/compound/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/compound/generated/contract.d.ts index d3af49d9e52f..b3a08c639661 100644 --- a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/compound/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/compound/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -760,7 +762,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/generated/contract.d.ts index b2cf70e906aa..417e03404514 100644 --- a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -698,7 +700,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/nested/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/nested/generated/contract.d.ts index 239906d2ec26..11f08d7d623f 100644 --- a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/nested/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/nested/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -963,7 +965,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/self/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/self/generated/contract.d.ts index 6bc90cc15bb3..d8d6b448ae17 100644 --- a/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/self/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/many_count_relation/_fixture/self/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -566,7 +568,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/max/_fixture/common/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/max/_fixture/common/generated/contract.d.ts index 341641c1724f..9d483e62c072 100644 --- a/test/integration/test/ports/engines/queries/aggregation/max/_fixture/common/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/max/_fixture/common/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -459,7 +461,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/max/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/max/_fixture/decimal/generated/contract.d.ts index 6c6df1fc1559..6b20ace0c417 100644 --- a/test/integration/test/ports/engines/queries/aggregation/max/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/max/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/min/_fixture/common/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/min/_fixture/common/generated/contract.d.ts index 341641c1724f..9d483e62c072 100644 --- a/test/integration/test/ports/engines/queries/aggregation/min/_fixture/common/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/min/_fixture/common/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -459,7 +461,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/min/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/min/_fixture/decimal/generated/contract.d.ts index 6c6df1fc1559..6b20ace0c417 100644 --- a/test/integration/test/ports/engines/queries/aggregation/min/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/min/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/decimal/generated/contract.d.ts index 6c6df1fc1559..6b20ace0c417 100644 --- a/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/numeric/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/numeric/generated/contract.d.ts index f62f8dfc0029..4c6e6d081893 100644 --- a/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/numeric/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/sum/_fixture/numeric/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -447,7 +449,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/base/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/base/generated/contract.d.ts index b2cf70e906aa..417e03404514 100644 --- a/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/base/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/base/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -698,7 +700,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/nested/generated/contract.d.ts b/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/nested/generated/contract.d.ts index 239906d2ec26..11f08d7d623f 100644 --- a/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/nested/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/aggregation/uniq-count-relation/_fixture/nested/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -963,7 +965,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/bigint/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/bigint/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/bigint/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/bigint/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/bool/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/bool/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/bool/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/bool/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/relations/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/relations/generated/contract.d.ts index 06ee8b61aed2..2e5f7692c7ea 100644 --- a/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/relations/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/relations/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -516,7 +518,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/scalars/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/scalars/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/scalars/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/bytes/_fixture/scalars/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/datetime/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/datetime/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/datetime/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/datetime/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/decimal/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/decimal/_fixture/generated/contract.d.ts index e2d8eb53b59d..18310808d924 100644 --- a/test/integration/test/ports/engines/queries/data_types/decimal/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/decimal/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/enum_type/_fixture/postgres/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/enum_type/_fixture/postgres/generated/contract.d.ts index 7a627cb1b347..a24b2df3e4fe 100644 --- a/test/integration/test/ports/engines/queries/data_types/enum_type/_fixture/postgres/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/enum_type/_fixture/postgres/generated/contract.d.ts @@ -283,14 +283,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -460,7 +462,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/float/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/float/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/float/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/float/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/int/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/int/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/int/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/int/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/json/_fixture/scalar/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/json/_fixture/scalar/generated/contract.d.ts index 7954f1c9e0f4..fbca3bfcc1c9 100644 --- a/test/integration/test/ports/engines/queries/data_types/json/_fixture/scalar/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/json/_fixture/scalar/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/other/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/other/generated/contract.d.ts index 92edb32bc11f..153da5dab1ce 100644 --- a/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/other/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/other/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -548,7 +550,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/string/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/string/generated/contract.d.ts index 798ac18f2e96..082110cc196f 100644 --- a/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/string/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/native/postgres/_fixture/string/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -604,7 +606,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/string/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/string/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/data_types/string/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/string/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/common/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/common/generated/contract.d.ts index c00dba970752..96dcb5f5a086 100644 --- a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/common/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/common/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -591,7 +593,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/decimal/generated/contract.d.ts index 776330827bce..7214addb8271 100644 --- a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -516,7 +518,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/enum/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/enum/generated/contract.d.ts index eada9b85d619..cbc8b3419fdc 100644 --- a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/enum/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/enum/generated/contract.d.ts @@ -286,14 +286,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -559,7 +561,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/json/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/json/generated/contract.d.ts index 505fe2600868..30499eaea859 100644 --- a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/json/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/json/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -516,7 +518,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/lists/generated/contract.d.ts b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/lists/generated/contract.d.ts index ca304388dfc4..102aef3ab889 100644 --- a/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/lists/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/data_types/through_relation/_fixture/lists/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -624,7 +626,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/distinct/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/distinct/_fixture/generated/contract.d.ts index aa3bb4d5f89d..503ea7ec4a4f 100644 --- a/test/integration/test/ports/engines/queries/distinct/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/distinct/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -579,7 +581,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/bigint_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/bigint_filter/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/filters/bigint_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/bigint_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/bytes_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/bytes_filter/_fixture/generated/contract.d.ts index f7a2f4f95ce5..8f90e7df3cb0 100644 --- a/test/integration/test/ports/engines/queries/filters/bytes_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/bytes_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -498,7 +500,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/decimal_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/decimal_filter/_fixture/generated/contract.d.ts index aaa2f4e4d2d3..5adfa088b9f2 100644 --- a/test/integration/test/ports/engines/queries/filters/decimal_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/decimal_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -428,7 +430,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/decimal-list/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/decimal-list/generated/contract.d.ts index 6374d6ff5b37..0d37452fb9bf 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/decimal-list/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/decimal-list/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -449,7 +451,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/enum/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/enum/generated/contract.d.ts index 53cef6d0078b..ed4854cf0ff4 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/enum/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/enum/generated/contract.d.ts @@ -284,14 +284,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -468,7 +470,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/list/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/list/generated/contract.d.ts index 5f972eed7d0a..15f1fe4be568 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/list/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/list/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -686,7 +688,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/mixed/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/mixed/generated/contract.d.ts index 6ea69e18eddb..322aeabed645 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/mixed/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/_fixture/mixed/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -592,7 +594,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/bigint_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/bigint_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/bigint_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/bigint_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/bytes_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/bytes_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/bytes_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/bytes_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/datetime_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/datetime_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/datetime_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/datetime_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/decimal_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/decimal_filter/_fixture/generated/contract.d.ts index 21d4bc610dd2..53a402b5ce0b 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/decimal_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/decimal_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -435,7 +437,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/common/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/common/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/common/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/common/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/default/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/default/generated/contract.d.ts index 8ad50d276bf8..3f3a2213b8b9 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/default/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/default/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -530,7 +532,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/list/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/list/generated/contract.d.ts index ea3cd3f6daa5..5dbd0eb66396 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/list/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/failure/_fixture/list/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -556,7 +558,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/float_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/float_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/float_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/float_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/having_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/having_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/having_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/having_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/int_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/int_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/int_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/int_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/generated/contract.d.ts index 0f8ee1734935..4607d170c0ad 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -447,7 +449,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/list/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/list/generated/contract.d.ts index b6a440e20503..7a972765f7be 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/list/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/json_filter/_fixture/list/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -449,7 +451,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/complex/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/complex/generated/contract.d.ts index db4f0bd497dd..cce398fb9fba 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/complex/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/complex/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -615,7 +617,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-many/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-many/generated/contract.d.ts index 676b2f57ae9d..e37c9fbb378a 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-many/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-many/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -528,7 +530,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one-list/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one-list/generated/contract.d.ts index 13a53f7c85cd..5c47b4393c39 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one-list/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one-list/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -525,7 +527,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one/generated/contract.d.ts index d9e48285c554..99030ca45f06 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/relation_filter/_fixture/one-to-one/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -524,7 +526,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/field_reference/string_filter/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/field_reference/string_filter/_fixture/generated/contract.d.ts index 24984f967a5b..1eb2b5c5e515 100644 --- a/test/integration/test/ports/engines/queries/filters/field_reference/string_filter/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/field_reference/string_filter/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -585,7 +587,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/compound-one-to-many/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/compound-one-to-many/generated/contract.d.ts index b031cff28bbe..305dd5942c57 100644 --- a/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/compound-one-to-many/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/compound-one-to-many/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -554,7 +556,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/many-to-many/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/many-to-many/generated/contract.d.ts index 2227733b694d..0b0640f89a27 100644 --- a/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/many-to-many/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/many-to-many/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -651,7 +653,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/one-to-many/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/one-to-many/generated/contract.d.ts index 1f437aea1161..eba601a3a17c 100644 --- a/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/one-to-many/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/filter_regression/_fixture/one-to-many/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -530,7 +532,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/filters/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/filters/_fixture/generated/contract.d.ts index 2ddabd42d4d6..2b78e4ac05cc 100644 --- a/test/integration/test/ports/engines/queries/filters/filters/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/filters/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -659,7 +661,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/json/_fixture/optional/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/json/_fixture/optional/generated/contract.d.ts index 7954f1c9e0f4..fbca3bfcc1c9 100644 --- a/test/integration/test/ports/engines/queries/filters/json/_fixture/optional/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/json/_fixture/optional/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/base/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/base/generated/contract.d.ts index 30c19adfe6a7..4b8da6ddee47 100644 --- a/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/base/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/base/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -505,7 +507,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/decimal/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/decimal/generated/contract.d.ts index dd1442dba7fc..511041691c75 100644 --- a/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/decimal/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/decimal/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -424,7 +426,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/json/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/json/generated/contract.d.ts index dc87644ec604..68c90049d21f 100644 --- a/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/json/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/list_filters/_fixture/json/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -424,7 +426,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/25103/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/25103/generated/contract.d.ts index 1f864c7b304d..ef0b1314379a 100644 --- a/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/25103/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/25103/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -723,7 +725,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/l2-to-one/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/l2-to-one/generated/contract.d.ts index 1c82d448e810..40cf942b8a19 100644 --- a/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/l2-to-one/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/many_relation/_fixture/l2-to-one/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -609,7 +611,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/one2one_regression/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/one2one_regression/_fixture/generated/contract.d.ts index 8a65c47cda27..97d20d08f85e 100644 --- a/test/integration/test/ports/engines/queries/filters/one2one_regression/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/one2one_regression/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -469,7 +471,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21356/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21356/generated/contract.d.ts index 49039430a9a4..71515159faef 100644 --- a/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21356/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21356/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -560,7 +562,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21366/generated/contract.d.ts b/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21366/generated/contract.d.ts index 7a4d85020407..2cb1ae63d4f5 100644 --- a/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21366/generated/contract.d.ts +++ b/test/integration/test/ports/engines/queries/filters/one_relation/_fixture/21366/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -514,7 +516,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/engines/writes/top_level_mutations/create_many/_fixture/generated/contract.d.ts b/test/integration/test/ports/engines/writes/top_level_mutations/create_many/_fixture/generated/contract.d.ts index f51ef42eef15..9c0b9b04ce37 100644 --- a/test/integration/test/ports/engines/writes/top_level_mutations/create_many/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/engines/writes/top_level_mutations/create_many/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -451,7 +453,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/batching-bigint/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/batching-bigint/_fixture/generated/contract.d.ts index 035c51c5faa0..6c080fae78df 100644 --- a/test/integration/test/ports/prisma/functional/batching-bigint/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/batching-bigint/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/batching-bytes/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/batching-bytes/_fixture/generated/contract.d.ts index cd04ffed0ad3..7b81d864e5e6 100644 --- a/test/integration/test/ports/prisma/functional/batching-bytes/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/batching-bytes/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/blog-update/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/blog-update/_fixture/generated/contract.d.ts index 76f033b38a65..982537476703 100644 --- a/test/integration/test/ports/prisma/functional/blog-update/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/blog-update/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -849,7 +851,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/bytes-upsert/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/bytes-upsert/_fixture/generated/contract.d.ts index e06dfaa7a1d4..e5e3fd0ecbfd 100644 --- a/test/integration/test/ports/prisma/functional/bytes-upsert/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/bytes-upsert/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -442,7 +444,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/chunking-query/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/chunking-query/_fixture/generated/contract.d.ts index 9f9fd7a0ef19..e822076d1319 100644 --- a/test/integration/test/ports/prisma/functional/chunking-query/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/chunking-query/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -579,7 +581,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-create/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-createMany/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-delete/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-deleteMany/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-findFirst/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-findMany/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-update/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-updateMany/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-upsert-create/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.d.ts index 35f1682bc5bd..191591ef3fc2 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b'>; + StorageHashBase<'996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -133,6 +135,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -156,7 +191,13 @@ type ContractBase = Omit< readonly fields: { readonly contents: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; readonly many: { readonly elementNullable: false }; }; readonly country: { @@ -179,69 +220,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.json index db9acf2be9b6..6c8a9508f653 100644 --- a/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-list-upsert-update/_fixture/generated/contract.json @@ -21,8 +21,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -56,47 +59,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -168,13 +130,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "e9069c51c3786d2cdf847963cb30bffe3fd5891d6fbfb8d1d2b123e58b10c28b" + "storageHash": "996e1dd50d20ae8f000f3e1058f779aeed3f999c307ebb2eb853b07221d895dd" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-create/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-createMany/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-delete/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-deleteMany/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-findFirst/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-findMany/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-update/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-updateMany/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-upsert-create/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.d.ts index f4d774998491..d15b086c85b8 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd'>; + StorageHashBase<'20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -179,6 +181,39 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly CommentContent: { + readonly fields: { + readonly text: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly upvotes: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContentUpvotes'; + }; + readonly many: { readonly elementNullable: false }; + }; + }; + }; + readonly CommentContentUpvotes: { + readonly fields: { + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly vote: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; + }; + }; + }; + }; }; }; }; @@ -206,7 +241,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -231,7 +272,13 @@ type ContractBase = Omit< readonly fields: { readonly content: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContent' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'CommentContent'; + }; }; readonly country: { readonly nullable: true; @@ -253,69 +300,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { - readonly kind: 'valueObject'; - readonly name: 'CommentContentUpvotes'; - }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly CommentContent: { - readonly fields: { - readonly text: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly upvotes: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'CommentContentUpvotes' }; - readonly many: { readonly elementNullable: false }; - }; - }; - }; - readonly CommentContentUpvotes: { - readonly fields: { - readonly userId: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly vote: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/bool@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.json index 3999f639855d..34e5ed46fb71 100644 --- a/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-object-upsert-update/_fixture/generated/contract.json @@ -22,8 +22,11 @@ "content": { "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -62,8 +65,11 @@ "content": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "CommentContent", "kind": "valueObject", - "name": "CommentContent" + "namespaceId": "__unbound__", + "plane": "storage" } }, "country": { @@ -97,47 +103,6 @@ } } } - }, - "valueObjects": { - "CommentContent": { - "fields": { - "text": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "upvotes": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "CommentContentUpvotes" - } - } - } - }, - "CommentContentUpvotes": { - "fields": { - "userId": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "vote": { - "nullable": false, - "type": { - "codecId": "mongo/bool@1", - "kind": "scalar" - } - } - } - } } } } @@ -271,13 +236,57 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "CommentContent": { + "fields": { + "text": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "upvotes": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "CommentContentUpvotes", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + } + } + }, + "CommentContentUpvotes": { + "fields": { + "userId": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "vote": { + "nullable": false, + "type": { + "codecId": "mongo/bool@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "95ba06155628a6148dd6c81cf187f68d1e0d09c7289de3530bbd4b58ac311fcd" + "storageHash": "20ad63fd1abaddd34b269a096dfe71a87c028cddcd13c1af679cbfbfb63a3b34" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.d.ts index 272bb53adddb..2fa82a992d61 100644 --- a/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'50c7e480b0d0d6850a19a52ebe53ad54ca00fe4ffc1d9149172bc774c7b1d3b0'>; + StorageHashBase<'089550830c023543ff80ad5464449ea5f0b073a9f07a57b487a184b44e991cbb'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -57,14 +57,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -148,6 +150,67 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Name: { + readonly fields: { + readonly firstName: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly lastName: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Profile: { + readonly fields: { + readonly alternateName: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Name'; + }; + }; + readonly favoriteThings: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Thing'; + }; + readonly many: { readonly elementNullable: false }; + }; + readonly name: { + readonly nullable: false; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Name'; + }; + }; + readonly url: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + readonly Thing: { + readonly fields: { + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; @@ -172,7 +235,13 @@ type ContractBase = Omit< }; readonly profile: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Profile' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Profile'; + }; }; }; readonly relations: Record; @@ -185,98 +254,12 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Name: { - readonly fields: { - readonly firstName: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly lastName: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Profile: { - readonly fields: { - readonly alternateName: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Name' }; - }; - readonly favoriteThings: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Thing' }; - readonly many: { readonly elementNullable: false }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Name' }; - }; - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Thing: { - readonly fields: { - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Name: { - readonly fields: { - readonly firstName: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly lastName: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Profile: { - readonly fields: { - readonly alternateName: { - readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Name' }; - }; - readonly favoriteThings: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Thing' }; - readonly many: { readonly elementNullable: false }; - }; - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Name' }; - }; - readonly url: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - readonly Thing: { - readonly fields: { - readonly name: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.json b/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.json index 533b56b49081..1a005ec82473 100644 --- a/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/composites-selection/_fixture/generated/contract.json @@ -25,8 +25,11 @@ "profile": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Profile", "kind": "valueObject", - "name": "Profile" + "namespaceId": "__unbound__", + "plane": "storage" } } }, @@ -43,72 +46,6 @@ } } } - }, - "valueObjects": { - "Name": { - "fields": { - "firstName": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - }, - "lastName": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "Profile": { - "fields": { - "alternateName": { - "nullable": true, - "type": { - "kind": "valueObject", - "name": "Name" - } - }, - "favoriteThings": { - "many": { - "elementNullable": false - }, - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Thing" - } - }, - "name": { - "nullable": false, - "type": { - "kind": "valueObject", - "name": "Name" - } - }, - "url": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - }, - "Thing": { - "fields": { - "name": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -207,13 +144,88 @@ "validationLevel": "strict" } } + }, + "valueObject": { + "Name": { + "fields": { + "firstName": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "lastName": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "Profile": { + "fields": { + "alternateName": { + "nullable": true, + "type": { + "entityKind": "valueObject", + "entityName": "Name", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "favoriteThings": { + "many": { + "elementNullable": false + }, + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "Thing", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "name": { + "nullable": false, + "type": { + "entityKind": "valueObject", + "entityName": "Name", + "kind": "valueObject", + "namespaceId": "__unbound__", + "plane": "storage" + } + }, + "url": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + }, + "Thing": { + "fields": { + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } } }, "id": "__unbound__", "kind": "mongo-database" } }, - "storageHash": "50c7e480b0d0d6850a19a52ebe53ad54ca00fe4ffc1d9149172bc774c7b1d3b0" + "storageHash": "089550830c023543ff80ad5464449ea5f0b073a9f07a57b487a184b44e991cbb" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/create-default-date/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/create-default-date/_fixture/generated/contract.d.ts index 9772fe413043..2265d3ebf58d 100644 --- a/test/integration/test/ports/prisma/functional/create-default-date/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/create-default-date/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -431,7 +433,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/decimal-list/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/decimal-list/_fixture/generated/contract.d.ts index 9e9bace0da98..6a20ed32de99 100644 --- a/test/integration/test/ports/prisma/functional/decimal-list/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/decimal-list/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -445,7 +447,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/decimal-precision/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/decimal-precision/_fixture/generated/contract.d.ts index 7ba79bb02b38..bb42da7fa456 100644 --- a/test/integration/test/ports/prisma/functional/decimal-precision/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/decimal-precision/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -478,7 +480,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/decimal-scalar/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/decimal-scalar/_fixture/generated/contract.d.ts index 2b9d895de131..d32d517e8768 100644 --- a/test/integration/test/ports/prisma/functional/decimal-scalar/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/decimal-scalar/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/default-selection/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/default-selection/_fixture/generated/contract.d.ts index fd9cc7c247a0..fa25f00657f2 100644 --- a/test/integration/test/ports/prisma/functional/default-selection/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/default-selection/_fixture/generated/contract.d.ts @@ -284,14 +284,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -582,7 +584,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.d.ts index 8d8f74c8baa7..504b0ee99b90 100644 --- a/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.d.ts @@ -19,11 +19,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'7fe8a5ef11abf3a2db023b5af601b57e7c00e94563ef45301135e34b64182696'>; + StorageHashBase<'b3aa4824e50739d258a1bae8ba11729a450c18c6276f6008bdf5276d6f5ed8f5'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; @@ -31,15 +31,15 @@ export type ProfileHash = export type CodecTypes = MongoCodecTypes; type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -62,14 +62,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -177,6 +179,16 @@ type ContractBase = Omit< }; }; }; + readonly valueObject: { + readonly Composite: { + readonly fields: { + readonly value: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; @@ -198,7 +210,13 @@ type ContractBase = Omit< readonly fields: { readonly composite: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Composite' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Composite'; + }; }; readonly enum: { readonly nullable: false; @@ -281,16 +299,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Composite: { - readonly fields: { - readonly value: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly enum: { readonly Enum: { readonly codecId: 'mongo/string@1'; @@ -312,16 +320,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Composite: { - readonly fields: { - readonly value: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.json b/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.json index 57391f610072..e25bee088ae3 100644 --- a/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.json +++ b/test/integration/test/ports/prisma/functional/default-selection/_fixture/mongo/generated/contract.json @@ -37,8 +37,11 @@ "composite": { "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Composite", "kind": "valueObject", - "name": "Composite" + "namespaceId": "__unbound__", + "plane": "storage" } }, "enum": { @@ -184,19 +187,6 @@ } } } - }, - "valueObjects": { - "Composite": { - "fields": { - "value": { - "nullable": false, - "type": { - "codecId": "mongo/string@1", - "kind": "scalar" - } - } - } - } } } } @@ -306,6 +296,19 @@ } } }, + "valueObject": { + "Composite": { + "fields": { + "value": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } + }, "valueSet": { "Enum": { "kind": "valueSet", @@ -320,7 +323,7 @@ "kind": "mongo-database" } }, - "storageHash": "7fe8a5ef11abf3a2db023b5af601b57e7c00e94563ef45301135e34b64182696" + "storageHash": "b3aa4824e50739d258a1bae8ba11729a450c18c6276f6008bdf5276d6f5ed8f5" }, "capabilities": {}, "extensions": {}, diff --git a/test/integration/test/ports/prisma/functional/distinct/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/distinct/_fixture/generated/contract.d.ts index ed89b33c639d..8afcb2ad48ee 100644 --- a/test/integration/test/ports/prisma/functional/distinct/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/distinct/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -435,7 +437,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/driver-adapters-error-forwarding/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/driver-adapters-error-forwarding/_fixture/generated/contract.d.ts index 2454e4dcaae8..ecfa65dab8bc 100644 --- a/test/integration/test/ports/prisma/functional/driver-adapters-error-forwarding/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/driver-adapters-error-forwarding/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -495,7 +497,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/driver-adapters-team-orm-687-bytes/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/driver-adapters-team-orm-687-bytes/_fixture/generated/contract.d.ts index f616735d3de3..f0a02be8fc1f 100644 --- a/test/integration/test/ports/prisma/functional/driver-adapters-team-orm-687-bytes/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/driver-adapters-team-orm-687-bytes/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/generated/contract.d.ts index 6ff68a7d15e8..75e1db43539f 100644 --- a/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -409,7 +411,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/sqlite/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/sqlite/generated/contract.d.ts index d7c088adf5c2..c21215dfb70f 100644 --- a/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/sqlite/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/driver-adapters-validate-active-provider/_fixture/sqlite/generated/contract.d.ts @@ -139,14 +139,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -255,7 +257,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/enum-array/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/enum-array/_fixture/generated/contract.d.ts index 282b6df62c34..9516a2d0306b 100644 --- a/test/integration/test/ports/prisma/functional/enum-array/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/enum-array/_fixture/generated/contract.d.ts @@ -287,14 +287,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -483,7 +485,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/enums/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/enums/_fixture/generated/contract.d.ts index a5211479783c..0fb352c5df0c 100644 --- a/test/integration/test/ports/prisma/functional/enums/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/enums/_fixture/generated/contract.d.ts @@ -48,14 +48,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -163,7 +165,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/extended-where/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/extended-where/_fixture/generated/contract.d.ts index e52a151f5303..ef496fd45411 100644 --- a/test/integration/test/ports/prisma/functional/extended-where/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/extended-where/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -777,7 +779,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/filter-count-relations/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/filter-count-relations/_fixture/generated/contract.d.ts index bba4c91b2db8..4a1948bb32ba 100644 --- a/test/integration/test/ports/prisma/functional/filter-count-relations/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/filter-count-relations/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -758,7 +760,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/find-unique-or-throw-batching/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/find-unique-or-throw-batching/_fixture/generated/contract.d.ts index f13dfe64bf20..95d3148cf795 100644 --- a/test/integration/test/ports/prisma/functional/find-unique-or-throw-batching/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/find-unique-or-throw-batching/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -409,7 +411,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/handle-int-overflow/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/handle-int-overflow/_fixture/generated/contract.d.ts index f8fe71b68df8..17a78d07bd57 100644 --- a/test/integration/test/ports/prisma/functional/handle-int-overflow/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/handle-int-overflow/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/interactive-transactions/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/interactive-transactions/_fixture/generated/contract.d.ts index 86b47698325b..720c2be3278f 100644 --- a/test/integration/test/ports/prisma/functional/interactive-transactions/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/interactive-transactions/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -620,7 +622,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-11974/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-11974/_fixture/generated/contract.d.ts index 59c8bad32294..74be01ae70a6 100644 --- a/test/integration/test/ports/prisma/functional/issues-11974/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-11974/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -735,7 +737,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-12378/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-12378/_fixture/generated/contract.d.ts index df42b2a1f928..07bc928c3055 100644 --- a/test/integration/test/ports/prisma/functional/issues-12378/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-12378/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -646,7 +648,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-12557/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-12557/_fixture/generated/contract.d.ts index 5d359deead5d..fe37da612845 100644 --- a/test/integration/test/ports/prisma/functional/issues-12557/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-12557/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -627,7 +629,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-12572/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-12572/_fixture/generated/contract.d.ts index 63180d3470cc..afbe55c25158 100644 --- a/test/integration/test/ports/prisma/functional/issues-12572/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-12572/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -467,7 +469,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-13089-dollar-in-search/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-13089-dollar-in-search/_fixture/generated/contract.d.ts index 92a562952693..dfaf5075d7d1 100644 --- a/test/integration/test/ports/prisma/functional/issues-13089-dollar-in-search/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-13089-dollar-in-search/_fixture/generated/contract.d.ts @@ -37,14 +37,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -139,7 +141,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-14271/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-14271/_fixture/generated/contract.d.ts index 4fdb7a1300e7..8539016476da 100644 --- a/test/integration/test/ports/prisma/functional/issues-14271/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-14271/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -538,7 +540,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-14954-date-batch/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-14954-date-batch/_fixture/generated/contract.d.ts index b0b0c13f94ea..8845559baa89 100644 --- a/test/integration/test/ports/prisma/functional/issues-14954-date-batch/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-14954-date-batch/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -442,7 +444,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-15044/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-15044/_fixture/generated/contract.d.ts index 8e0aece2b546..2c31e881d648 100644 --- a/test/integration/test/ports/prisma/functional/issues-15044/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-15044/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -666,7 +668,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-16535-select-enum/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-16535-select-enum/_fixture/generated/contract.d.ts index f1d1e114145d..7e459822c74a 100644 --- a/test/integration/test/ports/prisma/functional/issues-16535-select-enum/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-16535-select-enum/_fixture/generated/contract.d.ts @@ -284,14 +284,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -474,7 +476,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-17005-args-type-conflict/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-17005-args-type-conflict/_fixture/generated/contract.d.ts index 1f3a4e2a4e8b..2f5a0c0d7fc4 100644 --- a/test/integration/test/ports/prisma/functional/issues-17005-args-type-conflict/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-17005-args-type-conflict/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -623,7 +625,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-17030-args-type-conflict/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-17030-args-type-conflict/_fixture/generated/contract.d.ts index aa9ece970405..55b2f5a83619 100644 --- a/test/integration/test/ports/prisma/functional/issues-17030-args-type-conflict/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-17030-args-type-conflict/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -519,7 +521,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-18970-invalid-date/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-18970-invalid-date/_fixture/generated/contract.d.ts index 143d0f821149..70d1617d59c7 100644 --- a/test/integration/test/ports/prisma/functional/issues-18970-invalid-date/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-18970-invalid-date/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -442,7 +444,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-20261-group-by-shortcut/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-20261-group-by-shortcut/_fixture/generated/contract.d.ts index 66b887bb68c9..ca980aeea0ba 100644 --- a/test/integration/test/ports/prisma/functional/issues-20261-group-by-shortcut/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-20261-group-by-shortcut/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -451,7 +453,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-21352-id-does-not-exist/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-21352-id-does-not-exist/_fixture/generated/contract.d.ts index 24dc948b1014..27b5c35ccb66 100644 --- a/test/integration/test/ports/prisma/functional/issues-21352-id-does-not-exist/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-21352-id-does-not-exist/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-21454-type-in-json/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-21454-type-in-json/_fixture/generated/contract.d.ts index dc14977ff0e4..7bc058bd1593 100644 --- a/test/integration/test/ports/prisma/functional/issues-21454-type-in-json/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-21454-type-in-json/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-21631-batching-in-transaction/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-21631-batching-in-transaction/_fixture/generated/contract.d.ts index 8da6309cc989..0b691f16f579 100644 --- a/test/integration/test/ports/prisma/functional/issues-21631-batching-in-transaction/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-21631-batching-in-transaction/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -454,7 +456,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-22098-column-does-not-exist/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-22098-column-does-not-exist/_fixture/generated/contract.d.ts index 4d666e42e710..a0cb905c789a 100644 --- a/test/integration/test/ports/prisma/functional/issues-22098-column-does-not-exist/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-22098-column-does-not-exist/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-22610-parallel-batch/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-22610-parallel-batch/_fixture/generated/contract.d.ts index e5d958e9dbc1..5bc54133d706 100644 --- a/test/integration/test/ports/prisma/functional/issues-22610-parallel-batch/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-22610-parallel-batch/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -588,7 +590,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-23201-non-ascii-comments/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-23201-non-ascii-comments/_fixture/generated/contract.d.ts index 2249158354d1..0e9cf049a5fc 100644 --- a/test/integration/test/ports/prisma/functional/issues-23201-non-ascii-comments/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-23201-non-ascii-comments/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-23902/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-23902/_fixture/generated/contract.d.ts index f5072b4acbee..20318795d1e7 100644 --- a/test/integration/test/ports/prisma/functional/issues-23902/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-23902/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -644,7 +646,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-25404/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-25404/_fixture/generated/contract.d.ts index af2d9d860e20..90667d72ea16 100644 --- a/test/integration/test/ports/prisma/functional/issues-25404/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-25404/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-27455-bytes-id/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-27455-bytes-id/_fixture/generated/contract.d.ts index e2639fb6cce9..937598b48f8c 100644 --- a/test/integration/test/ports/prisma/functional/issues-27455-bytes-id/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-27455-bytes-id/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -524,7 +526,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-27511-include-enum-array/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-27511-include-enum-array/_fixture/generated/contract.d.ts index 3f3119168e4f..820299233a76 100644 --- a/test/integration/test/ports/prisma/functional/issues-27511-include-enum-array/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-27511-include-enum-array/_fixture/generated/contract.d.ts @@ -286,14 +286,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -669,7 +671,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-28151-broken-nested-set/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-28151-broken-nested-set/_fixture/generated/contract.d.ts index 0a61ca91bb45..84f54fcb025a 100644 --- a/test/integration/test/ports/prisma/functional/issues-28151-broken-nested-set/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-28151-broken-nested-set/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -522,7 +524,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-28192-pg-historical-dates/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-28192-pg-historical-dates/_fixture/generated/contract.d.ts index a24e89d7cf7a..f521429a0070 100644 --- a/test/integration/test/ports/prisma/functional/issues-28192-pg-historical-dates/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-28192-pg-historical-dates/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -469,7 +471,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-28591-mapped-enums/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-28591-mapped-enums/_fixture/generated/contract.d.ts index 27636c708a42..a9f900d2b066 100644 --- a/test/integration/test/ports/prisma/functional/issues-28591-mapped-enums/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-28591-mapped-enums/_fixture/generated/contract.d.ts @@ -286,14 +286,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -484,7 +486,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29010-bigint-precision-relation-joins/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29010-bigint-precision-relation-joins/_fixture/generated/contract.d.ts index ffaf088a463d..cdfa38f469cf 100644 --- a/test/integration/test/ports/prisma/functional/issues-29010-bigint-precision-relation-joins/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29010-bigint-precision-relation-joins/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -524,7 +526,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29174-jsonb-parameter-regression/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29174-jsonb-parameter-regression/_fixture/generated/contract.d.ts index a59c62e5a743..0870f4af88e7 100644 --- a/test/integration/test/ports/prisma/functional/issues-29174-jsonb-parameter-regression/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29174-jsonb-parameter-regression/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29176-cursor-parameter-regression/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29176-cursor-parameter-regression/_fixture/generated/contract.d.ts index 803bdb98459f..1e9e62d97d49 100644 --- a/test/integration/test/ports/prisma/functional/issues-29176-cursor-parameter-regression/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29176-cursor-parameter-regression/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -468,7 +470,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29254-query-plan-cache-mutation/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29254-query-plan-cache-mutation/_fixture/generated/contract.d.ts index 0324e153c7d3..df27999c60d6 100644 --- a/test/integration/test/ports/prisma/functional/issues-29254-query-plan-cache-mutation/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29254-query-plan-cache-mutation/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29267-uint8array-in-json/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29267-uint8array-in-json/_fixture/generated/contract.d.ts index 3e501b676faa..54971eeae5b0 100644 --- a/test/integration/test/ports/prisma/functional/issues-29267-uint8array-in-json/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29267-uint8array-in-json/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -442,7 +444,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29309-datetime-cursor/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29309-datetime-cursor/_fixture/generated/contract.d.ts index 0ded7d823444..44a2e45cf902 100644 --- a/test/integration/test/ports/prisma/functional/issues-29309-datetime-cursor/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29309-datetime-cursor/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -435,7 +437,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-29331-query-plan-cache-bloat/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-29331-query-plan-cache-bloat/_fixture/generated/contract.d.ts index cc4c03475d9b..28f7381bafb1 100644 --- a/test/integration/test/ports/prisma/functional/issues-29331-query-plan-cache-bloat/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-29331-query-plan-cache-bloat/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -756,7 +758,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-4004/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-4004/_fixture/generated/contract.d.ts index 0caeb9f277d3..d85ccfc818ac 100644 --- a/test/integration/test/ports/prisma/functional/issues-4004/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-4004/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -641,7 +643,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-5952-decimal-batch/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-5952-decimal-batch/_fixture/generated/contract.d.ts index aeeec55736a7..da599488b40b 100644 --- a/test/integration/test/ports/prisma/functional/issues-5952-decimal-batch/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-5952-decimal-batch/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-TML-1664-invalid-enum-value-error/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-TML-1664-invalid-enum-value-error/_fixture/generated/contract.d.ts index c970ea08f99c..f5d8210c9ffe 100644 --- a/test/integration/test/ports/prisma/functional/issues-TML-1664-invalid-enum-value-error/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-TML-1664-invalid-enum-value-error/_fixture/generated/contract.d.ts @@ -286,14 +286,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -454,7 +456,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-TML-1664-unknown-enum-value-read-error/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-TML-1664-unknown-enum-value-read-error/_fixture/generated/contract.d.ts index e7b6774e6178..58282cf726ec 100644 --- a/test/integration/test/ports/prisma/functional/issues-TML-1664-unknown-enum-value-read-error/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-TML-1664-unknown-enum-value-read-error/_fixture/generated/contract.d.ts @@ -286,14 +286,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = { readonly public: { @@ -454,7 +456,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/issues-unmapped-driver-error-user-facing/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/issues-unmapped-driver-error-user-facing/_fixture/generated/contract.d.ts index e4a6e00150ba..5baeea5fa52d 100644 --- a/test/integration/test/ports/prisma/functional/issues-unmapped-driver-error-user-facing/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/issues-unmapped-driver-error-user-facing/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -435,7 +437,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/json-fields/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/json-fields/_fixture/generated/contract.d.ts index a47ae2c73a53..33490ab2f21e 100644 --- a/test/integration/test/ports/prisma/functional/json-fields/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/json-fields/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/large-floats/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/large-floats/_fixture/generated/contract.d.ts index a1d64a266ab5..81c5f3c3354f 100644 --- a/test/integration/test/ports/prisma/functional/large-floats/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/large-floats/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/legacy-aggregate-raw/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/legacy-aggregate-raw/_fixture/generated/contract.d.ts index 7a02f1e4bb8e..1498ba01dc58 100644 --- a/test/integration/test/ports/prisma/functional/legacy-aggregate-raw/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/legacy-aggregate-raw/_fixture/generated/contract.d.ts @@ -37,14 +37,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -265,7 +267,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/legacy-aggregations/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/legacy-aggregations/_fixture/generated/contract.d.ts index d12c84abfeb2..0cd272653b5d 100644 --- a/test/integration/test/ports/prisma/functional/legacy-aggregations/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/legacy-aggregations/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -636,7 +638,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/legacy-json/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/legacy-json/_fixture/generated/contract.d.ts index 3af051032f68..f30f6a82b76f 100644 --- a/test/integration/test/ports/prisma/functional/legacy-json/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/legacy-json/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -435,7 +437,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/legacy-malformed-id/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/legacy-malformed-id/_fixture/generated/contract.d.ts index 22cbdc3672f2..d50d2699eee2 100644 --- a/test/integration/test/ports/prisma/functional/legacy-malformed-id/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/legacy-malformed-id/_fixture/generated/contract.d.ts @@ -37,14 +37,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; export namespace Models { @@ -143,7 +145,6 @@ type ContractBase = Omit< readonly capabilities: {}; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/legacy-optional-relation-filters/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/legacy-optional-relation-filters/_fixture/generated/contract.d.ts index 0d9e93acb690..9813a41fd896 100644 --- a/test/integration/test/ports/prisma/functional/legacy-optional-relation-filters/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/legacy-optional-relation-filters/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -534,7 +536,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-count/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-count/_fixture/generated/contract.d.ts index e0c5667dfb02..403b3840e1d0 100644 --- a/test/integration/test/ports/prisma/functional/methods-count/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-count/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -636,7 +638,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-createMany/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-createMany/_fixture/generated/contract.d.ts index ddd77f3cec1c..e5a471e0f29d 100644 --- a/test/integration/test/ports/prisma/functional/methods-createMany/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-createMany/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -560,7 +562,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-createManyAndReturn/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-createManyAndReturn/_fixture/generated/contract.d.ts index ddd77f3cec1c..e5a471e0f29d 100644 --- a/test/integration/test/ports/prisma/functional/methods-createManyAndReturn/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-createManyAndReturn/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -560,7 +562,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-findFirstOrThrow/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-findFirstOrThrow/_fixture/generated/contract.d.ts index d212ca906fff..76ca552d0b50 100644 --- a/test/integration/test/ports/prisma/functional/methods-findFirstOrThrow/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-findFirstOrThrow/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -548,7 +550,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-findUniqueOrThrow/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-findUniqueOrThrow/_fixture/generated/contract.d.ts index 664ab8fcdeeb..cd1b60707827 100644 --- a/test/integration/test/ports/prisma/functional/methods-findUniqueOrThrow/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-findUniqueOrThrow/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -548,7 +550,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-updateManyAndReturn/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-updateManyAndReturn/_fixture/generated/contract.d.ts index ddd77f3cec1c..e5a471e0f29d 100644 --- a/test/integration/test/ports/prisma/functional/methods-updateManyAndReturn/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-updateManyAndReturn/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -560,7 +562,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-upsert-native-atomic/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-upsert-native-atomic/_fixture/generated/contract.d.ts index 80593281bafc..d042837663c2 100644 --- a/test/integration/test/ports/prisma/functional/methods-upsert-native-atomic/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-upsert-native-atomic/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -624,7 +626,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/methods-upsert-simple/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/methods-upsert-simple/_fixture/generated/contract.d.ts index 6d5cc4f4b852..fa8995e6bcb4 100644 --- a/test/integration/test/ports/prisma/functional/methods-upsert-simple/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/methods-upsert-simple/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -439,7 +441,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/mixed-string-uuid-datetime-list-inputs/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/mixed-string-uuid-datetime-list-inputs/_fixture/generated/contract.d.ts index 26424054f0f8..a09f057c2fb3 100644 --- a/test/integration/test/ports/prisma/functional/mixed-string-uuid-datetime-list-inputs/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/mixed-string-uuid-datetime-list-inputs/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -440,7 +442,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/multi-schema/_fixture/different-names/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/multi-schema/_fixture/different-names/generated/contract.d.ts index 3b33f9ee76b7..dcc4b63c19cb 100644 --- a/test/integration/test/ports/prisma/functional/multi-schema/_fixture/different-names/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/multi-schema/_fixture/different-names/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -573,7 +575,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/multi-schema/_fixture/identical-names/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/multi-schema/_fixture/identical-names/generated/contract.d.ts index 3f09775ce37b..b98e62c9f039 100644 --- a/test/integration/test/ports/prisma/functional/multi-schema/_fixture/identical-names/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/multi-schema/_fixture/identical-names/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -576,7 +578,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/multi-schema/_fixture/no-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/multi-schema/_fixture/no-map/generated/contract.d.ts index 01f327375997..7a2b3d70d9d9 100644 --- a/test/integration/test/ports/prisma/functional/multi-schema/_fixture/no-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/multi-schema/_fixture/no-map/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -570,7 +572,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/multiple-types/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/multiple-types/_fixture/generated/contract.d.ts index c61eb1355df1..ad40f103088d 100644 --- a/test/integration/test/ports/prisma/functional/multiple-types/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/multiple-types/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -526,7 +528,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/optimistic-concurrency-control/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/optimistic-concurrency-control/_fixture/generated/contract.d.ts index 872dbe4b6486..c5f89310cec3 100644 --- a/test/integration/test/ports/prisma/functional/optimistic-concurrency-control/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/optimistic-concurrency-control/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -540,7 +542,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/referential-actions-set-default-1to1/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/referential-actions-set-default-1to1/_fixture/generated/contract.d.ts index 76b7076e2082..d967f8974df4 100644 --- a/test/integration/test/ports/prisma/functional/referential-actions-set-default-1to1/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/referential-actions-set-default-1to1/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -647,7 +649,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/referential-actions-set-default-1ton/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/referential-actions-set-default-1ton/_fixture/generated/contract.d.ts index 76b7076e2082..d967f8974df4 100644 --- a/test/integration/test/ports/prisma/functional/referential-actions-set-default-1ton/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/referential-actions-set-default-1ton/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -647,7 +649,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-17255-mixed-actions/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-17255-mixed-actions/_fixture/generated/contract.d.ts index fa52219d1e56..6810e7923b31 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-17255-mixed-actions/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-17255-mixed-actions/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -583,7 +585,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-17255-same-actions/_fixture/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-17255-same-actions/_fixture/generated/contract.d.ts index 531c1685a072..93c637c8a844 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-17255-same-actions/_fixture/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-17255-same-actions/_fixture/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -583,7 +585,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-map/generated/contract.d.ts index 99c0e3aed834..a2155116dd18 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-plain/generated/contract.d.ts index a03b3273edef..ea8bcb3c109c 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/cascade-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-map/generated/contract.d.ts index 078cf0fc57b3..c0d9f814e49f 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-plain/generated/contract.d.ts index 65c54d1a5e47..655f1ac9d82c 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/noaction-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-map/generated/contract.d.ts index b2faee52f5c5..4c1251985e74 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-plain/generated/contract.d.ts index ace4d9dea95e..e22b75945a1d 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/restrict-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-map/generated/contract.d.ts index 385624aeaa1e..b81b7e7cc071 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-plain/generated/contract.d.ts index e76f025db203..77e60516fe0b 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-1/_fixture/setnull-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -633,7 +635,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-map/generated/contract.d.ts index 9ab36dcedf89..ee21399befbf 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-plain/generated/contract.d.ts index 3eb731c6caaf..a4cb742c6623 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/cascade-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-map/generated/contract.d.ts index 4956bcf0ba41..711d77dfe6b6 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-plain/generated/contract.d.ts index 9184e173d9b7..ab2f6eae2bec 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/noaction-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-map/generated/contract.d.ts index 2816cc23aad5..8262df7b254f 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-plain/generated/contract.d.ts index 961b9213d4b1..e3c2e5e85f03 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/restrict-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-map/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-map/generated/contract.d.ts index aea97820b877..287b22f3be7b 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-map/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-map/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-plain/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-plain/generated/contract.d.ts index df9d8fca45a4..5a88552ed63a 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-plain/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-1-to-n/_fixture/setnull-plain/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -621,7 +623,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/cascade-nomap/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/cascade-nomap/generated/contract.d.ts index 02ec8d7fdc5e..af6de3acc92d 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/cascade-nomap/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/cascade-nomap/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -619,7 +621,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/default-nomap/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/default-nomap/generated/contract.d.ts index a7d95996d526..4f46ac1db62d 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/default-nomap/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/default-nomap/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -619,7 +621,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/noaction-nomap/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/noaction-nomap/generated/contract.d.ts index a7d95996d526..4f46ac1db62d 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/noaction-nomap/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/noaction-nomap/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -619,7 +621,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/restrict-nomap/generated/contract.d.ts b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/restrict-nomap/generated/contract.d.ts index dad3483bacc2..e4b52d02f3bb 100644 --- a/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/restrict-nomap/generated/contract.d.ts +++ b/test/integration/test/ports/prisma/functional/relation-mode-gh-m-to-n/_fixture/restrict-nomap/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -619,7 +621,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/prepared-polymorphism.test.ts b/test/integration/test/prepared-polymorphism.test.ts index 82aa95bfaac5..6a317e549e6a 100644 --- a/test/integration/test/prepared-polymorphism.test.ts +++ b/test/integration/test/prepared-polymorphism.test.ts @@ -32,7 +32,7 @@ it( .prepared.all(), ); expectTypeOf(all.query(runtime, { first: 1, second: 2 })).toEqualTypeOf< - AsyncIterableResult<{ id: number; title: string; type: string }> + AsyncIterableResult<{ id: number; title: string; type: 'bug' | 'epic' | 'feature' }> >(); expect(await all.query(runtime, { first: 1, second: 2 })).toEqual([ { id: 1, title: 'Bug', type: 'bug' }, diff --git a/test/integration/test/psl-print/authored-contract-roundtrip.integration.test.ts b/test/integration/test/psl-print/authored-contract-roundtrip.integration.test.ts index d35221b6c3eb..f5a1e20d4ea5 100644 --- a/test/integration/test/psl-print/authored-contract-roundtrip.integration.test.ts +++ b/test/integration/test/psl-print/authored-contract-roundtrip.integration.test.ts @@ -50,6 +50,43 @@ function loadContract(relativePath: string): Contract { * carry. Each is read, printed, read back, and compared. */ const pslCases: ReadonlyArray<{ readonly name: string; readonly schema: string }> = [ + { + name: 'value objects with nested, list, enum and self-referring members, and a discriminator, each with its derived check', + schema: readFileSync( + join( + repoRoot, + 'test/integration/test/sql-orm-client/fixtures/value-object-checks/contract.prisma', + ), + 'utf-8', + ).replace('// use prisma-8\n', ''), + }, + { + name: 'a value object field of a variant that shares the base table, whose column is nullable', + schema: `type Geo { + lat Float + lng Float +} + +model Shape { + id Int @id + kind String + + @@discriminator(kind) +} + +model Circle { + center Geo + + @@base(Shape, "circle") +} + +model Square { + side Int + + @@base(Shape, "square") +} +`, + }, { name: 'a model control policy', schema: `model Ledger { @@ -209,6 +246,33 @@ policy_select p_read { @@check(expression: sql\`length(email) > 0\`, name: "widget_email_not_blank") @@check(expression: sql\`id > 0\`, map: "widget_id_positive") } +`, + }, + { + name: 'a value object nested in another, a list of nested value objects, and an enum member', + schema: `enum Country { + @@type("pg/text@1") + DE = "DE" + FR = "FR" +} + +type Geo { + lat Float + lng Float +} + +type Address { + street String + country Country + geo Geo + stops Geo[] +} + +model Person { + id Int @id + home Address + homes Address[] +} `, }, { diff --git a/test/integration/test/sql-builder/fixtures/generated-no-pgvector/contract.d.ts b/test/integration/test/sql-builder/fixtures/generated-no-pgvector/contract.d.ts index 7a2058a0a1fe..d3d2ec5974d6 100644 --- a/test/integration/test/sql-builder/fixtures/generated-no-pgvector/contract.d.ts +++ b/test/integration/test/sql-builder/fixtures/generated-no-pgvector/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -773,7 +775,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-builder/fixtures/generated/contract.d.ts b/test/integration/test/sql-builder/fixtures/generated/contract.d.ts index b7a660e27d7a..de353848fdc8 100644 --- a/test/integration/test/sql-builder/fixtures/generated/contract.d.ts +++ b/test/integration/test/sql-builder/fixtures/generated/contract.d.ts @@ -283,14 +283,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -1012,7 +1014,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/codec-async.test.ts b/test/integration/test/sql-orm-client/codec-async.test.ts index bd3a53e8f2a3..48a2a59bc3e6 100644 --- a/test/integration/test/sql-orm-client/codec-async.test.ts +++ b/test/integration/test/sql-orm-client/codec-async.test.ts @@ -3,7 +3,7 @@ * * `Post.embedding` flows through the `pg/vector@1` codec from `@internal/extension-pgvector`. The pgvector codec's `encode` and `decode` are authored synchronously, but the codec base in `framework-components` lifts them to Promise-returning at the boundary, so this column exercises the runtime's async dispatch path on every read and write. * - * `User.address` flows through `pg/jsonb@1` (a built-in `adapter-postgres` codec, also lifted to async by the same boundary) backed by the `Address` value object. Adding a second codec with a different wire shape gives us "mixed sync/async codec columns" in a single integration run. + * `User.address` flows through `pg/jsonb@1` (a built-in `adapter-postgres` codec, also lifted to async by the same boundary). Adding a second codec with a different wire shape gives us "mixed sync/async codec columns" in a single integration run. * * The tests below verify: * diff --git a/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.d.ts index 3a9d55796e0e..8c339719f244 100644 --- a/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.d.ts @@ -28,7 +28,7 @@ import type { } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'8973aa4e3654dc5083f7ff15a7c187e3e590f977b4171bb816cd6566fa4db93b'>; + StorageHashBase<'87d224586379cdc42fda508d70ac88410eb9795e164b6031ef5a9dd9a5a1b759'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -276,22 +276,34 @@ type DefaultLiteralValue = CodecId extends keyo type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type FieldTypeOverrides = {}; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Note: { readonly kind: { readonly output: 'memo'; readonly input: 'memo' } }; + }; + }; +}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; -type ColumnTypeOverrides = {}; +type ColumnTypeOverrides = { + readonly public: { + readonly notes: { readonly kind: { readonly output: 'memo'; readonly input: 'memo' } }; + }; +}; export type StorageColumnTypes = ColumnTypesFromStorage< ContractBase['storage'], CodecTypes, @@ -483,6 +495,9 @@ type ContractBase = Omit< foreignKeys: readonly []; }; }; + readonly valueSet: { + readonly 'Note.kind': { readonly kind: 'valueSet'; readonly values: readonly ['memo'] }; + }; }; }; }; @@ -687,7 +702,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.json index a13f0e70eabe..281f66221c23 100644 --- a/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/collection-methods/generated/contract.json @@ -211,6 +211,13 @@ "entries": { "table": { "notes": { + "checks": [ + { + "expression": "\"kind\" IN ('memo')", + "name": "notes_kind_check_97646eca", + "prefix": "notes_kind_check" + } + ], "columns": { "body": { "codecId": "pg/text@1", @@ -225,7 +232,13 @@ "kind": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Note.kind", + "namespaceId": "public", + "plane": "storage" + } }, "pinned": { "codecId": "pg/bool@1", @@ -339,13 +352,21 @@ }, "uniques": [] } + }, + "valueSet": { + "Note.kind": { + "kind": "valueSet", + "values": [ + "memo" + ] + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "8973aa4e3654dc5083f7ff15a7c187e3e590f977b4171bb816cd6566fa4db93b" + "storageHash": "87d224586379cdc42fda508d70ac88410eb9795e164b6031ef5a9dd9a5a1b759" }, "capabilities": { "postgres": { diff --git a/test/integration/test/sql-orm-client/fixtures/contract.ts b/test/integration/test/sql-orm-client/fixtures/contract.ts index 1d2d4af56dd0..0a93d76426bb 100644 --- a/test/integration/test/sql-orm-client/fixtures/contract.ts +++ b/test/integration/test/sql-orm-client/fixtures/contract.ts @@ -168,7 +168,7 @@ const User = UserBase.relations({ ], })); -const baseContract = defineContract({ +export const contract = defineContract({ extensions: { pgvector }, models: { User, @@ -184,53 +184,3 @@ const baseContract = defineContract({ ProjectLink, }, }); - -const defaultNamespace = baseContract.domain.namespaces['public']!; -const userModel = defaultNamespace.models['User']!; - -export const contract = { - ...baseContract, - domain: { - namespaces: { - ...baseContract.domain.namespaces, - public: { - ...defaultNamespace, - models: { - ...defaultNamespace.models, - User: { - ...userModel, - fields: { - ...userModel.fields, - address: { - many: false as const, - nullable: true as const, - type: { kind: 'valueObject' as const, name: 'Address' }, - }, - }, - }, - }, - valueObjects: { - Address: { - fields: { - street: { - many: false as const, - nullable: false as const, - type: { kind: 'scalar' as const, codecId: 'pg/text@1' as const }, - }, - city: { - many: false as const, - nullable: false as const, - type: { kind: 'scalar' as const, codecId: 'pg/text@1' as const }, - }, - zip: { - many: false as const, - nullable: true as const, - type: { kind: 'scalar' as const, codecId: 'pg/text@1' as const }, - }, - }, - }, - }, - }, - }, - }, -}; diff --git a/test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.d.ts index daee95a0333f..cc2aaf11a359 100644 --- a/test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -599,7 +601,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/fragment-namespace/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/fragment-namespace/generated/contract.d.ts index 4a0bc8f4834c..069381911c50 100644 --- a/test/integration/test/sql-orm-client/fixtures/fragment-namespace/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/fragment-namespace/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -493,7 +495,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts index 1f2a81a3b2f8..9bfdbf381d6b 100644 --- a/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts @@ -25,7 +25,6 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, } from '@internal/contract/types'; export type StorageHash = @@ -277,36 +276,23 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], - CodecTypes, - ParameterizedOutputTypeFunctions, - FieldTypeOverrides, - 'output' ->; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], - CodecTypes, - ParameterizedOutputTypeFunctions, - FieldTypeOverrides, - 'input' ->; -export type AddressOutput = ValueObjectOutputTypes['public']['Address']; -export type AddressInput = ValueObjectInputTypes['public']['Address']; + type FieldTypeOverrides = {}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -1169,7 +1155,7 @@ type ContractBase = Omit< readonly fields: { readonly address: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/jsonb@1' }; }; readonly email: { readonly nullable: false; @@ -1335,24 +1321,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; }; }; }; @@ -1446,24 +1414,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: true; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/generated/contract.json index fe24243054be..2dc5dd47490e 100644 --- a/test/integration/test/sql-orm-client/fixtures/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/generated/contract.json @@ -505,8 +505,8 @@ "address": { "nullable": true, "type": { - "kind": "valueObject", - "name": "Address" + "codecId": "pg/jsonb@1", + "kind": "scalar" } }, "email": { @@ -778,33 +778,6 @@ "table": "user_tags" } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "street": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "zip": { - "nullable": true, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } diff --git a/test/integration/test/sql-orm-client/fixtures/integer-representation-sqlite/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/integer-representation-sqlite/generated/contract.d.ts index 7fd5d0eca066..ea074a87babd 100644 --- a/test/integration/test/sql-orm-client/fixtures/integer-representation-sqlite/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/integer-representation-sqlite/generated/contract.d.ts @@ -139,14 +139,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -388,7 +390,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/integer-representation/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/integer-representation/generated/contract.d.ts index c29c79d99878..714f74a2d5ab 100644 --- a/test/integration/test/sql-orm-client/fixtures/integer-representation/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/integer-representation/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -548,7 +550,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts index cf321d22b5ac..39b31348475c 100644 --- a/test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -795,7 +797,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/mn-psl/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/mn-psl/generated/contract.d.ts index 24c71ae44ad8..f9c5eac71171 100644 --- a/test/integration/test/sql-orm-client/fixtures/mn-psl/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/mn-psl/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -624,7 +626,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/non-identifier-names/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/non-identifier-names/generated/contract.d.ts index ddc8a0f51c8b..53265ec213d5 100644 --- a/test/integration/test/sql-orm-client/fixtures/non-identifier-names/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/non-identifier-names/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -423,7 +425,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/on-update-only/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/on-update-only/generated/contract.d.ts index 37b0bd85e647..88b672847ec3 100644 --- a/test/integration/test/sql-orm-client/fixtures/on-update-only/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/on-update-only/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -460,7 +462,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.d.ts index cd940d8aece6..ef7760ab7389 100644 --- a/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.d.ts @@ -23,7 +23,7 @@ import type { } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'20f073da80d13b3d30479280c2ff02efa9594c42efebfbb14f233b93e9df3d2f'>; + StorageHashBase<'b4c46998d16621f5bfb704da34f41ffc3a24a4029fb2c0ef7d2c10f9878597cc'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -269,22 +269,60 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type FieldTypeOverrides = {}; +type FieldTypeOverrides = { + readonly a: { + readonly models: { + readonly Task: { + readonly type: { + readonly output: 'b-epic' | 'bug' | 'c-epic' | 'chore' | 'feature'; + readonly input: 'b-epic' | 'bug' | 'c-epic' | 'chore' | 'feature'; + }; + }; + }; + }; + readonly b: { + readonly models: { + readonly Doc: { readonly kind: { readonly output: 'memo'; readonly input: 'memo' } }; + }; + }; + readonly c: { + readonly models: { + readonly Doc: { readonly kind: { readonly output: 'note'; readonly input: 'note' } }; + }; + }; +}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; -type ColumnTypeOverrides = {}; +type ColumnTypeOverrides = { + readonly a: { + readonly tasks: { + readonly type: { + readonly output: 'b-epic' | 'bug' | 'c-epic' | 'chore' | 'feature'; + readonly input: 'b-epic' | 'bug' | 'c-epic' | 'chore' | 'feature'; + }; + }; + }; + readonly b: { + readonly docs: { readonly kind: { readonly output: 'memo'; readonly input: 'memo' } }; + }; + readonly c: { + readonly docs: { readonly kind: { readonly output: 'note'; readonly input: 'note' } }; + }; +}; export type StorageColumnTypes = ColumnTypesFromStorage< ContractBase['storage'], CodecTypes, @@ -548,6 +586,12 @@ type ContractBase = Omit< ]; }; }; + readonly valueSet: { + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['b-epic', 'bug', 'c-epic', 'chore', 'feature']; + }; + }; }; }; readonly b: { @@ -693,6 +737,9 @@ type ContractBase = Omit< ]; }; }; + readonly valueSet: { + readonly 'Doc.kind': { readonly kind: 'valueSet'; readonly values: readonly ['memo'] }; + }; }; }; readonly c: { @@ -760,6 +807,9 @@ type ContractBase = Omit< ]; }; }; + readonly valueSet: { + readonly 'Doc.kind': { readonly kind: 'valueSet'; readonly values: readonly ['note'] }; + }; }; }; readonly public: { @@ -1101,7 +1151,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.json index fda2e0e38e84..a3aeeaaa2844 100644 --- a/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/polymorphism-namespaces/generated/contract.json @@ -565,6 +565,13 @@ "uniques": [] }, "tasks": { + "checks": [ + { + "expression": "\"type\" IN ('b-epic', 'bug', 'c-epic', 'chore', 'feature')", + "name": "tasks_type_check_fb4446ef", + "prefix": "tasks_type_check" + } + ], "columns": { "id": { "codecId": "pg/int4@1", @@ -589,7 +596,13 @@ "type": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Task.type", + "namespaceId": "a", + "plane": "storage" + } } }, "foreignKeys": [ @@ -630,6 +643,18 @@ }, "uniques": [] } + }, + "valueSet": { + "Task.type": { + "kind": "valueSet", + "values": [ + "b-epic", + "bug", + "c-epic", + "chore", + "feature" + ] + } } }, "id": "a", @@ -639,6 +664,13 @@ "entries": { "table": { "docs": { + "checks": [ + { + "expression": "\"kind\" IN ('memo')", + "name": "docs_kind_check_97646eca", + "prefix": "docs_kind_check" + } + ], "columns": { "body": { "codecId": "pg/text@1", @@ -653,7 +685,13 @@ "kind": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Doc.kind", + "namespaceId": "b", + "plane": "storage" + } } }, "foreignKeys": [], @@ -806,6 +844,14 @@ }, "uniques": [] } + }, + "valueSet": { + "Doc.kind": { + "kind": "valueSet", + "values": [ + "memo" + ] + } } }, "id": "b", @@ -815,6 +861,13 @@ "entries": { "table": { "docs": { + "checks": [ + { + "expression": "\"kind\" IN ('note')", + "name": "docs_kind_check_144bbad4", + "prefix": "docs_kind_check" + } + ], "columns": { "body": { "codecId": "pg/text@1", @@ -829,7 +882,13 @@ "kind": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Doc.kind", + "namespaceId": "c", + "plane": "storage" + } } }, "foreignKeys": [], @@ -884,6 +943,14 @@ }, "uniques": [] } + }, + "valueSet": { + "Doc.kind": { + "kind": "valueSet", + "values": [ + "note" + ] + } } }, "id": "c", @@ -897,7 +964,7 @@ "kind": "postgres-schema" } }, - "storageHash": "20f073da80d13b3d30479280c2ff02efa9594c42efebfbb14f233b93e9df3d2f" + "storageHash": "b4c46998d16621f5bfb704da34f41ffc3a24a4029fb2c0ef7d2c10f9878597cc" }, "capabilities": { "postgres": { diff --git a/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.d.ts index f78b0d918171..b0bed32a24b2 100644 --- a/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.d.ts @@ -23,7 +23,7 @@ import type { } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'8938bb7c885ba3bade87681ec83e5856d1a2107f4db096afde6a52bd48bea45d'>; + StorageHashBase<'819d42189875a8fbc83dedd6c2b2616a82ec5c3628dea2b618935d3597c29f95'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -269,22 +269,38 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type FieldTypeOverrides = {}; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Task: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; + }; + }; +}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; -type ColumnTypeOverrides = {}; +type ColumnTypeOverrides = { + readonly public: { + readonly tasks: { + readonly type: { readonly output: 'bug' | 'feature'; readonly input: 'bug' | 'feature' }; + }; + }; +}; export type StorageColumnTypes = ColumnTypesFromStorage< ContractBase['storage'], CodecTypes, @@ -479,6 +495,12 @@ type ContractBase = Omit< ]; }; }; + readonly valueSet: { + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'feature']; + }; + }; }; }; }; @@ -632,7 +654,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.json index 7830a05c5b25..d986883f9d63 100644 --- a/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/polymorphism-self-relation/generated/contract.json @@ -272,6 +272,13 @@ "uniques": [] }, "tasks": { + "checks": [ + { + "expression": "\"type\" IN ('bug', 'feature')", + "name": "tasks_type_check_cda3f5e7", + "prefix": "tasks_type_check" + } + ], "columns": { "id": { "codecId": "pg/int4@1", @@ -296,7 +303,13 @@ "type": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Task.type", + "namespaceId": "public", + "plane": "storage" + } } }, "foreignKeys": [ @@ -337,13 +350,22 @@ }, "uniques": [] } + }, + "valueSet": { + "Task.type": { + "kind": "valueSet", + "values": [ + "bug", + "feature" + ] + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "8938bb7c885ba3bade87681ec83e5856d1a2107f4db096afde6a52bd48bea45d" + "storageHash": "819d42189875a8fbc83dedd6c2b2616a82ec5c3628dea2b618935d3597c29f95" }, "capabilities": { "postgres": { diff --git a/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts index 741846a4cec7..3db1e40b9a97 100644 --- a/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts @@ -23,7 +23,7 @@ import type { } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'2ccba7c309ee13e4714b958e47eb88ca06415dac60cd475a51940499c3331f8a'>; + StorageHashBase<'00913b83286a9e6492e6621c3ba277633d43a309cdda39f9cb46f0a54752c1eb'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -269,22 +269,53 @@ type DefaultLiteralValue = CodecId extends keyo : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type FieldTypeOverrides = {}; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Task: { + readonly type: { + readonly output: 'bug' | 'epic' | 'feature'; + readonly input: 'bug' | 'epic' | 'feature'; + }; + }; + readonly User: { + readonly kind: { + readonly output: 'admin' | 'regular'; + readonly input: 'admin' | 'regular'; + }; + }; + }; + }; +}; export type FieldOutputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; -type ColumnTypeOverrides = {}; +type ColumnTypeOverrides = { + readonly public: { + readonly tasks: { + readonly type: { + readonly output: 'bug' | 'epic' | 'feature'; + readonly input: 'bug' | 'epic' | 'feature'; + }; + }; + readonly users: { + readonly kind: { readonly output: 'admin' | 'regular'; readonly input: 'admin' | 'regular' }; + }; + }; +}; export type StorageColumnTypes = ColumnTypesFromStorage< ContractBase['storage'], CodecTypes, @@ -884,6 +915,16 @@ type ContractBase = Omit< ]; }; }; + readonly valueSet: { + readonly 'Task.type': { + readonly kind: 'valueSet'; + readonly values: readonly ['bug', 'epic', 'feature']; + }; + readonly 'User.kind': { + readonly kind: 'valueSet'; + readonly values: readonly ['admin', 'regular']; + }; + }; }; }; }; @@ -1363,7 +1404,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json index c62e7950f392..fa8deeced427 100644 --- a/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json @@ -957,6 +957,13 @@ "uniques": [] }, "tasks": { + "checks": [ + { + "expression": "\"type\" IN ('bug', 'epic', 'feature')", + "name": "tasks_type_check_b3d5b43f", + "prefix": "tasks_type_check" + } + ], "columns": { "bug_assignee_person_id": { "codecId": "pg/int4@1", @@ -995,7 +1002,13 @@ "type": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "Task.type", + "namespaceId": "public", + "plane": "storage" + } } }, "foreignKeys": [ @@ -1124,6 +1137,13 @@ "uniques": [] }, "users": { + "checks": [ + { + "expression": "\"kind\" IN ('admin', 'regular')", + "name": "users_kind_check_dc69a8d4", + "prefix": "users_kind_check" + } + ], "columns": { "account_id": { "codecId": "pg/int4@1", @@ -1147,7 +1167,13 @@ "kind": { "codecId": "pg/text@1", "dataType": "pg/text", - "nullable": false + "nullable": false, + "valueSet": { + "entityKind": "valueSet", + "entityName": "User.kind", + "namespaceId": "public", + "plane": "storage" + } }, "name": { "codecId": "pg/text@1", @@ -1203,13 +1229,30 @@ }, "uniques": [] } + }, + "valueSet": { + "Task.type": { + "kind": "valueSet", + "values": [ + "bug", + "epic", + "feature" + ] + }, + "User.kind": { + "kind": "valueSet", + "values": [ + "admin", + "regular" + ] + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "2ccba7c309ee13e4714b958e47eb88ca06415dac60cd475a51940499c3331f8a" + "storageHash": "00913b83286a9e6492e6621c3ba277633d43a309cdda39f9cb46f0a54752c1eb" }, "capabilities": { "postgres": { diff --git a/test/integration/test/sql-orm-client/fixtures/scalar-lists/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/scalar-lists/generated/contract.d.ts index 42b4486e6950..05bc1ad5f7b4 100644 --- a/test/integration/test/sql-orm-client/fixtures/scalar-lists/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/scalar-lists/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -437,7 +439,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/scope-namespace/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/scope-namespace/generated/contract.d.ts index bc89d2cf7208..dd6df2b631f4 100644 --- a/test/integration/test/sql-orm-client/fixtures/scope-namespace/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/scope-namespace/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -493,7 +495,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/self-relations/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/self-relations/generated/contract.d.ts index 2657cb8f7381..86c1f2b4cb17 100644 --- a/test/integration/test/sql-orm-client/fixtures/self-relations/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/self-relations/generated/contract.d.ts @@ -275,14 +275,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -856,7 +858,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.d.ts index 62f3fe8bdef7..ac6b16a295f1 100644 --- a/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.d.ts +++ b/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.d.ts @@ -20,11 +20,11 @@ import type { NamespaceId, ProfileHashBase, StorageHashBase, - ValueObjectTypesFromDomain, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'dfd3186b551a7868c3339bf4a5f80ca2a3ad4aa43a610cc64189db7c62a1e475'>; + StorageHashBase<'388580bf7258d1c37e6113016201f45f9054b3a56527588f9d8f6646473ea9ef'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -269,15 +269,15 @@ type DefaultLiteralValue = CodecId extends keyo : CodecTypes[CodecId]['json'] : Encoded; type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; -type ValueObjectOutputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, 'output' >; -type ValueObjectInputTypes = ValueObjectTypesFromDomain< - ContractBase['domain']['namespaces'], +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, @@ -291,14 +291,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -571,6 +573,16 @@ type ContractBase = Omit< foreignKeys: readonly []; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -706,7 +718,13 @@ type ContractBase = Omit< readonly fields: { readonly addresses: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; readonly many: { readonly elementNullable: false }; }; readonly id: { @@ -772,16 +790,6 @@ type ContractBase = Omit< }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; }; }; }; @@ -814,16 +822,6 @@ type ContractBase = Omit< }; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.json index d595f985f0e7..e28ee18ce55c 100644 --- a/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.json +++ b/test/integration/test/sql-orm-client/fixtures/soft-delete/generated/contract.json @@ -204,8 +204,11 @@ }, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "public", + "plane": "storage" } }, "id": { @@ -313,19 +316,6 @@ "table": "users" } } - }, - "valueObjects": { - "Address": { - "fields": { - "street": { - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } @@ -481,6 +471,11 @@ }, "tags": { "checks": [ + { + "expression": "jsonb_path_match(\"addresses\", 'strict $.type() == \"array\" && !exists($[*] ? (!((@.type() == \"object\" && exists(@.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists(@.keyvalue() ? (@.key != \"street\"))))))') IS TRUE", + "name": "tags_addresses_shape_c752a33a", + "prefix": "tags_addresses_shape" + }, { "expression": "array_position(\"labels\", NULL) IS NULL", "name": "tags_labels_elem_not_null_0c04ab5c", @@ -559,13 +554,26 @@ }, "uniques": [] } + }, + "valueObject": { + "Address": { + "fields": { + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "dfd3186b551a7868c3339bf4a5f80ca2a3ad4aa43a610cc64189db7c62a1e475" + "storageHash": "388580bf7258d1c37e6113016201f45f9054b3a56527588f9d8f6646473ea9ef" }, "capabilities": { "postgres": { diff --git a/test/integration/test/sql-orm-client/fixtures/value-object-checks/contract.prisma b/test/integration/test/sql-orm-client/fixtures/value-object-checks/contract.prisma new file mode 100644 index 000000000000..6f0bea1c83f7 --- /dev/null +++ b/test/integration/test/sql-orm-client/fixtures/value-object-checks/contract.prisma @@ -0,0 +1,62 @@ +// use prisma-8 + +// Value objects with nested, list, enum and self-referring members, and a +// polymorphic base whose discriminator has no enum. On Postgres each value +// object column and the discriminator column derive a check. + +enum PlaceKind { + @@type("pg/text@1") + home + work +} + +type Geo { + lat Float + lng Float +} + +type Place { + label String + kind PlaceKind + note String? + tags String[] + ratings Int?[] + geo Geo + stops Geo[] +} + +type NavItem { + title String + children NavItem[] +} + +model Trip { + id Int @id + origin Place + detour Place? + legs Place[] + menu NavItem? + + @@map("trip") +} + +model Shape { + id Int @id + kind String + name String + + @@discriminator(kind) + @@map("shape") +} + +model Circle { + radius Int? + + @@base(Shape, "circle") +} + +model Square { + side Int? + + @@base(Shape, "square") +} diff --git a/test/integration/test/sql-orm-client/fixtures/value-objects/contract.prisma b/test/integration/test/sql-orm-client/fixtures/value-objects/contract.prisma new file mode 100644 index 000000000000..5df35551e994 --- /dev/null +++ b/test/integration/test/sql-orm-client/fixtures/value-objects/contract.prisma @@ -0,0 +1,18 @@ +// use prisma-8 + +// Emitted fixture for the sql-orm-client value object type tests. +// `User.address` holds an `Address` value object in a jsonb column. + +type Address { + street String + city String + zip String? +} + +model User { + id Int @id + name String + address Address? + + @@map("users") +} diff --git a/test/integration/test/sql-orm-client/fixtures/value-objects/generated/contract.d.ts b/test/integration/test/sql-orm-client/fixtures/value-objects/generated/contract.d.ts new file mode 100644 index 000000000000..00525f6dcdc3 --- /dev/null +++ b/test/integration/test/sql-orm-client/fixtures/value-objects/generated/contract.d.ts @@ -0,0 +1,485 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as PgTypes, + ParameterizedOutputTypesForTypeParams as PgParameterizedOutputTypes, +} from '@internal/target-postgres/codec-types'; +import type { QueryOperationTypes as PgTargetQueryOps } from '@internal/target-postgres/operation-types'; + +import type { + ColumnTypesFromStorage, + ContractWithTypeMaps, + RelationKeys, + TypeMaps as TypeMapsType, +} from '@internal/sql-contract/types'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@internal/contract/types'; + +export type StorageHash = + StorageHashBase<'312f3bd7f0c8c146f44a26148cb251da42b2d8d449c3d6ad0bdf7ae7823df718'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; + +export type CodecTypes = PgTypes; +export type LaneCodecTypes = CodecTypes; +export type QueryOperationTypes = PgTargetQueryOps; +export type AggregateTypes = { + readonly avg: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + }; + }; + readonly avgDecimal: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + }; + }; + readonly count: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + }; + readonly countBigInt: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + }; + readonly max: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly min: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly sum: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + }; + }; + readonly sumBigInt: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + }; + }; +}; +type DefaultLiteralValue = CodecId extends keyof CodecTypes + ? Encoded extends CodecTypes[CodecId]['json'] + ? Encoded + : CodecTypes[CodecId]['json'] + : Encoded; +type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['public']['Address']; +export type AddressInput = ValueObjectInputTypes['public']['Address']; +type FieldTypeOverrides = {}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; +type ColumnTypeOverrides = {}; +export type StorageColumnTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'output' +>; +export type StorageColumnInputTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'input' +>; + +export namespace Models { + export type public_User = { + address: FieldOutputTypes['public']['User']['address']; + id: FieldOutputTypes['public']['User']['id']; + name: FieldOutputTypes['public']['User']['name']; + readonly [RelationKeys]?: never; + }; +} + +export declare const models: { + public: { + User: Models.public_User; + }; +}; + +export type TypeMaps = TypeMapsType< + CodecTypes, + QueryOperationTypes, + FieldOutputTypes, + FieldInputTypes, + StorageColumnTypes, + StorageColumnInputTypes, + AggregateTypes +>; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly public: { + readonly id: 'public'; + readonly kind: 'postgres-schema'; + readonly entries: { + readonly table: { + readonly users: { + columns: { + readonly address: { + readonly dataType: 'pg/jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly many: false; + }; + readonly name: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'postgres'; + readonly targetFamily: 'sql'; + readonly roots: { + readonly users: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly public: { + readonly models: { + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int4@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'users'; + readonly namespaceId: 'public'; + readonly fields: { + readonly address: { readonly column: 'address' }; + readonly id: { readonly column: 'id' }; + readonly name: { readonly column: 'name' }; + }; + }; + }; + }; + }; + }; + }; + readonly capabilities: { + readonly postgres: { + readonly distinctOn: true; + readonly forKeyShare: true; + readonly forNoKeyUpdate: true; + readonly jsonAgg: true; + readonly lateral: true; + readonly limit: true; + readonly orderBy: true; + readonly returning: true; + }; + readonly sql: { + readonly checkConstraint: true; + readonly defaultInInsert: true; + readonly enums: true; + readonly forShare: true; + readonly forUpdate: true; + readonly insertOnConflictSkip: true; + readonly insertOnConflictWithoutTarget: true; + readonly lateral: true; + readonly lockNowait: true; + readonly lockOf: true; + readonly lockSkipLocked: true; + readonly returning: true; + readonly scalarList: true; + }; + }; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = ContractWithTypeMaps; + +export type Namespaces = Contract['storage']['namespaces']; diff --git a/test/integration/test/sql-orm-client/fixtures/value-objects/generated/contract.json b/test/integration/test/sql-orm-client/fixtures/value-objects/generated/contract.json new file mode 100644 index 000000000000..ba2542ad8405 --- /dev/null +++ b/test/integration/test/sql-orm-client/fixtures/value-objects/generated/contract.json @@ -0,0 +1,172 @@ +{ + "schemaVersion": "1", + "targetFamily": "sql", + "target": "postgres", + "profileHash": "3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2", + "roots": { + "users": { + "model": "User", + "namespace": "public" + } + }, + "domain": { + "namespaces": { + "public": { + "models": { + "User": { + "fields": { + "address": { + "nullable": true, + "type": { + "entityKind": "valueObject", + "entityName": "Address", + "kind": "valueObject", + "namespaceId": "public", + "plane": "storage" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "pg/int4@1", + "kind": "scalar" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "fields": { + "address": { + "column": "address" + }, + "id": { + "column": "id" + }, + "name": { + "column": "name" + } + }, + "namespaceId": "public", + "table": "users" + } + } + } + } + } + }, + "storage": { + "namespaces": { + "public": { + "entries": { + "table": { + "users": { + "checks": [ + { + "expression": "\"address\" IS NULL OR jsonb_path_match(\"address\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key == \"zip\" && !(@.value.type() == \"null\" || (@.value.type() == \"string\")))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE", + "name": "users_address_shape_0c7d1c3a", + "prefix": "users_address_shape" + } + ], + "columns": { + "address": { + "codecId": "pg/jsonb@1", + "dataType": "pg/jsonb", + "nullable": true + }, + "id": { + "codecId": "pg/int4@1", + "dataType": "pg/int4", + "nullable": false + }, + "name": { + "codecId": "pg/text@1", + "dataType": "pg/text", + "nullable": false + } + }, + "foreignKeys": [], + "indexes": [], + "primaryKey": { + "columns": [ + "id" + ] + }, + "uniques": [] + } + }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } + } + }, + "id": "public", + "kind": "postgres-schema" + } + }, + "storageHash": "312f3bd7f0c8c146f44a26148cb251da42b2d8d449c3d6ad0bdf7ae7823df718" + }, + "capabilities": { + "postgres": { + "distinctOn": true, + "forKeyShare": true, + "forNoKeyUpdate": true, + "jsonAgg": true, + "lateral": true, + "limit": true, + "orderBy": true, + "returning": true + }, + "sql": { + "checkConstraint": true, + "defaultInInsert": true, + "enums": true, + "forShare": true, + "forUpdate": true, + "insertOnConflictSkip": true, + "insertOnConflictWithoutTarget": true, + "lateral": true, + "lockNowait": true, + "lockOf": true, + "lockSkipLocked": true, + "returning": true, + "scalarList": true + } + }, + "extensions": {}, + "meta": {}, + "_generated": { + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", + "message": "This file is automatically generated by \"prisma contract emit\".", + "regenerate": "To regenerate, run: prisma contract emit" + } +} \ No newline at end of file diff --git a/test/integration/test/sql-orm-client/fixtures/value-objects/prisma.config.ts b/test/integration/test/sql-orm-client/fixtures/value-objects/prisma.config.ts new file mode 100644 index 000000000000..c19c293e813c --- /dev/null +++ b/test/integration/test/sql-orm-client/fixtures/value-objects/prisma.config.ts @@ -0,0 +1,9 @@ +import { defineConfig as ormConfig } from '@internal/postgres/config'; +import { definePrismaConfig } from '@prisma/cli-engine'; + +export default definePrismaConfig({ + orm: ormConfig({ + contract: './contract.prisma', + output: 'generated', + }), +}); diff --git a/test/integration/test/sql-orm-client/include-codecs.test.ts b/test/integration/test/sql-orm-client/include-codecs.test.ts index b43efbe1f7e7..a02f9c4e5f84 100644 --- a/test/integration/test/sql-orm-client/include-codecs.test.ts +++ b/test/integration/test/sql-orm-client/include-codecs.test.ts @@ -73,11 +73,14 @@ class IncludedTextDescriptor extends CodecDescriptorImpl { * column reaches JSON, since the renderer asks the descriptor rather than * assuming. This one stores and projects text unchanged. */ -const includedTextDescriptor = postgresCodec(new IncludedTextDescriptor(), { - dataType: pgText, - jsonProjection: (expression) => expression, - factory: (descriptor, dataType) => () => new IncludedTextCodec(descriptor, dataType), -}); +const includedTextDescriptor = postgresCodec( + Object.assign(new IncludedTextDescriptor(), { jsonType: 'any' as const }), + { + dataType: pgText, + jsonProjection: (expression) => expression, + factory: (descriptor, dataType) => () => new IncludedTextCodec(descriptor, dataType), + }, +); const includedTextColumn = { codecId: TEST_INCLUDED_TEXT_CODEC_ID, } as const satisfies ColumnTypeDescriptor; diff --git a/test/integration/test/sql-orm-client/value-object-checks.test.ts b/test/integration/test/sql-orm-client/value-object-checks.test.ts new file mode 100644 index 000000000000..75d0a7035a34 --- /dev/null +++ b/test/integration/test/sql-orm-client/value-object-checks.test.ts @@ -0,0 +1,288 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import postgresAdapterControl from '@internal/adapter-postgres/control'; +import postgresAdapter from '@internal/adapter-postgres/runtime'; +import type { Contract } from '@internal/contract/types'; +import postgresDriverControl from '@internal/driver-postgres/control'; +import postgresDriver from '@internal/driver-postgres/runtime'; +import sqlFamilyControl, { INIT_ADDITIVE_POLICY } from '@internal/family-sql/control'; +import { APP_SPACE_ID, createControlStack } from '@internal/framework-components/control'; +import { instantiateExecutionStack } from '@internal/framework-components/execution'; +import { buildFabricatedMigrationEdge } from '@internal/migration-tools/aggregate'; +import { PostgresRuntimeImpl } from '@internal/postgres/runtime'; +import type { SqlStorage } from '@internal/sql-contract/types'; +import { Collection } from '@internal/sql-orm-client'; +import { createExecutionContext, createSqlExecutionStack } from '@internal/sql-runtime'; +import postgresTargetControl from '@internal/target-postgres/control'; +import postgresTarget from '@internal/target-postgres/runtime'; +import { timeouts, withDevDatabase } from '@repo/test-utils'; +import { dirname, join } from 'pathe'; +import { Client } from 'pg'; +import { describe, expect, it } from 'vitest'; +import { authorSqlContractFromPsl } from '../scalar-lists/psl-list-authoring'; + +const schema = readFileSync( + join(dirname(fileURLToPath(import.meta.url)), 'fixtures/value-object-checks/contract.prisma'), + 'utf-8', +); + +const controlStack = createControlStack({ + family: sqlFamilyControl, + target: postgresTargetControl, + adapter: postgresAdapterControl, + driver: postgresDriverControl, + extensions: [], +}); + +async function authored(): Promise> { + const result = await authorSqlContractFromPsl(schema); + expect(result.diagnostics).toEqual([]); + const { contract } = result; + if (contract === undefined) throw new Error('the fixture did not author a contract'); + return contract; +} + +const frameworkComponents = [ + postgresTargetControl, + postgresAdapterControl, + postgresDriverControl, +] as const; + +/** Plans the contract against the empty database and applies it, as `db update` does, then verifies the database against it. */ +async function pushAndVerify(contract: Contract, connectionString: string) { + const family = sqlFamilyControl.create(controlStack); + const driver = await postgresDriverControl.create(connectionString); + try { + const planResult = postgresTargetControl + .createPlanner(postgresAdapterControl.create(controlStack)) + .plan({ + contract, + schema: await family.introspect({ driver, contract }), + policy: INIT_ADDITIVE_POLICY, + fromContract: null, + origin: null, + statements: [], + frameworkComponents, + spaceId: APP_SPACE_ID, + snapshotsImportPath: '../../snapshots', + }); + if (planResult.kind !== 'success') throw new Error(JSON.stringify(planResult)); + const { plan } = planResult; + const applied = await postgresTargetControl.createRunner(family).execute({ + driver, + perSpaceOptions: [ + { + space: plan.spaceId ?? APP_SPACE_ID, + plan, + migrationEdges: [ + buildFabricatedMigrationEdge({ + currentMarkerStorageHash: plan.origin?.storageHash, + destinationStorageHash: plan.destination.storageHash, + operationCount: plan.operations.length, + }), + ], + driver, + destinationContract: contract, + policy: INIT_ADDITIVE_POLICY, + frameworkComponents, + }, + ], + }); + if (!applied.ok) throw new Error(JSON.stringify(applied.failure)); + return family.verifySchema({ + contract, + schema: await family.introspect({ driver, contract }), + strict: true, + frameworkComponents, + }); + } finally { + await driver.close(); + } +} + +/** One connection for the ORM and for raw SQL: the PGlite-backed dev server takes one at a time. */ +async function withRuntime( + contract: Contract, + connectionString: string, + fn: (runtime: PostgresRuntimeImpl, client: Client) => Promise, +): Promise { + const client = new Client({ connectionString }); + await client.connect(); + const stack = createSqlExecutionStack({ + target: postgresTarget, + adapter: postgresAdapter, + driver: { + ...postgresDriver, + create() { + return postgresDriver.create({ cursor: { disabled: true } }); + }, + }, + }); + const instance = instantiateExecutionStack(stack); + if (instance.adapter === undefined || instance.driver === undefined) { + throw new Error('the Postgres execution stack has no adapter or driver'); + } + await instance.driver.connect({ kind: 'pgClient', client, ownedByDriver: false }); + const runtime = new PostgresRuntimeImpl({ + closeRefusal: undefined, + context: createExecutionContext({ contract, stack }), + adapter: instance.adapter, + driver: instance.driver, + middleware: [], + }); + try { + await fn(runtime, client); + } finally { + await runtime.close(); + await client.end(); + } +} + +const geo = { lat: 59.9, lng: 10.7 }; +const place = { + label: 'Home', + kind: 'home', + tags: ['a'], + ratings: [1, null], + geo, + stops: [geo], +}; + +function without(key: string, value: Record) { + return Object.fromEntries(Object.entries(value).filter(([name]) => name !== key)); +} + +async function insertTrip( + client: Client, + columns: Partial>, +) { + const values = { origin: place, legs: [], ...columns }; + const names = Object.keys(values); + await client.query( + `insert into trip (id, ${names.map((name) => `"${name}"`).join(', ')}) values ((select coalesce(max(id), 0) + 1 from trip), ${names.map((_, index) => `$${index + 1}::jsonb`).join(', ')})`, + Object.values(values).map((value) => (value === undefined ? null : JSON.stringify(value))), + ); +} + +const refused = { code: '23514' }; + +describe('derived checks of value object and discriminator columns on Postgres', () => { + it( + 'are created by db update, verify cleanly, accept the rows the ORM writes and refuse rows that do not fit', + async () => { + const contract = await authored(); + await withDevDatabase(async ({ connectionString }) => { + expect(await pushAndVerify(contract, connectionString)).toMatchObject({ ok: true }); + + await withRuntime(contract, connectionString, async (runtime, client) => { + const tables = contract.storage.namespaces['public']?.entries.table ?? {}; + const declared = ['trip', 'shape'].flatMap((table) => tables[table]?.checks ?? []); + const live = await client.query<{ conname: string }>( + "select conname from pg_constraint where contype = 'c' and conrelid in ('trip'::regclass, 'shape'::regclass) order by conname", + ); + expect({ + prefixes: declared.map((check) => check.prefix), + live: live.rows.map((row) => row.conname), + }).toEqual({ + prefixes: [ + 'trip_detour_shape', + 'trip_legs_shape', + 'trip_menu_shape', + 'trip_origin_shape', + 'shape_kind_check', + ], + live: declared.map((check) => check.name).sort(), + }); + + const context = createExecutionContext({ + contract, + stack: createSqlExecutionStack({ target: postgresTarget, adapter: postgresAdapter }), + }); + const trips = new Collection({ runtime, context }, 'Trip', { namespaceId: 'public' }); + await trips.create({ + id: 1, + origin: { ...place, note: 'gate code 4' }, + detour: { ...place, note: null, ratings: [] }, + legs: [place, place], + menu: { title: 'root', children: [{ title: 'child', children: [] }] }, + } as never); + await trips.create({ id: 2, origin: place, legs: [] } as never); + const written = await client.query( + 'select id, detour is null as detour_sql_null, menu is null as menu_sql_null from trip order by id', + ); + expect(written.rows).toEqual([ + { id: 1, detour_sql_null: false, menu_sql_null: false }, + { id: 2, detour_sql_null: true, menu_sql_null: true }, + ]); + + const acceptedRows: Record>> = { + 'SQL NULL in a nullable column': { detour: undefined }, + 'JSON null in a nullable column': { detour: null }, + 'a nullable member that is absent': { origin: place }, + 'a nullable member that is JSON null': { origin: { ...place, note: null } }, + 'null elements in a list whose elements are nullable': { + origin: { ...place, ratings: [null, 2] }, + }, + 'any object below where a value object refers back into itself': { + menu: { title: 'a', children: [{ anything: 1 }] }, + }, + }; + const refusedRows: Record>> = { + 'a missing key': { origin: without('label', place) }, + 'an extra key': { origin: { ...place, extra: 1 } }, + 'a wrong JSON type': { origin: { ...place, label: 1 } }, + 'a value outside the enum': { origin: { ...place, kind: 'school' } }, + 'a null element in a list whose elements are not nullable': { + origin: { ...place, tags: [null] }, + }, + 'a list member that is not an array': { origin: { ...place, ratings: 'x' } }, + 'a nested object with a wrong JSON type': { + origin: { ...place, geo: { lat: 'x', lng: 1 } }, + }, + 'a nested object with a missing key': { origin: { ...place, geo: { lat: 1 } } }, + 'a list of nested objects with an extra key': { + origin: { ...place, stops: [{ ...geo, alt: 1 }] }, + }, + 'JSON null in a required column': { origin: null }, + 'a null element in a list column': { legs: [null] }, + 'an object in a list column': { legs: place }, + 'a member below where a value object refers back into itself that is not an object': { + menu: { title: 'a', children: [1] }, + }, + 'a missing key above where a value object refers back into itself': { + menu: { children: [] }, + }, + }; + const outcomes = async (rows: Record>>) => { + const results: Record = {}; + for (const [name, columns] of Object.entries(rows)) { + results[name] = await insertTrip(client, columns).then( + () => 'accepted', + (error: { code?: string }) => ({ code: error.code }), + ); + } + return results; + }; + expect({ + accepted: await outcomes(acceptedRows), + refused: await outcomes(refusedRows), + }).toEqual({ + accepted: Object.fromEntries( + Object.keys(acceptedRows).map((name) => [name, 'accepted']), + ), + refused: Object.fromEntries(Object.keys(refusedRows).map((name) => [name, refused])), + }); + + const shapes = new Collection({ runtime, context }, 'Shape', { namespaceId: 'public' }); + await shapes + .variant('circle' as never) + .create({ id: 1, name: 'wheel', radius: 2 } as never); + await expect( + client.query(`insert into shape (id, kind, name) values (2, 'triangle', 'x')`), + ).rejects.toMatchObject(refused); + }); + }); + }, + timeouts.spinUpPpgDev, + ); +}); diff --git a/test/integration/test/temporal-defaults/_fixture-aliases/generated/contract.d.ts b/test/integration/test/temporal-defaults/_fixture-aliases/generated/contract.d.ts index 7f140d7dc8a0..49794a4a1845 100644 --- a/test/integration/test/temporal-defaults/_fixture-aliases/generated/contract.d.ts +++ b/test/integration/test/temporal-defaults/_fixture-aliases/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -525,7 +527,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/temporal-defaults/_fixture-attributes/generated/contract.d.ts b/test/integration/test/temporal-defaults/_fixture-attributes/generated/contract.d.ts index 3b6b863b22c5..45dc1e666356 100644 --- a/test/integration/test/temporal-defaults/_fixture-attributes/generated/contract.d.ts +++ b/test/integration/test/temporal-defaults/_fixture-attributes/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -1285,7 +1287,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/temporal-defaults/_fixture-now/generated/contract.d.ts b/test/integration/test/temporal-defaults/_fixture-now/generated/contract.d.ts index 3b6b863b22c5..45dc1e666356 100644 --- a/test/integration/test/temporal-defaults/_fixture-now/generated/contract.d.ts +++ b/test/integration/test/temporal-defaults/_fixture-now/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -1285,7 +1287,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/temporal-defaults/_fixture-timestamp/generated/contract.d.ts b/test/integration/test/temporal-defaults/_fixture-timestamp/generated/contract.d.ts index 207db1337b24..bca36943d459 100644 --- a/test/integration/test/temporal-defaults/_fixture-timestamp/generated/contract.d.ts +++ b/test/integration/test/temporal-defaults/_fixture-timestamp/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -483,7 +485,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/temporal-defaults/_fixture/generated/contract.d.ts b/test/integration/test/temporal-defaults/_fixture/generated/contract.d.ts index 7f140d7dc8a0..49794a4a1845 100644 --- a/test/integration/test/temporal-defaults/_fixture/generated/contract.d.ts +++ b/test/integration/test/temporal-defaults/_fixture/generated/contract.d.ts @@ -276,14 +276,16 @@ export type FieldOutputTypes = FieldTypesFromDomain< CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'output' + 'output', + ContractBase['storage']['namespaces'] >; export type FieldInputTypes = FieldTypesFromDomain< ContractBase['domain']['namespaces'], CodecTypes, ParameterizedOutputTypeFunctions, FieldTypeOverrides, - 'input' + 'input', + ContractBase['storage']['namespaces'] >; type ColumnTypeOverrides = {}; export type StorageColumnTypes = ColumnTypesFromStorage< @@ -525,7 +527,6 @@ type ContractBase = Omit< }; }; readonly meta: {}; - readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/migration.json new file mode 100644 index 000000000000..eb148543a554 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/migration.json @@ -0,0 +1,7 @@ +{ + "from": null, + "to": "396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901", + "providedInvariants": [], + "createdAt": "2026-04-09T10:30:13.948Z", + "migrationHash": "a4dbf4e008b176a2fed392de87662f68cd4c2f537b60cf760e234048e6f55abe" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/migration.ts new file mode 100644 index 000000000000..24f8a81660b0 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/migration.ts @@ -0,0 +1,16 @@ +import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as End } from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract'; +import endContract from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json' with { + type: 'json', +}; + +class InitialMigration extends Migration { + override readonly endContractJson = endContract; + + override get operations() { + return [createIndex('users', [{ field: 'email', direction: 1 }], { unique: true })]; + } +} + +export default InitialMigration; +MigrationCLI.run(import.meta.url, InitialMigration); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/ops.json new file mode 100644 index 000000000000..533b620b26a8 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260409T1030_migration/ops.json @@ -0,0 +1,101 @@ +[ + { + "id": "index.users.create(email:1)", + "label": "Create index on users (email:1)", + "operationClass": "additive", + "precheck": [ + { + "description": "index does not already exist on users", + "source": { + "kind": "listIndexes", + "collection": "users" + }, + "filter": { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "email": 1 + } + }, + "expect": "notExists", + "object": "an index on { email: 1 } in \"users\"" + } + ], + "execute": [ + { + "description": "create index on users", + "command": { + "kind": "createIndex", + "collection": "users", + "keys": [ + { + "field": "email", + "direction": 1 + } + ], + "unique": true, + "name": "email_1" + } + } + ], + "postcheck": [ + { + "description": "index exists on users", + "source": { + "kind": "listIndexes", + "collection": "users" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "email": 1 + } + }, + { + "kind": "field", + "field": "unique", + "op": "$eq", + "value": true + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "sparse", + "op": "$eq", + "value": true + } + }, + { + "kind": "exists", + "field": "expireAfterSeconds", + "exists": false + }, + { + "kind": "exists", + "field": "partialFilterExpression", + "exists": false + }, + { + "kind": "exists", + "field": "wildcardProjection", + "exists": false + }, + { + "kind": "exists", + "field": "collation", + "exists": false + } + ] + }, + "expect": "exists" + } + ] + } +] diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.json new file mode 100644 index 000000000000..8ee13dc3e337 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.json @@ -0,0 +1,7 @@ +{ + "from": "396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901", + "to": "95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9", + "providedInvariants": [], + "createdAt": "2026-06-26T16:05:45.955Z", + "migrationHash": "08ac0867c606d262ac4dc80b3ad7f1f0afc8418bd72fe957c13c9c549bfc4651" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.ts new file mode 100755 index 000000000000..9284d486566d --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.ts @@ -0,0 +1,65 @@ +#!/usr/bin/env -S node +import { collMod, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as End } from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract'; +import endContract from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json' with { + type: 'json', +}; +import type { Contract as Start } from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract'; +import startContract from '../../snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json' with { + type: 'json', +}; + +class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; + + override get operations() { + return [ + collMod( + 'users', + { + validator: { + $jsonSchema: { + additionalProperties: false, + bsonType: 'object', + properties: { + _id: { bsonType: 'objectId' }, + address: { + oneOf: [ + { bsonType: 'null' }, + { + additionalProperties: false, + bsonType: 'object', + properties: { + city: { bsonType: 'string' }, + country: { bsonType: 'string' }, + street: { bsonType: 'string' }, + zip: { bsonType: ['null', 'string'] }, + }, + required: ['city', 'country', 'street'], + }, + ], + }, + bio: { bsonType: ['null', 'string'] }, + email: { bsonType: 'string' }, + name: { bsonType: 'string' }, + role: { bsonType: 'string', enum: ['admin', 'author', 'reader'] }, + }, + required: ['_id', 'email', 'name', 'role'], + }, + }, + validationLevel: 'strict', + validationAction: 'error', + }, + { + id: 'validator.users.update', + label: 'Update validator on users', + operationClass: 'destructive', + }, + ), + ]; + } +} + +export default M; +MigrationCLI.run(import.meta.url, M); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/ops.json new file mode 100644 index 000000000000..11605cf4708e --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1605_add_user_role_enum/ops.json @@ -0,0 +1,171 @@ +[ + { + "id": "validator.users.update", + "label": "Update validator on users", + "operationClass": "destructive", + "precheck": [ + { + "description": "collection users exists", + "source": { + "kind": "listCollections" + }, + "filter": { + "kind": "field", + "field": "name", + "op": "$eq", + "value": "users" + }, + "expect": "exists", + "object": "collection \"users\"" + } + ], + "execute": [ + { + "description": "modify users", + "command": { + "kind": "collMod", + "collection": "users", + "validator": { + "$jsonSchema": { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "address": { + "oneOf": [ + { + "bsonType": "null" + }, + { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "city": { + "bsonType": "string" + }, + "country": { + "bsonType": "string" + }, + "street": { + "bsonType": "string" + }, + "zip": { + "bsonType": ["null", "string"] + } + }, + "required": ["city", "country", "street"] + } + ] + }, + "bio": { + "bsonType": ["null", "string"] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "role": { + "bsonType": "string", + "enum": ["admin", "author", "reader"] + } + }, + "required": ["_id", "email", "name", "role"] + } + }, + "validationLevel": "strict", + "validationAction": "error" + } + } + ], + "postcheck": [ + { + "description": "validator applied on users", + "source": { + "kind": "listCollections" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "name", + "op": "$eq", + "value": "users" + }, + { + "kind": "field", + "field": "options.validationLevel", + "op": "$eq", + "value": "strict" + }, + { + "kind": "field", + "field": "options.validationAction", + "op": "$eq", + "value": "error" + }, + { + "kind": "field", + "field": "options.validator.$jsonSchema", + "op": "$eq", + "value": { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "address": { + "oneOf": [ + { + "bsonType": "null" + }, + { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "city": { + "bsonType": "string" + }, + "country": { + "bsonType": "string" + }, + "street": { + "bsonType": "string" + }, + "zip": { + "bsonType": ["null", "string"] + } + }, + "required": ["city", "country", "street"] + } + ] + }, + "bio": { + "bsonType": ["null", "string"] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "role": { + "bsonType": "string", + "enum": ["admin", "author", "reader"] + } + }, + "required": ["_id", "email", "name", "role"] + } + } + ] + }, + "expect": "exists" + } + ] + } +] diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.json new file mode 100644 index 000000000000..76235940857c --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.json @@ -0,0 +1,7 @@ +{ + "from": "95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9", + "to": "406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d", + "providedInvariants": [], + "createdAt": "2026-06-26T19:16:47.657Z", + "migrationHash": "a77e60df92e1d3ee6a5a000904e3aa1869a382d602279a64bce9038c17c0475d" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.ts new file mode 100755 index 000000000000..a5488460b751 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env -S node +import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as Start } from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract'; +import startContract from '../../snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json' with { + type: 'json', +}; +import type { Contract as End } from '../../snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract'; +import endContract from '../../snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json' with { + type: 'json', +}; + +class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; + + override get operations() { + return [ + createIndex('posts', [{ direction: 1, field: 'authorId' }], {}), + createIndex( + 'posts', + [ + { direction: -1, field: 'createdAt' }, + { direction: 1, field: 'authorId' }, + ], + {}, + ), + ]; + } +} + +export default M; +MigrationCLI.run(import.meta.url, M); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/ops.json new file mode 100644 index 000000000000..d9b60da89298 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/app/20260626T1916_add_posts_indexes/ops.json @@ -0,0 +1,210 @@ +[ + { + "id": "index.posts.create(authorId:1)", + "label": "Create index on posts (authorId:1)", + "operationClass": "additive", + "precheck": [ + { + "description": "index does not already exist on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "authorId": 1 + } + }, + "expect": "notExists", + "object": "an index on { authorId: 1 } in \"posts\"" + } + ], + "execute": [ + { + "description": "create index on posts", + "command": { + "kind": "createIndex", + "collection": "posts", + "keys": [ + { + "direction": 1, + "field": "authorId" + } + ], + "name": "authorId_1" + } + } + ], + "postcheck": [ + { + "description": "index exists on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "authorId": 1 + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "unique", + "op": "$eq", + "value": true + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "sparse", + "op": "$eq", + "value": true + } + }, + { + "kind": "exists", + "field": "expireAfterSeconds", + "exists": false + }, + { + "kind": "exists", + "field": "partialFilterExpression", + "exists": false + }, + { + "kind": "exists", + "field": "wildcardProjection", + "exists": false + }, + { + "kind": "exists", + "field": "collation", + "exists": false + } + ] + }, + "expect": "exists" + } + ] + }, + { + "id": "index.posts.create(createdAt:-1,authorId:1)", + "label": "Create index on posts (createdAt:-1, authorId:1)", + "operationClass": "additive", + "precheck": [ + { + "description": "index does not already exist on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "createdAt": -1, + "authorId": 1 + } + }, + "expect": "notExists", + "object": "an index on { createdAt: -1, authorId: 1 } in \"posts\"" + } + ], + "execute": [ + { + "description": "create index on posts", + "command": { + "kind": "createIndex", + "collection": "posts", + "keys": [ + { + "direction": -1, + "field": "createdAt" + }, + { + "direction": 1, + "field": "authorId" + } + ], + "name": "createdAt_-1_authorId_1" + } + } + ], + "postcheck": [ + { + "description": "index exists on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "createdAt": -1, + "authorId": 1 + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "unique", + "op": "$eq", + "value": true + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "sparse", + "op": "$eq", + "value": true + } + }, + { + "kind": "exists", + "field": "expireAfterSeconds", + "exists": false + }, + { + "kind": "exists", + "field": "partialFilterExpression", + "exists": false + }, + { + "kind": "exists", + "field": "wildcardProjection", + "exists": false + }, + { + "kind": "exists", + "field": "collation", + "exists": false + } + ] + }, + "expect": "exists" + } + ] + } +] diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.d.ts new file mode 100644 index 000000000000..450aa81593d9 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.d.ts @@ -0,0 +1,441 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = {}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['_id', 'email', 'name']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + }; + }; + }; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json new file mode 100644 index 000000000000..5699666ec9cd --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"models":{"Article":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"summary":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"summary":{"field":"summary"}}}},"Post":{"discriminator":{"field":"kind"},"fields":{"authorId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"content":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"author":{"cardinality":"N:1","nullable":false,"on":{"localFields":["authorId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"posts","fields":{"authorId":{"field":"authorId"},"content":{"field":"content"},"createdAt":{"field":"createdAt"},"id":{"field":"_id"},"kind":{"field":"kind"},"title":{"field":"title"}}},"variants":{"article":{"model":"Article","namespace":"__unbound__"},"tutorial":{"model":"Tutorial","namespace":"__unbound__"}}},"Tutorial":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"difficulty":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"duration":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"difficulty":{"field":"difficulty"},"duration":{"field":"duration"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"bio":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["authorId"]},"to":{"model":"Post","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"bio":{"field":"bio"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"posts":{"model":"Post","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"posts":{"indexes":[{"keys":[{"direction":1,"field":"summary"}],"kind":"mongo-index","partialFilterExpression":{"kind":"article"},"unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"enum":["article"]},"summary":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["kind","summary"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"difficulty":{"bsonType":"string"},"duration":{"bsonType":"int"},"kind":{"enum":["tutorial"]},"title":{"bsonType":"string"}},"required":["difficulty","duration","kind"]}],"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["_id","authorId","content","createdAt","kind","title"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"street":{"bsonType":"string"},"zip":{"bsonType":["null","string"]}},"required":["city","country","street"]}]},"bio":{"bsonType":["null","string"]},"email":{"bsonType":"string"},"name":{"bsonType":"string"}},"required":["_id","email","name"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901"},"target":"mongo","targetFamily":"mongo"} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.d.ts new file mode 100644 index 000000000000..80c6ac7a1cf7 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.d.ts @@ -0,0 +1,490 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly role: { + readonly output: 'admin' | 'author' | 'reader'; + readonly input: 'admin' | 'author' | 'reader'; + }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + role: FieldOutputTypes['__unbound__']['User']['role']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'authorId' }]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [ + { readonly direction: -1; readonly field: 'createdAt' }, + { readonly direction: 1; readonly field: 'authorId' }, + ]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + readonly role: { + readonly bsonType: 'string'; + readonly enum: readonly ['admin', 'author', 'reader']; + }; + }; + readonly required: readonly ['_id', 'email', 'name', 'role']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly role: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + readonly role: { readonly field: 'role' }; + }; + }; + }; + }; + readonly enum: { + readonly UserRole: { + readonly codecId: 'mongo/string@1'; + readonly members: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly UserRole: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json new file mode 100644 index 000000000000..83ba3e77dc37 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"UserRole":{"codecId":"mongo/string@1","members":[{"name":"Admin","value":"admin"},{"name":"Author","value":"author"},{"name":"Reader","value":"reader"}]}},"models":{"Article":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"summary":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"summary":{"field":"summary"}}}},"Post":{"discriminator":{"field":"kind"},"fields":{"authorId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"content":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"author":{"cardinality":"N:1","nullable":false,"on":{"localFields":["authorId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"posts","fields":{"authorId":{"field":"authorId"},"content":{"field":"content"},"createdAt":{"field":"createdAt"},"id":{"field":"_id"},"kind":{"field":"kind"},"title":{"field":"title"}}},"variants":{"article":{"model":"Article","namespace":"__unbound__"},"tutorial":{"model":"Tutorial","namespace":"__unbound__"}}},"Tutorial":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"difficulty":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"duration":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"difficulty":{"field":"difficulty"},"duration":{"field":"duration"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"bio":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"role":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"UserRole","namespaceId":"__unbound__","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["authorId"]},"to":{"model":"Post","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"bio":{"field":"bio"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"},"role":{"field":"role"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"posts":{"model":"Post","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"posts":{"indexes":[{"keys":[{"direction":1,"field":"authorId"}],"kind":"mongo-index"},{"keys":[{"direction":-1,"field":"createdAt"},{"direction":1,"field":"authorId"}],"kind":"mongo-index"},{"keys":[{"direction":1,"field":"summary"}],"kind":"mongo-index","partialFilterExpression":{"kind":"article"},"unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"enum":["article"]},"summary":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["kind","summary"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"difficulty":{"bsonType":"string"},"duration":{"bsonType":"int"},"kind":{"enum":["tutorial"]},"title":{"bsonType":"string"}},"required":["difficulty","duration","kind"]}],"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["_id","authorId","content","createdAt","kind","title"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"street":{"bsonType":"string"},"zip":{"bsonType":["null","string"]}},"required":["city","country","street"]}]},"bio":{"bsonType":["null","string"]},"email":{"bsonType":"string"},"name":{"bsonType":"string"},"role":{"bsonType":"string","enum":["admin","author","reader"]}},"required":["_id","email","name","role"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}}},"valueSet":{"UserRole":{"kind":"valueSet","values":["admin","author","reader"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d"},"target":"mongo","targetFamily":"mongo"} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts new file mode 100644 index 000000000000..c61fa21a4db6 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts @@ -0,0 +1,479 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly role: { + readonly output: 'admin' | 'author' | 'reader'; + readonly input: 'admin' | 'author' | 'reader'; + }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + role: FieldOutputTypes['__unbound__']['User']['role']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + readonly role: { + readonly bsonType: 'string'; + readonly enum: readonly ['admin', 'author', 'reader']; + }; + }; + readonly required: readonly ['_id', 'email', 'name', 'role']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly role: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + readonly role: { readonly field: 'role' }; + }; + }; + }; + }; + readonly enum: { + readonly UserRole: { + readonly codecId: 'mongo/string@1'; + readonly members: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly UserRole: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json new file mode 100644 index 000000000000..feed3a57d193 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{},"domain":{"namespaces":{"__unbound__":{"enum":{"UserRole":{"codecId":"mongo/string@1","members":[{"name":"Admin","value":"admin"},{"name":"Author","value":"author"},{"name":"Reader","value":"reader"}]}},"models":{"Article":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"summary":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"summary":{"field":"summary"}}}},"Post":{"discriminator":{"field":"kind"},"fields":{"authorId":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"content":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"createdAt":{"nullable":false,"type":{"codecId":"mongo/date@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}}},"relations":{"author":{"cardinality":"N:1","nullable":false,"on":{"localFields":["authorId"],"targetFields":["id"]},"to":{"model":"User","namespace":"__unbound__"}}},"storage":{"collection":"posts","fields":{"authorId":{"field":"authorId"},"content":{"field":"content"},"createdAt":{"field":"createdAt"},"id":{"field":"_id"},"kind":{"field":"kind"},"title":{"field":"title"}}},"variants":{"article":{"model":"Article","namespace":"__unbound__"},"tutorial":{"model":"Tutorial","namespace":"__unbound__"}}},"Tutorial":{"base":{"model":"Post","namespace":"__unbound__"},"fields":{"difficulty":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"duration":{"nullable":false,"type":{"codecId":"mongo/int32@1","kind":"scalar"}}},"relations":{},"storage":{"collection":"posts","fields":{"difficulty":{"field":"difficulty"},"duration":{"field":"duration"}}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"__unbound__","plane":"storage"}},"bio":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"mongo/objectId@1","kind":"scalar"}},"name":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"role":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"UserRole","namespaceId":"__unbound__","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["authorId"]},"to":{"model":"Post","namespace":"__unbound__"}}},"storage":{"collection":"users","fields":{"address":{"field":"address"},"bio":{"field":"bio"},"email":{"field":"email"},"id":{"field":"_id"},"name":{"field":"name"},"role":{"field":"role"}}}}}}}},"extensions":{},"meta":{},"profileHash":"251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3","roots":{"posts":{"model":"Post","namespace":"__unbound__"},"users":{"model":"User","namespace":"__unbound__"}},"schemaVersion":"1","storage":{"namespaces":{"__unbound__":{"entries":{"collection":{"posts":{"indexes":[{"keys":[{"direction":1,"field":"summary"}],"kind":"mongo-index","partialFilterExpression":{"kind":"article"},"unique":true}],"kind":"mongo-collection","validator":{"jsonSchema":{"bsonType":"object","oneOf":[{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"enum":["article"]},"summary":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["kind","summary"]},{"additionalProperties":false,"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"difficulty":{"bsonType":"string"},"duration":{"bsonType":"int"},"kind":{"enum":["tutorial"]},"title":{"bsonType":"string"}},"required":["difficulty","duration","kind"]}],"properties":{"_id":{"bsonType":"objectId"},"authorId":{"bsonType":"objectId"},"content":{"bsonType":"string"},"createdAt":{"bsonType":"date"},"kind":{"bsonType":"string"},"title":{"bsonType":"string"}},"required":["_id","authorId","content","createdAt","kind","title"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}},"users":{"kind":"mongo-collection","validator":{"jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"address":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"city":{"bsonType":"string"},"country":{"bsonType":"string"},"street":{"bsonType":"string"},"zip":{"bsonType":["null","string"]}},"required":["city","country","street"]}]},"bio":{"bsonType":["null","string"]},"email":{"bsonType":"string"},"name":{"bsonType":"string"},"role":{"bsonType":"string","enum":["admin","author","reader"]}},"required":["_id","email","name","role"]},"kind":"mongo-validator","validationAction":"error","validationLevel":"strict"}}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"mongo/string@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"mongo/string@1","kind":"scalar"}}}}},"valueSet":{"UserRole":{"kind":"valueSet","values":["admin","author","reader"]}}},"id":"__unbound__","kind":"mongo-database"}},"storageHash":"95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9"},"target":"mongo","targetFamily":"mongo"} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/src/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/src/contract.d.ts new file mode 100644 index 000000000000..80c6ac7a1cf7 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/src/contract.d.ts @@ -0,0 +1,490 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly role: { + readonly output: 'admin' | 'author' | 'reader'; + readonly input: 'admin' | 'author' | 'reader'; + }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + role: FieldOutputTypes['__unbound__']['User']['role']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'authorId' }]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [ + { readonly direction: -1; readonly field: 'createdAt' }, + { readonly direction: 1; readonly field: 'authorId' }, + ]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + readonly role: { + readonly bsonType: 'string'; + readonly enum: readonly ['admin', 'author', 'reader']; + }; + }; + readonly required: readonly ['_id', 'email', 'name', 'role']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly role: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + readonly role: { readonly field: 'role' }; + }; + }; + }; + }; + readonly enum: { + readonly UserRole: { + readonly codecId: 'mongo/string@1'; + readonly members: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly UserRole: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/src/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/src/contract.json new file mode 100644 index 000000000000..2809c2eb8410 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/mongo/src/contract.json @@ -0,0 +1,583 @@ +{ + "schemaVersion": "1", + "targetFamily": "mongo", + "target": "mongo", + "profileHash": "251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3", + "roots": { + "posts": { + "model": "Post", + "namespace": "__unbound__" + }, + "users": { + "model": "User", + "namespace": "__unbound__" + } + }, + "domain": { + "namespaces": { + "__unbound__": { + "enum": { + "UserRole": { + "codecId": "mongo/string@1", + "members": [ + { + "name": "Admin", + "value": "admin" + }, + { + "name": "Author", + "value": "author" + }, + { + "name": "Reader", + "value": "reader" + } + ] + } + }, + "models": { + "Article": { + "base": { + "model": "Post", + "namespace": "__unbound__" + }, + "fields": { + "summary": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "collection": "posts", + "fields": { + "summary": { + "field": "summary" + } + } + } + }, + "Post": { + "discriminator": { + "field": "kind" + }, + "fields": { + "authorId": { + "nullable": false, + "type": { + "codecId": "mongo/objectId@1", + "kind": "scalar" + } + }, + "content": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "createdAt": { + "nullable": false, + "type": { + "codecId": "mongo/date@1", + "kind": "scalar" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "mongo/objectId@1", + "kind": "scalar" + } + }, + "kind": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "title": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + }, + "relations": { + "author": { + "cardinality": "N:1", + "nullable": false, + "on": { + "localFields": [ + "authorId" + ], + "targetFields": [ + "id" + ] + }, + "to": { + "model": "User", + "namespace": "__unbound__" + } + } + }, + "storage": { + "collection": "posts", + "fields": { + "authorId": { + "field": "authorId" + }, + "content": { + "field": "content" + }, + "createdAt": { + "field": "createdAt" + }, + "id": { + "field": "_id" + }, + "kind": { + "field": "kind" + }, + "title": { + "field": "title" + } + } + }, + "variants": { + "article": { + "model": "Article", + "namespace": "__unbound__" + }, + "tutorial": { + "model": "Tutorial", + "namespace": "__unbound__" + } + } + }, + "Tutorial": { + "base": { + "model": "Post", + "namespace": "__unbound__" + }, + "fields": { + "difficulty": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "duration": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "collection": "posts", + "fields": { + "difficulty": { + "field": "difficulty" + }, + "duration": { + "field": "duration" + } + } + } + }, + "User": { + "fields": { + "address": { + "nullable": true, + "type": { + "kind": "valueObject", + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Address" + } + }, + "bio": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "email": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "mongo/objectId@1", + "kind": "scalar" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "role": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "valueSet": { + "entityKind": "enum", + "entityName": "UserRole", + "namespaceId": "__unbound__", + "plane": "domain" + } + } + }, + "relations": { + "posts": { + "cardinality": "1:N", + "on": { + "localFields": [ + "id" + ], + "targetFields": [ + "authorId" + ] + }, + "to": { + "model": "Post", + "namespace": "__unbound__" + } + } + }, + "storage": { + "collection": "users", + "fields": { + "address": { + "field": "address" + }, + "bio": { + "field": "bio" + }, + "email": { + "field": "email" + }, + "id": { + "field": "_id" + }, + "name": { + "field": "name" + }, + "role": { + "field": "role" + } + } + } + } + } + } + } + }, + "storage": { + "namespaces": { + "__unbound__": { + "entries": { + "collection": { + "posts": { + "indexes": [ + { + "keys": [ + { + "direction": 1, + "field": "authorId" + } + ], + "kind": "mongo-index" + }, + { + "keys": [ + { + "direction": -1, + "field": "createdAt" + }, + { + "direction": 1, + "field": "authorId" + } + ], + "kind": "mongo-index" + }, + { + "keys": [ + { + "direction": 1, + "field": "summary" + } + ], + "kind": "mongo-index", + "partialFilterExpression": { + "kind": "article" + }, + "unique": true + } + ], + "kind": "mongo-collection", + "validator": { + "jsonSchema": { + "bsonType": "object", + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "_id": { + "bsonType": "objectId" + }, + "authorId": { + "bsonType": "objectId" + }, + "content": { + "bsonType": "string" + }, + "createdAt": { + "bsonType": "date" + }, + "kind": { + "enum": [ + "article" + ] + }, + "summary": { + "bsonType": "string" + }, + "title": { + "bsonType": "string" + } + }, + "required": [ + "kind", + "summary" + ] + }, + { + "additionalProperties": false, + "properties": { + "_id": { + "bsonType": "objectId" + }, + "authorId": { + "bsonType": "objectId" + }, + "content": { + "bsonType": "string" + }, + "createdAt": { + "bsonType": "date" + }, + "difficulty": { + "bsonType": "string" + }, + "duration": { + "bsonType": "int" + }, + "kind": { + "enum": [ + "tutorial" + ] + }, + "title": { + "bsonType": "string" + } + }, + "required": [ + "difficulty", + "duration", + "kind" + ] + } + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "authorId": { + "bsonType": "objectId" + }, + "content": { + "bsonType": "string" + }, + "createdAt": { + "bsonType": "date" + }, + "kind": { + "bsonType": "string" + }, + "title": { + "bsonType": "string" + } + }, + "required": [ + "_id", + "authorId", + "content", + "createdAt", + "kind", + "title" + ] + }, + "kind": "mongo-validator", + "validationAction": "error", + "validationLevel": "strict" + } + }, + "users": { + "kind": "mongo-collection", + "validator": { + "jsonSchema": { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "address": { + "oneOf": [ + { + "bsonType": "null" + }, + { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "city": { + "bsonType": "string" + }, + "country": { + "bsonType": "string" + }, + "street": { + "bsonType": "string" + }, + "zip": { + "bsonType": [ + "null", + "string" + ] + } + }, + "required": [ + "city", + "country", + "street" + ] + } + ] + }, + "bio": { + "bsonType": [ + "null", + "string" + ] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "role": { + "bsonType": "string", + "enum": [ + "admin", + "author", + "reader" + ] + } + }, + "required": [ + "_id", + "email", + "name", + "role" + ] + }, + "kind": "mongo-validator", + "validationAction": "error", + "validationLevel": "strict" + } + } + }, + "valueSet": { + "UserRole": { + "kind": "valueSet", + "values": [ + "admin", + "author", + "reader" + ] + } + }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } + } + }, + "id": "__unbound__", + "kind": "mongo-database" + } + }, + "storageHash": "406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d" + }, + "capabilities": {}, + "extensions": {}, + "meta": {}, + "_generated": { + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", + "message": "This file is automatically generated by \"prisma contract emit\".", + "regenerate": "To regenerate, run: prisma contract emit" + } +} \ No newline at end of file diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/migration.json new file mode 100644 index 000000000000..d6a35f5bf1fc --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/migration.json @@ -0,0 +1,7 @@ +{ + "from": null, + "to": "1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3", + "providedInvariants": [], + "createdAt": "2026-06-19T15:33:14.992Z", + "migrationHash": "51cd63b5815f236613c14c46542184aab1ea662907704b3c4733f379ffd02e6f" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/migration.ts new file mode 100644 index 000000000000..cbb223b1ba03 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/migration.ts @@ -0,0 +1,215 @@ +#!/usr/bin/env -S node +import { + checkExpression, + col, + fn, + lit, + Migration, + MigrationCLI, + primaryKey, + rawSql, + unique, +} from '@prisma/orm-postgres/target/migration'; +import type { Contract as End } from '../../snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract'; +import endContract from '../../snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.json' with { + type: 'json', +}; + +export default class M extends Migration { + override readonly endContractJson = endContract; + + override get operations() { + return [ + rawSql({ + id: 'extension.vector', + label: 'Enable extension "vector"', + summary: 'Ensures the vector extension is available for pgvector operations', + operationClass: 'additive', + target: { id: 'postgres' }, + precheck: [ + { + description: 'verify extension "vector" is not already enabled', + sql: "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + expectation: { expects: 'absent', object: 'extension "vector"' }, + }, + ], + execute: [ + { + description: 'create extension "vector"', + sql: 'CREATE EXTENSION IF NOT EXISTS vector', + }, + ], + postcheck: [ + { + description: 'confirm extension "vector" is enabled', + sql: "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + expectation: { expects: 'present', object: 'extension "vector"' }, + }, + ], + }), + this.createTable({ + schema: 'public', + table: 'bug', + columns: [ + col('id', 'uuid', { notNull: true }), + col('severity', 'text', { notNull: true }), + col('stepsToRepro', 'text'), + ], + constraints: [primaryKey(['id'])], + }), + this.createTable({ + schema: 'public', + table: 'feature', + columns: [ + col('id', 'uuid', { notNull: true }), + col('priority', 'text', { notNull: true }), + col('targetRelease', 'text'), + ], + constraints: [primaryKey(['id'])], + }), + this.createTable({ + schema: 'public', + table: 'tag', + columns: [col('id', 'uuid', { notNull: true }), col('label', 'text', { notNull: true })], + constraints: [primaryKey(['id']), unique(['label'], { name: 'tag_label_key' })], + }), + this.createTable({ + schema: 'public', + table: 'user', + columns: [ + col('address', 'jsonb'), + col('createdAt', 'timestamptz', { notNull: true, default: fn('now()') }), + col('displayName', 'text', { notNull: true }), + col('email', 'text', { notNull: true }), + col('id', 'uuid', { notNull: true }), + col('kind', 'text', { notNull: true }), + ], + constraints: [ + primaryKey(['id']), + checkExpression('user_kind_check_836d43ef', "\"kind\" IN ('admin', 'user')"), + ], + }), + this.createTable({ + schema: 'public', + table: 'post', + columns: [ + col('createdAt', 'timestamptz', { notNull: true, default: fn('now()') }), + col('embedding', 'vector(1536)'), + col('id', 'uuid', { notNull: true }), + col('priority', 'int4', { notNull: true, default: lit(0) }), + col('title', 'text', { notNull: true }), + col('userId', 'uuid', { notNull: true }), + ], + constraints: [ + primaryKey(['id']), + checkExpression('post_priority_check_b236ace9', '"priority" IN (0, 1, 2)'), + ], + }), + this.createTable({ + schema: 'public', + table: 'task', + columns: [ + col('createdAt', 'timestamptz', { notNull: true, default: fn('now()') }), + col('description', 'text'), + col('id', 'uuid', { notNull: true }), + col('status', 'text', { notNull: true, default: lit('open') }), + col('title', 'text', { notNull: true }), + col('type', 'text', { notNull: true }), + col('userId', 'uuid', { notNull: true }), + ], + constraints: [primaryKey(['id'])], + }), + this.createTable({ + schema: 'public', + table: 'post_tag', + columns: [ + col('postId', 'uuid', { notNull: true }), + col('tagId', 'uuid', { notNull: true }), + ], + constraints: [primaryKey(['postId', 'tagId'])], + }), + this.addForeignKey({ + schema: 'public', + table: 'post', + foreignKey: { + name: 'post_userId_fkey', + columns: ['userId'], + references: { schema: 'public', table: 'user', columns: ['id'] }, + }, + }), + this.createIndex({ + schema: 'public', + table: 'post', + index: 'post_userId_idx_a489d58a', + columns: ['userId'], + }), + this.addForeignKey({ + schema: 'public', + table: 'task', + foreignKey: { + name: 'task_userId_fkey', + columns: ['userId'], + references: { schema: 'public', table: 'user', columns: ['id'] }, + }, + }), + this.createIndex({ + schema: 'public', + table: 'task', + index: 'task_userId_idx_a489d58a', + columns: ['userId'], + }), + this.addForeignKey({ + schema: 'public', + table: 'bug', + foreignKey: { + name: 'bug_id_fkey', + columns: ['id'], + references: { schema: 'public', table: 'task', columns: ['id'] }, + onDelete: 'cascade', + }, + }), + this.addForeignKey({ + schema: 'public', + table: 'feature', + foreignKey: { + name: 'feature_id_fkey', + columns: ['id'], + references: { schema: 'public', table: 'task', columns: ['id'] }, + onDelete: 'cascade', + }, + }), + this.createIndex({ + schema: 'public', + table: 'post_tag', + index: 'post_tag_postId_idx_a7a72715', + columns: ['postId'], + }), + this.createIndex({ + schema: 'public', + table: 'post_tag', + index: 'post_tag_tagId_idx_86854244', + columns: ['tagId'], + }), + this.addForeignKey({ + schema: 'public', + table: 'post_tag', + foreignKey: { + name: 'post_tag_postId_fkey', + columns: ['postId'], + references: { schema: 'public', table: 'post', columns: ['id'] }, + }, + }), + this.addForeignKey({ + schema: 'public', + table: 'post_tag', + foreignKey: { + name: 'post_tag_tagId_fkey', + columns: ['tagId'], + references: { schema: 'public', table: 'tag', columns: ['id'] }, + }, + }), + ]; + } +} + +MigrationCLI.run(import.meta.url, M); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/ops.json new file mode 100644 index 000000000000..3843b04f54cb --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/app/20260422T0720_initial/ops.json @@ -0,0 +1,810 @@ +[ + { + "id": "extension.vector", + "label": "Enable extension \"vector\"", + "summary": "Ensures the vector extension is available for pgvector operations", + "operationClass": "additive", + "target": { + "id": "postgres" + }, + "precheck": [ + { + "description": "verify extension \"vector\" is not already enabled", + "sql": "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + "expectation": { + "expects": "absent", + "object": "extension \"vector\"" + } + } + ], + "execute": [ + { + "description": "create extension \"vector\"", + "sql": "CREATE EXTENSION IF NOT EXISTS vector" + } + ], + "postcheck": [ + { + "description": "confirm extension \"vector\" is enabled", + "sql": "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + "expectation": { + "expects": "present", + "object": "extension \"vector\"" + } + } + ] + }, + { + "id": "table.bug", + "label": "Create table \"bug\"", + "summary": "Creates table \"bug\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "bug" + } + }, + "precheck": [ + { + "description": "ensure table \"bug\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"bug\""], + "expectation": { + "expects": "absent", + "object": "table \"bug\"" + } + } + ], + "execute": [ + { + "description": "create table \"bug\"", + "sql": "CREATE TABLE \"public\".\"bug\" (\n \"id\" uuid NOT NULL,\n \"severity\" text NOT NULL,\n \"stepsToRepro\" text,\n PRIMARY KEY (\"id\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"bug\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"bug\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"bug\"" + } + } + ] + }, + { + "id": "table.feature", + "label": "Create table \"feature\"", + "summary": "Creates table \"feature\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "feature" + } + }, + "precheck": [ + { + "description": "ensure table \"feature\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"feature\""], + "expectation": { + "expects": "absent", + "object": "table \"feature\"" + } + } + ], + "execute": [ + { + "description": "create table \"feature\"", + "sql": "CREATE TABLE \"public\".\"feature\" (\n \"id\" uuid NOT NULL,\n \"priority\" text NOT NULL,\n \"targetRelease\" text,\n PRIMARY KEY (\"id\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"feature\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"feature\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"feature\"" + } + } + ] + }, + { + "id": "table.tag", + "label": "Create table \"tag\"", + "summary": "Creates table \"tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "tag" + } + }, + "precheck": [ + { + "description": "ensure table \"tag\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"tag\""], + "expectation": { + "expects": "absent", + "object": "table \"tag\"" + } + } + ], + "execute": [ + { + "description": "create table \"tag\"", + "sql": "CREATE TABLE \"public\".\"tag\" (\n \"id\" uuid NOT NULL,\n \"label\" text NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"tag_label_key\" UNIQUE (\"label\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"tag\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"tag\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"tag\"" + } + } + ] + }, + { + "id": "table.user", + "label": "Create table \"user\"", + "summary": "Creates table \"user\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "user" + } + }, + "precheck": [ + { + "description": "ensure table \"user\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"user\""], + "expectation": { + "expects": "absent", + "object": "table \"user\"" + } + } + ], + "execute": [ + { + "description": "create table \"user\"", + "sql": "CREATE TABLE \"public\".\"user\" (\n \"address\" jsonb,\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"displayName\" text NOT NULL,\n \"email\" text NOT NULL,\n \"id\" uuid NOT NULL,\n \"kind\" text NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"user_kind_check_836d43ef\" CHECK (\"kind\" IN ('admin', 'user'))\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"user\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"user\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"user\"" + } + } + ] + }, + { + "id": "table.post", + "label": "Create table \"post\"", + "summary": "Creates table \"post\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "post" + } + }, + "precheck": [ + { + "description": "ensure table \"post\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post\""], + "expectation": { + "expects": "absent", + "object": "table \"post\"" + } + } + ], + "execute": [ + { + "description": "create table \"post\"", + "sql": "CREATE TABLE \"public\".\"post\" (\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"embedding\" vector(1536),\n \"id\" uuid NOT NULL,\n \"priority\" int4 DEFAULT 0 NOT NULL,\n \"title\" text NOT NULL,\n \"userId\" uuid NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"post_priority_check_b236ace9\" CHECK (\"priority\" IN (0, 1, 2))\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"post\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"post\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"post\"" + } + } + ] + }, + { + "id": "table.task", + "label": "Create table \"task\"", + "summary": "Creates table \"task\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "task" + } + }, + "precheck": [ + { + "description": "ensure table \"task\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"task\""], + "expectation": { + "expects": "absent", + "object": "table \"task\"" + } + } + ], + "execute": [ + { + "description": "create table \"task\"", + "sql": "CREATE TABLE \"public\".\"task\" (\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"description\" text,\n \"id\" uuid NOT NULL,\n \"status\" text DEFAULT 'open' NOT NULL,\n \"title\" text NOT NULL,\n \"type\" text NOT NULL,\n \"userId\" uuid NOT NULL,\n PRIMARY KEY (\"id\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"task\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"task\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"task\"" + } + } + ] + }, + { + "id": "table.post_tag", + "label": "Create table \"post_tag\"", + "summary": "Creates table \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure table \"post_tag\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_tag\""], + "expectation": { + "expects": "absent", + "object": "table \"post_tag\"" + } + } + ], + "execute": [ + { + "description": "create table \"post_tag\"", + "sql": "CREATE TABLE \"public\".\"post_tag\" (\n \"postId\" uuid NOT NULL,\n \"tagId\" uuid NOT NULL,\n PRIMARY KEY (\"postId\", \"tagId\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"post_tag\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"post_tag\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"post_tag\"" + } + } + ] + }, + { + "id": "foreignKey.post.post_userId_fkey", + "label": "Add foreign key \"post_userId_fkey\" on \"post\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "post_userId_fkey", + "table": "post" + } + }, + "precheck": [ + { + "description": "ensure FK \"post_userId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["post_userId_fkey", "public", "\"public\".\"post\""], + "expectation": { + "expects": "absent", + "object": "FK \"post_userId_fkey\" on \"post\"" + } + } + ], + "execute": [ + { + "description": "add FK \"post_userId_fkey\"", + "sql": "ALTER TABLE \"public\".\"post\"\nADD CONSTRAINT \"post_userId_fkey\"\nFOREIGN KEY (\"userId\")\nREFERENCES \"public\".\"user\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"post_userId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "post_userId_fkey", + "public", + "\"public\".\"post\"", + "f", + "[\"userId\"]", + "\"public\".\"user\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + }, + { + "id": "index.post.post_userId_idx_a489d58a", + "label": "Create index \"post_userId_idx_a489d58a\" on \"post\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "post_userId_idx_a489d58a", + "table": "post" + } + }, + "precheck": [ + { + "description": "ensure index \"post_userId_idx_a489d58a\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_userId_idx_a489d58a\""], + "expectation": { + "expects": "absent", + "object": "index \"post_userId_idx_a489d58a\"" + } + } + ], + "execute": [ + { + "description": "create index \"post_userId_idx_a489d58a\"", + "sql": "CREATE INDEX \"post_userId_idx_a489d58a\" ON \"public\".\"post\" (\"userId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"post_userId_idx_a489d58a\" exists on \"post\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"post_userId_idx_a489d58a\"", + "i", + "I", + "\"public\".\"post\"", + "btree", + "[\"userId\"]" + ] + } + ] + }, + { + "id": "foreignKey.task.task_userId_fkey", + "label": "Add foreign key \"task_userId_fkey\" on \"task\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "task_userId_fkey", + "table": "task" + } + }, + "precheck": [ + { + "description": "ensure FK \"task_userId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["task_userId_fkey", "public", "\"public\".\"task\""], + "expectation": { + "expects": "absent", + "object": "FK \"task_userId_fkey\" on \"task\"" + } + } + ], + "execute": [ + { + "description": "add FK \"task_userId_fkey\"", + "sql": "ALTER TABLE \"public\".\"task\"\nADD CONSTRAINT \"task_userId_fkey\"\nFOREIGN KEY (\"userId\")\nREFERENCES \"public\".\"user\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"task_userId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "task_userId_fkey", + "public", + "\"public\".\"task\"", + "f", + "[\"userId\"]", + "\"public\".\"user\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + }, + { + "id": "index.task.task_userId_idx_a489d58a", + "label": "Create index \"task_userId_idx_a489d58a\" on \"task\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "task_userId_idx_a489d58a", + "table": "task" + } + }, + "precheck": [ + { + "description": "ensure index \"task_userId_idx_a489d58a\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"task_userId_idx_a489d58a\""], + "expectation": { + "expects": "absent", + "object": "index \"task_userId_idx_a489d58a\"" + } + } + ], + "execute": [ + { + "description": "create index \"task_userId_idx_a489d58a\"", + "sql": "CREATE INDEX \"task_userId_idx_a489d58a\" ON \"public\".\"task\" (\"userId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"task_userId_idx_a489d58a\" exists on \"task\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"task_userId_idx_a489d58a\"", + "i", + "I", + "\"public\".\"task\"", + "btree", + "[\"userId\"]" + ] + } + ] + }, + { + "id": "foreignKey.bug.bug_id_fkey", + "label": "Add foreign key \"bug_id_fkey\" on \"bug\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "bug_id_fkey", + "table": "bug" + } + }, + "precheck": [ + { + "description": "ensure FK \"bug_id_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["bug_id_fkey", "public", "\"public\".\"bug\""], + "expectation": { + "expects": "absent", + "object": "FK \"bug_id_fkey\" on \"bug\"" + } + } + ], + "execute": [ + { + "description": "add FK \"bug_id_fkey\"", + "sql": "ALTER TABLE \"public\".\"bug\"\nADD CONSTRAINT \"bug_id_fkey\"\nFOREIGN KEY (\"id\")\nREFERENCES \"public\".\"task\" (\"id\")\nON DELETE CASCADE" + } + ], + "postcheck": [ + { + "description": "verify FK \"bug_id_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "bug_id_fkey", + "public", + "\"public\".\"bug\"", + "f", + "[\"id\"]", + "\"public\".\"task\"", + "[\"id\"]", + "c", + "a" + ] + } + ] + }, + { + "id": "foreignKey.feature.feature_id_fkey", + "label": "Add foreign key \"feature_id_fkey\" on \"feature\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "feature_id_fkey", + "table": "feature" + } + }, + "precheck": [ + { + "description": "ensure FK \"feature_id_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["feature_id_fkey", "public", "\"public\".\"feature\""], + "expectation": { + "expects": "absent", + "object": "FK \"feature_id_fkey\" on \"feature\"" + } + } + ], + "execute": [ + { + "description": "add FK \"feature_id_fkey\"", + "sql": "ALTER TABLE \"public\".\"feature\"\nADD CONSTRAINT \"feature_id_fkey\"\nFOREIGN KEY (\"id\")\nREFERENCES \"public\".\"task\" (\"id\")\nON DELETE CASCADE" + } + ], + "postcheck": [ + { + "description": "verify FK \"feature_id_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "feature_id_fkey", + "public", + "\"public\".\"feature\"", + "f", + "[\"id\"]", + "\"public\".\"task\"", + "[\"id\"]", + "c", + "a" + ] + } + ] + }, + { + "id": "index.post_tag.post_tag_postId_idx_a7a72715", + "label": "Create index \"post_tag_postId_idx_a7a72715\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "post_tag_postId_idx_a7a72715", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure index \"post_tag_postId_idx_a7a72715\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_tag_postId_idx_a7a72715\""], + "expectation": { + "expects": "absent", + "object": "index \"post_tag_postId_idx_a7a72715\"" + } + } + ], + "execute": [ + { + "description": "create index \"post_tag_postId_idx_a7a72715\"", + "sql": "CREATE INDEX \"post_tag_postId_idx_a7a72715\" ON \"public\".\"post_tag\" (\"postId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"post_tag_postId_idx_a7a72715\" exists on \"post_tag\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"post_tag_postId_idx_a7a72715\"", + "i", + "I", + "\"public\".\"post_tag\"", + "btree", + "[\"postId\"]" + ] + } + ] + }, + { + "id": "index.post_tag.post_tag_tagId_idx_86854244", + "label": "Create index \"post_tag_tagId_idx_86854244\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "post_tag_tagId_idx_86854244", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure index \"post_tag_tagId_idx_86854244\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_tag_tagId_idx_86854244\""], + "expectation": { + "expects": "absent", + "object": "index \"post_tag_tagId_idx_86854244\"" + } + } + ], + "execute": [ + { + "description": "create index \"post_tag_tagId_idx_86854244\"", + "sql": "CREATE INDEX \"post_tag_tagId_idx_86854244\" ON \"public\".\"post_tag\" (\"tagId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"post_tag_tagId_idx_86854244\" exists on \"post_tag\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"post_tag_tagId_idx_86854244\"", + "i", + "I", + "\"public\".\"post_tag\"", + "btree", + "[\"tagId\"]" + ] + } + ] + }, + { + "id": "foreignKey.post_tag.post_tag_postId_fkey", + "label": "Add foreign key \"post_tag_postId_fkey\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "post_tag_postId_fkey", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure FK \"post_tag_postId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["post_tag_postId_fkey", "public", "\"public\".\"post_tag\""], + "expectation": { + "expects": "absent", + "object": "FK \"post_tag_postId_fkey\" on \"post_tag\"" + } + } + ], + "execute": [ + { + "description": "add FK \"post_tag_postId_fkey\"", + "sql": "ALTER TABLE \"public\".\"post_tag\"\nADD CONSTRAINT \"post_tag_postId_fkey\"\nFOREIGN KEY (\"postId\")\nREFERENCES \"public\".\"post\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"post_tag_postId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "post_tag_postId_fkey", + "public", + "\"public\".\"post_tag\"", + "f", + "[\"postId\"]", + "\"public\".\"post\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + }, + { + "id": "foreignKey.post_tag.post_tag_tagId_fkey", + "label": "Add foreign key \"post_tag_tagId_fkey\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "post_tag_tagId_fkey", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure FK \"post_tag_tagId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["post_tag_tagId_fkey", "public", "\"public\".\"post_tag\""], + "expectation": { + "expects": "absent", + "object": "FK \"post_tag_tagId_fkey\" on \"post_tag\"" + } + } + ], + "execute": [ + { + "description": "add FK \"post_tag_tagId_fkey\"", + "sql": "ALTER TABLE \"public\".\"post_tag\"\nADD CONSTRAINT \"post_tag_tagId_fkey\"\nFOREIGN KEY (\"tagId\")\nREFERENCES \"public\".\"tag\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"post_tag_tagId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "post_tag_tagId_fkey", + "public", + "\"public\".\"post_tag\"", + "f", + "[\"tagId\"]", + "\"public\".\"tag\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + } +] diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.d.ts new file mode 100644 index 000000000000..2c5c317b884a --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.d.ts @@ -0,0 +1,1323 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as PgVectorTypes, + ParameterizedOutputTypesForTypeParams as PgVectorParameterizedOutputTypes, +} from '@prisma/orm-extension-pgvector/codec-types'; +import type { QueryOperationTypes as PgVectorQueryOperationTypes } from '@prisma/orm-extension-pgvector/operation-types'; +import type { + CodecTypes as PgTypes, + ParameterizedOutputTypesForTypeParams as PgParameterizedOutputTypes, +} from '@prisma/orm-postgres/target/codec-types'; +import type { QueryOperationTypes as PgTargetQueryOps } from '@prisma/orm-postgres/target/operation-types'; + +import type { + ColumnTypesFromStorage, + ContractWithTypeMaps, + RelationKeys, + TypeMaps as TypeMapsType, +} from '@prisma/orm-postgres/family-contract/types'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromStorage, +} from '@prisma/orm-postgres/contract/types'; + +export type StorageHash = + StorageHashBase<'1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3'>; +export type ExecutionHash = + ExecutionHashBase<'eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655'>; +export type ProfileHash = + ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; + +export type CodecTypes = PgTypes & PgVectorTypes; +export type LaneCodecTypes = CodecTypes; +export type QueryOperationTypes = PgTargetQueryOps & + PgVectorQueryOperationTypes; +export type AggregateTypes = { + readonly avg: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + }; + }; + readonly avgDecimal: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + }; + }; + readonly count: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + }; + readonly countBigInt: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + }; + readonly max: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly min: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly stddev: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + }; + }; + readonly sum: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + }; + }; + readonly sumBigInt: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + }; + }; +}; +type DefaultLiteralValue = CodecId extends keyof CodecTypes + ? Encoded extends CodecTypes[CodecId]['json'] + ? Encoded + : CodecTypes[CodecId]['json'] + : Encoded; +type ParameterizedOutputTypeFunctions = + PgParameterizedOutputTypes | PgVectorParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['public']['Address']; +export type AddressInput = ValueObjectInputTypes['public']['Address']; +type FieldTypeOverrides = { + readonly public: { + readonly models: { + readonly Post: { + readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 }; + }; + readonly User: { + readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; +type ColumnTypeOverrides = { + readonly public: { + readonly post: { readonly priority: { readonly output: 0 | 1 | 2; readonly input: 0 | 1 | 2 } }; + readonly user: { + readonly kind: { readonly output: 'admin' | 'user'; readonly input: 'admin' | 'user' }; + }; + }; +}; +export type StorageColumnTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'output' +>; +export type StorageColumnInputTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'input' +>; + +export namespace Models { + export type public_Bug = { + createdAt: FieldOutputTypes['public']['Task']['createdAt']; + description: FieldOutputTypes['public']['Task']['description']; + id: FieldOutputTypes['public']['Task']['id']; + status: FieldOutputTypes['public']['Task']['status']; + title: FieldOutputTypes['public']['Task']['title']; + type: 'bug'; + userId: FieldOutputTypes['public']['Task']['userId']; + severity: FieldOutputTypes['public']['Bug']['severity']; + stepsToRepro: FieldOutputTypes['public']['Bug']['stepsToRepro']; + user: public_User; + readonly [RelationKeys]?: 'user'; + }; + export type public_Feature = { + createdAt: FieldOutputTypes['public']['Task']['createdAt']; + description: FieldOutputTypes['public']['Task']['description']; + id: FieldOutputTypes['public']['Task']['id']; + status: FieldOutputTypes['public']['Task']['status']; + title: FieldOutputTypes['public']['Task']['title']; + type: 'feature'; + userId: FieldOutputTypes['public']['Task']['userId']; + priority: FieldOutputTypes['public']['Feature']['priority']; + targetRelease: FieldOutputTypes['public']['Feature']['targetRelease']; + user: public_User; + readonly [RelationKeys]?: 'user'; + }; + export type public_Post = { + createdAt: FieldOutputTypes['public']['Post']['createdAt']; + embedding: FieldOutputTypes['public']['Post']['embedding']; + id: FieldOutputTypes['public']['Post']['id']; + priority: FieldOutputTypes['public']['Post']['priority']; + title: FieldOutputTypes['public']['Post']['title']; + userId: FieldOutputTypes['public']['Post']['userId']; + tags: public_Tag[]; + user: public_User; + readonly [RelationKeys]?: 'tags' | 'user'; + }; + export type public_PostTag = { + postId: FieldOutputTypes['public']['PostTag']['postId']; + tagId: FieldOutputTypes['public']['PostTag']['tagId']; + post: public_Post; + tag: public_Tag; + readonly [RelationKeys]?: 'post' | 'tag'; + }; + export type public_Tag = { + id: FieldOutputTypes['public']['Tag']['id']; + label: FieldOutputTypes['public']['Tag']['label']; + posts: public_Post[]; + readonly [RelationKeys]?: 'posts'; + }; + export type public_Task = { + createdAt: FieldOutputTypes['public']['Task']['createdAt']; + description: FieldOutputTypes['public']['Task']['description']; + id: FieldOutputTypes['public']['Task']['id']; + status: FieldOutputTypes['public']['Task']['status']; + title: FieldOutputTypes['public']['Task']['title']; + type: 'bug' | 'feature'; + userId: FieldOutputTypes['public']['Task']['userId']; + user: public_User; + readonly [RelationKeys]?: 'user'; + }; + export type public_User = { + address: FieldOutputTypes['public']['User']['address']; + createdAt: FieldOutputTypes['public']['User']['createdAt']; + displayName: FieldOutputTypes['public']['User']['displayName']; + email: FieldOutputTypes['public']['User']['email']; + id: FieldOutputTypes['public']['User']['id']; + kind: FieldOutputTypes['public']['User']['kind']; + posts: public_Post[]; + tasks: public_AnyTask[]; + readonly [RelationKeys]?: 'posts' | 'tasks'; + }; + export type public_AnyTask = public_Bug | public_Feature; +} + +export declare const models: { + public: { + Bug: Models.public_Bug; + Feature: Models.public_Feature; + Post: Models.public_Post; + PostTag: Models.public_PostTag; + Tag: Models.public_Tag; + Task: Models.public_Task; + User: Models.public_User; + AnyTask: Models.public_AnyTask; + }; +}; + +export type TypeMaps = TypeMapsType< + CodecTypes, + QueryOperationTypes, + FieldOutputTypes, + FieldInputTypes, + StorageColumnTypes, + StorageColumnInputTypes, + AggregateTypes +>; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly public: { + readonly id: 'public'; + readonly kind: 'postgres-schema'; + readonly entries: { + readonly table: { + readonly bug: { + columns: { + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly severity: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly stepsToRepro: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'bug'; + readonly columns: readonly ['id']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly feature: { + columns: { + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly priority: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly targetRelease: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'feature'; + readonly columns: readonly ['id']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly post: { + columns: { + readonly createdAt: { + readonly dataType: 'pg/timestamptz'; + readonly codecId: 'pg/timestamptz-temporal@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly many: false; + }; + readonly embedding: { + readonly dataType: 'pgvector/vector'; + readonly codecId: 'pg/vector@1'; + readonly nullable: true; + readonly typeParams: { readonly length: 1536 }; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly priority: { + readonly dataType: 'pg/int4'; + readonly codecId: 'pg/int4@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/int4@1', 0>; + }; + readonly many: false; + }; + readonly title: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly userId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly name: 'post_userId_idx_a489d58a'; + readonly prefix: 'post_userId_idx'; + readonly columns: readonly ['userId']; + readonly unique: false; + }, + ]; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['userId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly post_tag: { + columns: { + readonly postId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly tagId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['postId', 'tagId'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly name: 'post_tag_postId_idx_a7a72715'; + readonly prefix: 'post_tag_postId_idx'; + readonly columns: readonly ['postId']; + readonly unique: false; + }, + { + readonly name: 'post_tag_tagId_idx_86854244'; + readonly prefix: 'post_tag_tagId_idx'; + readonly columns: readonly ['tagId']; + readonly unique: false; + }, + ]; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post_tag'; + readonly columns: readonly ['postId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post'; + readonly columns: readonly ['id']; + }; + }, + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'post_tag'; + readonly columns: readonly ['tagId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'tag'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly tag: { + columns: { + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly label: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly [{ readonly columns: readonly ['label'] }]; + indexes: readonly []; + foreignKeys: readonly []; + }; + readonly task: { + columns: { + readonly createdAt: { + readonly dataType: 'pg/timestamptz'; + readonly codecId: 'pg/timestamptz-temporal@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly many: false; + }; + readonly description: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: true; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly status: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly default: { + readonly kind: 'literal'; + readonly value: DefaultLiteralValue<'pg/text@1', 'open'>; + }; + readonly many: false; + }; + readonly title: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly type: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly userId: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly [ + { + readonly name: 'task_userId_idx_a489d58a'; + readonly prefix: 'task_userId_idx'; + readonly columns: readonly ['userId']; + readonly unique: false; + }, + ]; + foreignKeys: readonly [ + { + readonly source: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'task'; + readonly columns: readonly ['userId']; + }; + readonly target: { + readonly namespaceId: 'public' & NamespaceId; + readonly tableName: 'user'; + readonly columns: readonly ['id']; + }; + }, + ]; + }; + readonly user: { + columns: { + readonly address: { + readonly dataType: 'pg/jsonb'; + readonly codecId: 'pg/jsonb@1'; + readonly nullable: true; + readonly many: false; + }; + readonly createdAt: { + readonly dataType: 'pg/timestamptz'; + readonly codecId: 'pg/timestamptz-temporal@1'; + readonly nullable: false; + readonly default: { readonly kind: 'function'; readonly expression: 'now()' }; + readonly many: false; + }; + readonly displayName: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly email: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + readonly id: { + readonly dataType: 'pg/uuid'; + readonly codecId: 'pg/uuid@1'; + readonly nullable: false; + readonly many: false; + }; + readonly kind: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; + readonly many: false; + }; + }; + primaryKey: { readonly columns: readonly ['id'] }; + uniques: readonly []; + indexes: readonly []; + foreignKeys: readonly []; + }; + }; + readonly valueSet: { + readonly Priority: { readonly kind: 'valueSet'; readonly values: readonly [0, 1, 2] }; + readonly user_type: { + readonly kind: 'valueSet'; + readonly values: readonly ['admin', 'user']; + }; + }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'postgres'; + readonly targetFamily: 'sql'; + readonly roots: { + readonly post: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly post_tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'PostTag' }; + readonly tag: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; + readonly task: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + readonly user: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly public: { + readonly models: { + readonly Bug: { + readonly fields: { + readonly severity: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly stepsToRepro: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'bug'; + readonly namespaceId: 'public'; + readonly fields: { + readonly severity: { readonly column: 'severity' }; + readonly stepsToRepro: { readonly column: 'stepsToRepro' }; + }; + }; + readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + }; + readonly Feature: { + readonly fields: { + readonly priority: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly targetRelease: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly table: 'feature'; + readonly namespaceId: 'public'; + readonly fields: { + readonly priority: { readonly column: 'priority' }; + readonly targetRelease: { readonly column: 'targetRelease' }; + }; + }; + readonly base: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + }; + readonly Post: { + readonly fields: { + readonly createdAt: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/timestamptz-temporal@1'; + }; + }; + readonly embedding: { + readonly nullable: true; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/vector@1'; + readonly typeParams: { readonly length: 1536 }; + }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly priority: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int4@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + }; + readonly relations: { + readonly tags: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; + readonly cardinality: 'N:M'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['postId']; + }; + readonly through: { + readonly table: 'post_tag'; + readonly namespaceId: 'public'; + readonly parentColumns: readonly ['postId']; + readonly childColumns: readonly ['tagId']; + readonly targetColumns: readonly ['id']; + }; + }; + readonly user: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['userId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'post'; + readonly namespaceId: 'public'; + readonly fields: { + readonly createdAt: { readonly column: 'createdAt' }; + readonly embedding: { readonly column: 'embedding' }; + readonly id: { readonly column: 'id' }; + readonly priority: { readonly column: 'priority' }; + readonly title: { readonly column: 'title' }; + readonly userId: { readonly column: 'userId' }; + }; + }; + }; + readonly PostTag: { + readonly fields: { + readonly postId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly tagId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + }; + readonly relations: { + readonly post: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['postId']; + readonly targetFields: readonly ['id']; + }; + }; + readonly tag: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Tag' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['tagId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'post_tag'; + readonly namespaceId: 'public'; + readonly fields: { + readonly postId: { readonly column: 'postId' }; + readonly tagId: { readonly column: 'tagId' }; + }; + }; + }; + readonly Tag: { + readonly fields: { + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly label: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly cardinality: 'N:M'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['tagId']; + }; + readonly through: { + readonly table: 'post_tag'; + readonly namespaceId: 'public'; + readonly parentColumns: readonly ['tagId']; + readonly childColumns: readonly ['postId']; + readonly targetColumns: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'tag'; + readonly namespaceId: 'public'; + readonly fields: { + readonly id: { readonly column: 'id' }; + readonly label: { readonly column: 'label' }; + }; + }; + }; + readonly Task: { + readonly fields: { + readonly createdAt: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/timestamptz-temporal@1'; + }; + }; + readonly description: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly status: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly type: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly userId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + }; + readonly relations: { + readonly user: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['userId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly table: 'task'; + readonly namespaceId: 'public'; + readonly fields: { + readonly createdAt: { readonly column: 'createdAt' }; + readonly description: { readonly column: 'description' }; + readonly id: { readonly column: 'id' }; + readonly status: { readonly column: 'status' }; + readonly title: { readonly column: 'title' }; + readonly type: { readonly column: 'type' }; + readonly userId: { readonly column: 'userId' }; + }; + }; + readonly discriminator: { readonly field: 'type' }; + readonly variants: { + readonly bug: { readonly namespace: 'public' & NamespaceId; readonly model: 'Bug' }; + readonly feature: { + readonly namespace: 'public' & NamespaceId; + readonly model: 'Feature'; + }; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { + readonly kind: 'scalar'; + readonly codecId: 'pg/timestamptz-temporal@1'; + }; + }; + readonly displayName: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/uuid@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Post' }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['userId']; + }; + }; + readonly tasks: { + readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'Task' }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['userId']; + }; + }; + }; + readonly storage: { + readonly table: 'user'; + readonly namespaceId: 'public'; + readonly fields: { + readonly address: { readonly column: 'address' }; + readonly createdAt: { readonly column: 'createdAt' }; + readonly displayName: { readonly column: 'displayName' }; + readonly email: { readonly column: 'email' }; + readonly id: { readonly column: 'id' }; + readonly kind: { readonly column: 'kind' }; + }; + }; + }; + }; + readonly enum: { + readonly Priority: { + readonly codecId: 'pg/int4@1'; + readonly members: readonly [ + { readonly name: 'Low'; readonly value: 0 }, + { readonly name: 'High'; readonly value: 1 }, + { readonly name: 'Urgent'; readonly value: 2 }, + ]; + }; + readonly user_type: { + readonly codecId: 'pg/text@1'; + readonly members: readonly [ + { readonly name: 'admin'; readonly value: 'admin' }, + { readonly name: 'user'; readonly value: 'user' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly Priority: readonly [ + { readonly name: 'Low'; readonly value: 0 }, + { readonly name: 'High'; readonly value: 1 }, + { readonly name: 'Urgent'; readonly value: 2 }, + ]; + readonly user_type: readonly [ + { readonly name: 'admin'; readonly value: 'admin' }, + { readonly name: 'user'; readonly value: 'user' }, + ]; + }; + }; + }; + }; + readonly capabilities: { + readonly postgres: { + readonly distinctOn: true; + readonly forKeyShare: true; + readonly forNoKeyUpdate: true; + readonly jsonAgg: true; + readonly lateral: true; + readonly limit: true; + readonly orderBy: true; + readonly 'pgvector.cosine': true; + readonly returning: true; + }; + readonly sql: { + readonly checkConstraint: true; + readonly defaultInInsert: true; + readonly enums: true; + readonly forShare: true; + readonly forUpdate: true; + readonly insertOnConflictSkip: true; + readonly insertOnConflictWithoutTarget: true; + readonly lateral: true; + readonly lockNowait: true; + readonly lockOf: true; + readonly lockSkipLocked: true; + readonly returning: true; + readonly scalarList: true; + }; + }; + readonly extensions: { + readonly 'demo/engagement-stats': { + readonly familyId: 'sql'; + readonly id: 'demo/engagement-stats'; + readonly kind: 'extension'; + readonly targetId: 'postgres'; + readonly types: { + readonly aggregateDescriptors: readonly [ + { + readonly input: { readonly kind: 'trait'; readonly trait: 'numeric' }; + readonly lower: unknown; + readonly nullable: true; + readonly operation: 'stddev'; + readonly output: { readonly codecId: 'pg/numeric@1'; readonly kind: 'codec' }; + }, + ]; + }; + readonly version: '0.0.1'; + }; + readonly pgvector: { + readonly capabilities: { readonly postgres: { readonly 'pgvector.cosine': true } }; + readonly familyId: 'sql'; + readonly id: 'pgvector'; + readonly kind: 'extension'; + readonly targetId: 'postgres'; + readonly types: { + readonly codecTypes: { + readonly import: { + readonly alias: 'PgVectorTypes'; + readonly named: 'CodecTypes'; + readonly package: '@internal/extension-pgvector/codec-types'; + }; + readonly parameterizedOutputTypes: { + readonly alias: 'PgVectorParameterizedOutputTypes'; + readonly named: 'ParameterizedOutputTypesForTypeParams'; + readonly package: '@internal/extension-pgvector/codec-types'; + }; + }; + readonly operationTypes: { + readonly import: { + readonly alias: 'PgVectorOperationTypes'; + readonly named: 'OperationTypes'; + readonly package: '@internal/extension-pgvector/operation-types'; + }; + }; + readonly queryOperationTypes: { + readonly import: { + readonly alias: 'PgVectorQueryOperationTypes'; + readonly named: 'QueryOperationTypes'; + readonly package: '@internal/extension-pgvector/operation-types'; + }; + }; + }; + readonly version: '0.0.1'; + }; + }; + readonly execution: { + readonly executionHash: ExecutionHash; + readonly mutations: { + readonly defaults: readonly [ + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'post'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'tag'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'task'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + { + readonly onCreate: { readonly id: 'uuidv4'; readonly kind: 'generator' }; + readonly ref: { + readonly entry: 'user'; + readonly field: 'id'; + readonly namespace: 'public'; + }; + }, + ]; + }; + }; + readonly meta: {}; + readonly profileHash: ProfileHash; +}; + +export type Contract = ContractWithTypeMaps; + +export type Namespaces = Contract['storage']['namespaces']; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.json new file mode 100644 index 000000000000..b6a876b59a88 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/after/sql/migrations/snapshots/1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3/contract.json @@ -0,0 +1 @@ +{"_generated":{"message":"This file is automatically generated by \"prisma contract emit\".","regenerate":"To regenerate, run: prisma contract emit","warning":"⚠️ GENERATED FILE - DO NOT EDIT"},"capabilities":{"postgres":{"distinctOn":true,"forKeyShare":true,"forNoKeyUpdate":true,"jsonAgg":true,"lateral":true,"limit":true,"orderBy":true,"pgvector.cosine":true,"returning":true},"sql":{"checkConstraint":true,"defaultInInsert":true,"enums":true,"forShare":true,"forUpdate":true,"insertOnConflictSkip":true,"insertOnConflictWithoutTarget":true,"lateral":true,"lockNowait":true,"lockOf":true,"lockSkipLocked":true,"returning":true,"scalarList":true}},"domain":{"namespaces":{"public":{"enum":{"Priority":{"codecId":"pg/int4@1","members":[{"name":"Low","value":0},{"name":"High","value":1},{"name":"Urgent","value":2}]},"user_type":{"codecId":"pg/text@1","members":[{"name":"admin","value":"admin"},{"name":"user","value":"user"}]}},"models":{"Bug":{"base":{"model":"Task","namespace":"public"},"fields":{"severity":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"stepsToRepro":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"severity":{"column":"severity"},"stepsToRepro":{"column":"stepsToRepro"}},"namespaceId":"public","table":"bug"}},"Feature":{"base":{"model":"Task","namespace":"public"},"fields":{"priority":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"targetRelease":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{},"storage":{"fields":{"priority":{"column":"priority"},"targetRelease":{"column":"targetRelease"}},"namespaceId":"public","table":"feature"}},"Post":{"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"embedding":{"nullable":true,"type":{"codecId":"pg/vector@1","kind":"scalar","typeParams":{"length":1536}}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"priority":{"nullable":false,"type":{"codecId":"pg/int4@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"Priority","namespaceId":"public","plane":"domain"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"tags":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["postId"]},"through":{"childColumns":["tagId"],"namespaceId":"public","parentColumns":["postId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Tag","namespace":"public"}},"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"embedding":{"column":"embedding"},"id":{"column":"id"},"priority":{"column":"priority"},"title":{"column":"title"},"userId":{"column":"userId"}},"namespaceId":"public","table":"post"}},"PostTag":{"fields":{"postId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"tagId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"post":{"cardinality":"N:1","nullable":false,"on":{"localFields":["postId"],"targetFields":["id"]},"to":{"model":"Post","namespace":"public"}},"tag":{"cardinality":"N:1","nullable":false,"on":{"localFields":["tagId"],"targetFields":["id"]},"to":{"model":"Tag","namespace":"public"}}},"storage":{"fields":{"postId":{"column":"postId"},"tagId":{"column":"tagId"}},"namespaceId":"public","table":"post_tag"}},"Tag":{"fields":{"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"label":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}}},"relations":{"posts":{"cardinality":"N:M","on":{"localFields":["id"],"targetFields":["tagId"]},"through":{"childColumns":["postId"],"namespaceId":"public","parentColumns":["tagId"],"table":"post_tag","targetColumns":["id"]},"to":{"model":"Post","namespace":"public"}}},"storage":{"fields":{"id":{"column":"id"},"label":{"column":"label"}},"namespaceId":"public","table":"tag"}},"Task":{"discriminator":{"field":"type"},"fields":{"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"description":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"status":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"title":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"type":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"userId":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}}},"relations":{"user":{"cardinality":"N:1","nullable":false,"on":{"localFields":["userId"],"targetFields":["id"]},"to":{"model":"User","namespace":"public"}}},"storage":{"fields":{"createdAt":{"column":"createdAt"},"description":{"column":"description"},"id":{"column":"id"},"status":{"column":"status"},"title":{"column":"title"},"type":{"column":"type"},"userId":{"column":"userId"}},"namespaceId":"public","table":"task"},"variants":{"bug":{"model":"Bug","namespace":"public"},"feature":{"model":"Feature","namespace":"public"}}},"User":{"fields":{"address":{"nullable":true,"type":{"entityKind":"valueObject","entityName":"Address","kind":"valueObject","namespaceId":"public","plane":"storage"}},"createdAt":{"nullable":false,"type":{"codecId":"pg/timestamptz-temporal@1","kind":"scalar"}},"displayName":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"email":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"id":{"nullable":false,"type":{"codecId":"pg/uuid@1","kind":"scalar"}},"kind":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"},"valueSet":{"entityKind":"enum","entityName":"user_type","namespaceId":"public","plane":"domain"}}},"relations":{"posts":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Post","namespace":"public"}},"tasks":{"cardinality":"1:N","on":{"localFields":["id"],"targetFields":["userId"]},"to":{"model":"Task","namespace":"public"}}},"storage":{"fields":{"address":{"column":"address"},"createdAt":{"column":"createdAt"},"displayName":{"column":"displayName"},"email":{"column":"email"},"id":{"column":"id"},"kind":{"column":"kind"}},"namespaceId":"public","table":"user"}}}}}},"execution":{"executionHash":"eb9bffd4c01945792e83de10d571b20df42f5bf40405ec8c642c6592f7dc4655","mutations":{"defaults":[{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"post","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"tag","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"task","field":"id","namespace":"public"}},{"onCreate":{"id":"uuidv4","kind":"generator"},"ref":{"entry":"user","field":"id","namespace":"public"}}]}},"extensions":{"demo/engagement-stats":{"familyId":"sql","id":"demo/engagement-stats","kind":"extension","targetId":"postgres","types":{"aggregateDescriptors":[{"input":{"kind":"trait","trait":"numeric"},"nullable":true,"operation":"stddev","output":{"codecId":"pg/numeric@1","kind":"codec"}}]},"version":"0.0.1"},"pgvector":{"capabilities":{"postgres":{"pgvector.cosine":true}},"familyId":"sql","id":"pgvector","kind":"extension","targetId":"postgres","types":{"codecTypes":{"import":{"alias":"PgVectorTypes","named":"CodecTypes","package":"@internal/extension-pgvector/codec-types"},"parameterizedOutputTypes":{"alias":"PgVectorParameterizedOutputTypes","named":"ParameterizedOutputTypesForTypeParams","package":"@internal/extension-pgvector/codec-types"}},"operationTypes":{"import":{"alias":"PgVectorOperationTypes","named":"OperationTypes","package":"@internal/extension-pgvector/operation-types"}},"queryOperationTypes":{"import":{"alias":"PgVectorQueryOperationTypes","named":"QueryOperationTypes","package":"@internal/extension-pgvector/operation-types"}}},"version":"0.0.1"}},"meta":{},"profileHash":"3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2","roots":{"post":{"model":"Post","namespace":"public"},"post_tag":{"model":"PostTag","namespace":"public"},"tag":{"model":"Tag","namespace":"public"},"task":{"model":"Task","namespace":"public"},"user":{"model":"User","namespace":"public"}},"schemaVersion":"1","storage":{"namespaces":{"public":{"entries":{"table":{"bug":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"severity":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"stepsToRepro":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"bug"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"feature":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"targetRelease":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true}},"foreignKeys":[{"index":{"primaryKey":true},"onDelete":"cascade","source":{"columns":["id"],"namespaceId":"public","tableName":"feature"},"target":{"columns":["id"],"namespaceId":"public","tableName":"task"}}],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]},"post":{"checks":[{"expression":"\"priority\" IN (0, 1, 2)","name":"post_priority_check_b236ace9","prefix":"post_priority_check"}],"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"embedding":{"codecId":"pg/vector@1","dataType":"pgvector/vector","nullable":true,"typeParams":{"length":1536}},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"priority":{"codecId":"pg/int4@1","dataType":"pg/int4","default":{"kind":"literal","value":0},"nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Priority","namespaceId":"public","plane":"storage"}},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"post"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"post_userId_idx_a489d58a","prefix":"post_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"post_tag":{"columns":{"postId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"tagId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"post_tag_postId_idx_a7a72715"},"source":{"columns":["postId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"post"}},{"index":{"name":"post_tag_tagId_idx_86854244"},"source":{"columns":["tagId"],"namespaceId":"public","tableName":"post_tag"},"target":{"columns":["id"],"namespaceId":"public","tableName":"tag"}}],"indexes":[{"columns":["postId"],"name":"post_tag_postId_idx_a7a72715","prefix":"post_tag_postId_idx","unique":false},{"columns":["tagId"],"name":"post_tag_tagId_idx_86854244","prefix":"post_tag_tagId_idx","unique":false}],"primaryKey":{"columns":["postId","tagId"]},"uniques":[]},"tag":{"columns":{"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"label":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[{"columns":["label"]}]},"task":{"columns":{"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"description":{"codecId":"pg/text@1","dataType":"pg/text","nullable":true},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"status":{"codecId":"pg/text@1","dataType":"pg/text","default":{"kind":"literal","value":"open"},"nullable":false},"title":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"type":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"Task.type","namespaceId":"public","plane":"storage"}},"userId":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false}},"foreignKeys":[{"index":{"name":"task_userId_idx_a489d58a"},"source":{"columns":["userId"],"namespaceId":"public","tableName":"task"},"target":{"columns":["id"],"namespaceId":"public","tableName":"user"}}],"indexes":[{"columns":["userId"],"name":"task_userId_idx_a489d58a","prefix":"task_userId_idx","unique":false}],"primaryKey":{"columns":["id"]},"uniques":[]},"user":{"checks":[{"expression":"\"kind\" IN ('admin', 'user')","name":"user_kind_check_836d43ef","prefix":"user_kind_check"}],"columns":{"address":{"codecId":"pg/jsonb@1","dataType":"pg/jsonb","nullable":true},"createdAt":{"codecId":"pg/timestamptz-temporal@1","dataType":"pg/timestamptz","default":{"expression":"now()","kind":"function"},"nullable":false},"displayName":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"email":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false},"id":{"codecId":"pg/uuid@1","dataType":"pg/uuid","nullable":false},"kind":{"codecId":"pg/text@1","dataType":"pg/text","nullable":false,"valueSet":{"entityKind":"valueSet","entityName":"user_type","namespaceId":"public","plane":"storage"}}},"foreignKeys":[],"indexes":[],"primaryKey":{"columns":["id"]},"uniques":[]}},"valueObject":{"Address":{"fields":{"city":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"country":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"street":{"nullable":false,"type":{"codecId":"pg/text@1","kind":"scalar"}},"zip":{"nullable":true,"type":{"codecId":"pg/text@1","kind":"scalar"}}}}},"valueSet":{"Priority":{"kind":"valueSet","values":[0,1,2]},"Task.type":{"kind":"valueSet","values":["bug","feature"]},"user_type":{"kind":"valueSet","values":["admin","user"]}}},"id":"public","kind":"postgres-schema"}},"storageHash":"1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3"},"target":"postgres","targetFamily":"sql"} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/migration.json new file mode 100644 index 000000000000..3cb8739196ad --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/migration.json @@ -0,0 +1,7 @@ +{ + "from": null, + "to": "da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd", + "providedInvariants": [], + "createdAt": "2026-04-09T10:30:13.948Z", + "migrationHash": "3a28bd528b39d9b9c9a52082485a3274a8de15257cf9296776c290cc7b4e4fb5" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/migration.ts new file mode 100644 index 000000000000..ee3167d3c896 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/migration.ts @@ -0,0 +1,16 @@ +import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as End } from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract'; +import endContract from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json' with { + type: 'json', +}; + +class InitialMigration extends Migration { + override readonly endContractJson = endContract; + + override get operations() { + return [createIndex('users', [{ field: 'email', direction: 1 }], { unique: true })]; + } +} + +export default InitialMigration; +MigrationCLI.run(import.meta.url, InitialMigration); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/ops.json new file mode 100644 index 000000000000..533b620b26a8 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260409T1030_migration/ops.json @@ -0,0 +1,101 @@ +[ + { + "id": "index.users.create(email:1)", + "label": "Create index on users (email:1)", + "operationClass": "additive", + "precheck": [ + { + "description": "index does not already exist on users", + "source": { + "kind": "listIndexes", + "collection": "users" + }, + "filter": { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "email": 1 + } + }, + "expect": "notExists", + "object": "an index on { email: 1 } in \"users\"" + } + ], + "execute": [ + { + "description": "create index on users", + "command": { + "kind": "createIndex", + "collection": "users", + "keys": [ + { + "field": "email", + "direction": 1 + } + ], + "unique": true, + "name": "email_1" + } + } + ], + "postcheck": [ + { + "description": "index exists on users", + "source": { + "kind": "listIndexes", + "collection": "users" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "email": 1 + } + }, + { + "kind": "field", + "field": "unique", + "op": "$eq", + "value": true + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "sparse", + "op": "$eq", + "value": true + } + }, + { + "kind": "exists", + "field": "expireAfterSeconds", + "exists": false + }, + { + "kind": "exists", + "field": "partialFilterExpression", + "exists": false + }, + { + "kind": "exists", + "field": "wildcardProjection", + "exists": false + }, + { + "kind": "exists", + "field": "collation", + "exists": false + } + ] + }, + "expect": "exists" + } + ] + } +] diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.json new file mode 100644 index 000000000000..dfb26991f67f --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.json @@ -0,0 +1,7 @@ +{ + "from": "da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd", + "to": "62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590", + "providedInvariants": [], + "createdAt": "2026-06-26T16:05:45.955Z", + "migrationHash": "671d9b676330b6af88db75c5566f1fe1a7d7d4d0af4ca282828f093b8dd295c0" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.ts new file mode 100755 index 000000000000..9471c658b0b7 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/migration.ts @@ -0,0 +1,65 @@ +#!/usr/bin/env -S node +import { collMod, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as End } from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract'; +import endContract from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json' with { + type: 'json', +}; +import type { Contract as Start } from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract'; +import startContract from '../../snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json' with { + type: 'json', +}; + +class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; + + override get operations() { + return [ + collMod( + 'users', + { + validator: { + $jsonSchema: { + additionalProperties: false, + bsonType: 'object', + properties: { + _id: { bsonType: 'objectId' }, + address: { + oneOf: [ + { bsonType: 'null' }, + { + additionalProperties: false, + bsonType: 'object', + properties: { + city: { bsonType: 'string' }, + country: { bsonType: 'string' }, + street: { bsonType: 'string' }, + zip: { bsonType: ['null', 'string'] }, + }, + required: ['city', 'country', 'street'], + }, + ], + }, + bio: { bsonType: ['null', 'string'] }, + email: { bsonType: 'string' }, + name: { bsonType: 'string' }, + role: { bsonType: 'string', enum: ['admin', 'author', 'reader'] }, + }, + required: ['_id', 'email', 'name', 'role'], + }, + }, + validationLevel: 'strict', + validationAction: 'error', + }, + { + id: 'validator.users.update', + label: 'Update validator on users', + operationClass: 'destructive', + }, + ), + ]; + } +} + +export default M; +MigrationCLI.run(import.meta.url, M); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/ops.json new file mode 100644 index 000000000000..11605cf4708e --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1605_add_user_role_enum/ops.json @@ -0,0 +1,171 @@ +[ + { + "id": "validator.users.update", + "label": "Update validator on users", + "operationClass": "destructive", + "precheck": [ + { + "description": "collection users exists", + "source": { + "kind": "listCollections" + }, + "filter": { + "kind": "field", + "field": "name", + "op": "$eq", + "value": "users" + }, + "expect": "exists", + "object": "collection \"users\"" + } + ], + "execute": [ + { + "description": "modify users", + "command": { + "kind": "collMod", + "collection": "users", + "validator": { + "$jsonSchema": { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "address": { + "oneOf": [ + { + "bsonType": "null" + }, + { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "city": { + "bsonType": "string" + }, + "country": { + "bsonType": "string" + }, + "street": { + "bsonType": "string" + }, + "zip": { + "bsonType": ["null", "string"] + } + }, + "required": ["city", "country", "street"] + } + ] + }, + "bio": { + "bsonType": ["null", "string"] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "role": { + "bsonType": "string", + "enum": ["admin", "author", "reader"] + } + }, + "required": ["_id", "email", "name", "role"] + } + }, + "validationLevel": "strict", + "validationAction": "error" + } + } + ], + "postcheck": [ + { + "description": "validator applied on users", + "source": { + "kind": "listCollections" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "name", + "op": "$eq", + "value": "users" + }, + { + "kind": "field", + "field": "options.validationLevel", + "op": "$eq", + "value": "strict" + }, + { + "kind": "field", + "field": "options.validationAction", + "op": "$eq", + "value": "error" + }, + { + "kind": "field", + "field": "options.validator.$jsonSchema", + "op": "$eq", + "value": { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "address": { + "oneOf": [ + { + "bsonType": "null" + }, + { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "city": { + "bsonType": "string" + }, + "country": { + "bsonType": "string" + }, + "street": { + "bsonType": "string" + }, + "zip": { + "bsonType": ["null", "string"] + } + }, + "required": ["city", "country", "street"] + } + ] + }, + "bio": { + "bsonType": ["null", "string"] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "role": { + "bsonType": "string", + "enum": ["admin", "author", "reader"] + } + }, + "required": ["_id", "email", "name", "role"] + } + } + ] + }, + "expect": "exists" + } + ] + } +] diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.json new file mode 100644 index 000000000000..6d6ea9a9ebad --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.json @@ -0,0 +1,7 @@ +{ + "from": "62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590", + "to": "d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0", + "providedInvariants": [], + "createdAt": "2026-06-26T19:16:47.657Z", + "migrationHash": "a435f5206064150010733fe0d93690358b92cb892cfdfbd80c1b4aade84c16ef" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.ts new file mode 100755 index 000000000000..146f9deaca22 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/migration.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env -S node +import { createIndex, Migration, MigrationCLI } from '@prisma/orm-mongo/target/migration'; +import type { Contract as Start } from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract'; +import startContract from '../../snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json' with { + type: 'json', +}; +import type { Contract as End } from '../../snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract'; +import endContract from '../../snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.json' with { + type: 'json', +}; + +class M extends Migration { + override readonly startContractJson = startContract; + override readonly endContractJson = endContract; + + override get operations() { + return [ + createIndex('posts', [{ direction: 1, field: 'authorId' }], {}), + createIndex( + 'posts', + [ + { direction: -1, field: 'createdAt' }, + { direction: 1, field: 'authorId' }, + ], + {}, + ), + ]; + } +} + +export default M; +MigrationCLI.run(import.meta.url, M); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/ops.json new file mode 100644 index 000000000000..d9b60da89298 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/app/20260626T1916_add_posts_indexes/ops.json @@ -0,0 +1,210 @@ +[ + { + "id": "index.posts.create(authorId:1)", + "label": "Create index on posts (authorId:1)", + "operationClass": "additive", + "precheck": [ + { + "description": "index does not already exist on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "authorId": 1 + } + }, + "expect": "notExists", + "object": "an index on { authorId: 1 } in \"posts\"" + } + ], + "execute": [ + { + "description": "create index on posts", + "command": { + "kind": "createIndex", + "collection": "posts", + "keys": [ + { + "direction": 1, + "field": "authorId" + } + ], + "name": "authorId_1" + } + } + ], + "postcheck": [ + { + "description": "index exists on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "authorId": 1 + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "unique", + "op": "$eq", + "value": true + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "sparse", + "op": "$eq", + "value": true + } + }, + { + "kind": "exists", + "field": "expireAfterSeconds", + "exists": false + }, + { + "kind": "exists", + "field": "partialFilterExpression", + "exists": false + }, + { + "kind": "exists", + "field": "wildcardProjection", + "exists": false + }, + { + "kind": "exists", + "field": "collation", + "exists": false + } + ] + }, + "expect": "exists" + } + ] + }, + { + "id": "index.posts.create(createdAt:-1,authorId:1)", + "label": "Create index on posts (createdAt:-1, authorId:1)", + "operationClass": "additive", + "precheck": [ + { + "description": "index does not already exist on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "createdAt": -1, + "authorId": 1 + } + }, + "expect": "notExists", + "object": "an index on { createdAt: -1, authorId: 1 } in \"posts\"" + } + ], + "execute": [ + { + "description": "create index on posts", + "command": { + "kind": "createIndex", + "collection": "posts", + "keys": [ + { + "direction": -1, + "field": "createdAt" + }, + { + "direction": 1, + "field": "authorId" + } + ], + "name": "createdAt_-1_authorId_1" + } + } + ], + "postcheck": [ + { + "description": "index exists on posts", + "source": { + "kind": "listIndexes", + "collection": "posts" + }, + "filter": { + "kind": "and", + "exprs": [ + { + "kind": "field", + "field": "key", + "op": "$eq", + "value": { + "createdAt": -1, + "authorId": 1 + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "unique", + "op": "$eq", + "value": true + } + }, + { + "kind": "not", + "expr": { + "kind": "field", + "field": "sparse", + "op": "$eq", + "value": true + } + }, + { + "kind": "exists", + "field": "expireAfterSeconds", + "exists": false + }, + { + "kind": "exists", + "field": "partialFilterExpression", + "exists": false + }, + { + "kind": "exists", + "field": "wildcardProjection", + "exists": false + }, + { + "kind": "exists", + "field": "collation", + "exists": false + } + ] + }, + "expect": "exists" + } + ] + } +] diff --git a/examples/mongo-demo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.d.ts similarity index 100% rename from examples/mongo-demo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.d.ts rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.d.ts diff --git a/examples/mongo-demo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json similarity index 100% rename from examples/mongo-demo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590/contract.json diff --git a/examples/mongo-demo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.d.ts similarity index 100% rename from examples/mongo-demo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.d.ts rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.d.ts diff --git a/examples/mongo-demo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.json similarity index 100% rename from examples/mongo-demo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.json rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0/contract.json diff --git a/examples/mongo-demo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.d.ts similarity index 100% rename from examples/mongo-demo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.d.ts rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.d.ts diff --git a/examples/mongo-demo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json similarity index 100% rename from examples/mongo-demo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/migrations/snapshots/da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd/contract.json diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/src/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/src/contract.d.ts new file mode 100644 index 000000000000..af70721dd428 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/src/contract.d.ts @@ -0,0 +1,504 @@ +// ⚠️ GENERATED FILE - DO NOT EDIT +// This file is automatically generated by 'prisma contract emit'. +// To regenerate, run: prisma contract emit +import type { + CodecTypes as MongoCodecTypes, + ParameterizedOutputTypesForTypeParams as MongoParameterizedOutputTypes, +} from '@prisma/orm-mongo/target/codec-types'; + +import type { + MongoCollection, + MongoContractWithTypeMaps, + MongoTypeMaps, + RelationKeys, +} from '@prisma/orm-mongo/family-contract'; +import type { + Contract as ContractType, + ExecutionHashBase, + FieldTypesFromDomain, + NamespaceId, + ProfileHashBase, + StorageHashBase, + ValueObjectTypesFromDomain, +} from '@prisma/orm-mongo/contract/types'; + +export type StorageHash = + StorageHashBase<'d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0'>; +export type ExecutionHash = ExecutionHashBase; +export type ProfileHash = + ProfileHashBase<'251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3'>; + +export type CodecTypes = MongoCodecTypes; + +type ParameterizedOutputTypeFunctions = MongoParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['__unbound__']['Address']; +export type AddressInput = ValueObjectInputTypes['__unbound__']['Address']; +type FieldTypeOverrides = { + readonly __unbound__: { + readonly models: { + readonly User: { + readonly role: { + readonly output: 'admin' | 'author' | 'reader'; + readonly input: 'admin' | 'author' | 'reader'; + }; + }; + }; + }; +}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; + +export namespace Models { + export type unbound_Article = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + summary: FieldOutputTypes['__unbound__']['Article']['summary']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Post = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'article' | 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_Tutorial = { + authorId: FieldOutputTypes['__unbound__']['Post']['authorId']; + content: FieldOutputTypes['__unbound__']['Post']['content']; + createdAt: FieldOutputTypes['__unbound__']['Post']['createdAt']; + id: FieldOutputTypes['__unbound__']['Post']['id']; + kind: 'tutorial'; + title: FieldOutputTypes['__unbound__']['Post']['title']; + difficulty: FieldOutputTypes['__unbound__']['Tutorial']['difficulty']; + duration: FieldOutputTypes['__unbound__']['Tutorial']['duration']; + author: unbound_User; + readonly [RelationKeys]?: 'author'; + }; + export type unbound_User = { + address: FieldOutputTypes['__unbound__']['User']['address']; + bio: FieldOutputTypes['__unbound__']['User']['bio']; + email: FieldOutputTypes['__unbound__']['User']['email']; + id: FieldOutputTypes['__unbound__']['User']['id']; + name: FieldOutputTypes['__unbound__']['User']['name']; + role: FieldOutputTypes['__unbound__']['User']['role']; + posts: unbound_AnyPost[]; + readonly [RelationKeys]?: 'posts'; + }; + export type unbound_AnyPost = unbound_Article | unbound_Tutorial; +} + +export declare const models: { + __unbound__: { + Article: Models.unbound_Article; + Post: Models.unbound_Post; + Tutorial: Models.unbound_Tutorial; + User: Models.unbound_User; + AnyPost: Models.unbound_AnyPost; + }; +}; + +export type TypeMaps = MongoTypeMaps; + +type ContractBase = Omit< + ContractType<{ + readonly namespaces: { + readonly __unbound__: { + readonly id: '__unbound__'; + readonly kind: 'mongo-database'; + readonly entries: { + readonly collection: { + readonly posts: { + readonly indexes: readonly [ + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'authorId' }]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [ + { readonly direction: -1; readonly field: 'createdAt' }, + { readonly direction: 1; readonly field: 'authorId' }, + ]; + readonly kind: 'mongo-index'; + }, + { + readonly keys: readonly [{ readonly direction: 1; readonly field: 'summary' }]; + readonly kind: 'mongo-index'; + readonly partialFilterExpression: { readonly kind: 'article' }; + readonly unique: true; + }, + ]; + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly bsonType: 'object'; + readonly oneOf: readonly [ + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly enum: readonly ['article'] }; + readonly summary: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['kind', 'summary']; + }, + { + readonly additionalProperties: false; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly difficulty: { readonly bsonType: 'string' }; + readonly duration: { readonly bsonType: 'int' }; + readonly kind: { readonly enum: readonly ['tutorial'] }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly ['difficulty', 'duration', 'kind']; + }, + ]; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly authorId: { readonly bsonType: 'objectId' }; + readonly content: { readonly bsonType: 'string' }; + readonly createdAt: { readonly bsonType: 'date' }; + readonly kind: { readonly bsonType: 'string' }; + readonly title: { readonly bsonType: 'string' }; + }; + readonly required: readonly [ + '_id', + 'authorId', + 'content', + 'createdAt', + 'kind', + 'title', + ]; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + readonly users: { + readonly kind: 'mongo-collection'; + readonly validator: { + readonly jsonSchema: { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly _id: { readonly bsonType: 'objectId' }; + readonly address: { + readonly oneOf: readonly [ + { readonly bsonType: 'null' }, + { + readonly additionalProperties: false; + readonly bsonType: 'object'; + readonly properties: { + readonly city: { readonly bsonType: 'string' }; + readonly country: { readonly bsonType: 'string' }; + readonly street: { readonly bsonType: 'string' }; + readonly zip: { readonly bsonType: readonly ['null', 'string'] }; + }; + readonly required: readonly ['city', 'country', 'street']; + }, + ]; + }; + readonly bio: { readonly bsonType: readonly ['null', 'string'] }; + readonly email: { readonly bsonType: 'string' }; + readonly name: { readonly bsonType: 'string' }; + readonly role: { + readonly bsonType: 'string'; + readonly enum: readonly ['admin', 'author', 'reader']; + }; + }; + readonly required: readonly ['_id', 'email', 'name', 'role']; + }; + readonly kind: 'mongo-validator'; + readonly validationAction: 'error'; + readonly validationLevel: 'strict'; + }; + }; + }; + }; + }; + }; + readonly storageHash: StorageHash; + }>, + 'roots' | 'domain' +> & { + readonly target: 'mongo'; + readonly targetFamily: 'mongo'; + readonly roots: { + readonly posts: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'Post' }; + readonly users: { readonly namespace: '__unbound__' & NamespaceId; readonly model: 'User' }; + }; + readonly domain: { + readonly namespaces: { + readonly __unbound__: { + readonly models: { + readonly Article: { + readonly fields: { + readonly summary: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { readonly summary: { readonly field: 'summary' } }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly Post: { + readonly fields: { + readonly authorId: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly content: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly createdAt: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/date@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly kind: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly title: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly author: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'User'; + }; + readonly cardinality: 'N:1'; + readonly nullable: false; + readonly on: { + readonly localFields: readonly ['authorId']; + readonly targetFields: readonly ['id']; + }; + }; + }; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly authorId: { readonly field: 'authorId' }; + readonly content: { readonly field: 'content' }; + readonly createdAt: { readonly field: 'createdAt' }; + readonly id: { readonly field: '_id' }; + readonly kind: { readonly field: 'kind' }; + readonly title: { readonly field: 'title' }; + }; + }; + readonly discriminator: { readonly field: 'kind' }; + readonly variants: { + readonly article: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Article'; + }; + readonly tutorial: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Tutorial'; + }; + }; + }; + readonly Tutorial: { + readonly fields: { + readonly difficulty: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly duration: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/int32@1' }; + }; + }; + readonly relations: Record; + readonly storage: { + readonly collection: 'posts'; + readonly fields: { + readonly difficulty: { readonly field: 'difficulty' }; + readonly duration: { readonly field: 'duration' }; + }; + }; + readonly base: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + }; + readonly User: { + readonly fields: { + readonly address: { + readonly nullable: true; + readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + }; + readonly bio: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly email: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly id: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/objectId@1' }; + }; + readonly name: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly role: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + readonly relations: { + readonly posts: { + readonly to: { + readonly namespace: '__unbound__' & NamespaceId; + readonly model: 'Post'; + }; + readonly cardinality: '1:N'; + readonly on: { + readonly localFields: readonly ['id']; + readonly targetFields: readonly ['authorId']; + }; + }; + }; + readonly storage: { + readonly collection: 'users'; + readonly fields: { + readonly address: { readonly field: 'address' }; + readonly bio: { readonly field: 'bio' }; + readonly email: { readonly field: 'email' }; + readonly id: { readonly field: '_id' }; + readonly name: { readonly field: 'name' }; + readonly role: { readonly field: 'role' }; + }; + }; + }; + }; + readonly valueObjects: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + readonly enum: { + readonly UserRole: { + readonly codecId: 'mongo/string@1'; + readonly members: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + readonly enumMemberTypes?: { + readonly UserRole: readonly [ + { readonly name: 'Admin'; readonly value: 'admin' }, + { readonly name: 'Author'; readonly value: 'author' }, + { readonly name: 'Reader'; readonly value: 'reader' }, + ]; + }; + }; + }; + }; + readonly capabilities: {}; + readonly extensions: {}; + readonly meta: {}; + readonly valueObjects: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly country: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { + readonly nullable: true; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; + readonly profileHash: ProfileHash; +}; + +export type Contract = MongoContractWithTypeMaps; diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/src/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/src/contract.json new file mode 100644 index 000000000000..8e6eaca148f4 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/mongo/src/contract.json @@ -0,0 +1,580 @@ +{ + "schemaVersion": "1", + "targetFamily": "mongo", + "target": "mongo", + "profileHash": "251b3ce23f6c9f561892e7c1af9d2cc941a13d64ba1aa7226b90036b09568cc3", + "roots": { + "posts": { + "model": "Post", + "namespace": "__unbound__" + }, + "users": { + "model": "User", + "namespace": "__unbound__" + } + }, + "domain": { + "namespaces": { + "__unbound__": { + "enum": { + "UserRole": { + "codecId": "mongo/string@1", + "members": [ + { + "name": "Admin", + "value": "admin" + }, + { + "name": "Author", + "value": "author" + }, + { + "name": "Reader", + "value": "reader" + } + ] + } + }, + "models": { + "Article": { + "base": { + "model": "Post", + "namespace": "__unbound__" + }, + "fields": { + "summary": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "collection": "posts", + "fields": { + "summary": { + "field": "summary" + } + } + } + }, + "Post": { + "discriminator": { + "field": "kind" + }, + "fields": { + "authorId": { + "nullable": false, + "type": { + "codecId": "mongo/objectId@1", + "kind": "scalar" + } + }, + "content": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "createdAt": { + "nullable": false, + "type": { + "codecId": "mongo/date@1", + "kind": "scalar" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "mongo/objectId@1", + "kind": "scalar" + } + }, + "kind": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "title": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + }, + "relations": { + "author": { + "cardinality": "N:1", + "nullable": false, + "on": { + "localFields": [ + "authorId" + ], + "targetFields": [ + "id" + ] + }, + "to": { + "model": "User", + "namespace": "__unbound__" + } + } + }, + "storage": { + "collection": "posts", + "fields": { + "authorId": { + "field": "authorId" + }, + "content": { + "field": "content" + }, + "createdAt": { + "field": "createdAt" + }, + "id": { + "field": "_id" + }, + "kind": { + "field": "kind" + }, + "title": { + "field": "title" + } + } + }, + "variants": { + "article": { + "model": "Article", + "namespace": "__unbound__" + }, + "tutorial": { + "model": "Tutorial", + "namespace": "__unbound__" + } + } + }, + "Tutorial": { + "base": { + "model": "Post", + "namespace": "__unbound__" + }, + "fields": { + "difficulty": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "duration": { + "nullable": false, + "type": { + "codecId": "mongo/int32@1", + "kind": "scalar" + } + } + }, + "relations": {}, + "storage": { + "collection": "posts", + "fields": { + "difficulty": { + "field": "difficulty" + }, + "duration": { + "field": "duration" + } + } + } + }, + "User": { + "fields": { + "address": { + "nullable": true, + "type": { + "kind": "valueObject", + "name": "Address" + } + }, + "bio": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "email": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "id": { + "nullable": false, + "type": { + "codecId": "mongo/objectId@1", + "kind": "scalar" + } + }, + "name": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "role": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + }, + "valueSet": { + "entityKind": "enum", + "entityName": "UserRole", + "namespaceId": "__unbound__", + "plane": "domain" + } + } + }, + "relations": { + "posts": { + "cardinality": "1:N", + "on": { + "localFields": [ + "id" + ], + "targetFields": [ + "authorId" + ] + }, + "to": { + "model": "Post", + "namespace": "__unbound__" + } + } + }, + "storage": { + "collection": "users", + "fields": { + "address": { + "field": "address" + }, + "bio": { + "field": "bio" + }, + "email": { + "field": "email" + }, + "id": { + "field": "_id" + }, + "name": { + "field": "name" + }, + "role": { + "field": "role" + } + } + } + } + }, + "valueObjects": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "country": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": true, + "type": { + "codecId": "mongo/string@1", + "kind": "scalar" + } + } + } + } + } + } + } + }, + "storage": { + "namespaces": { + "__unbound__": { + "entries": { + "collection": { + "posts": { + "indexes": [ + { + "keys": [ + { + "direction": 1, + "field": "authorId" + } + ], + "kind": "mongo-index" + }, + { + "keys": [ + { + "direction": -1, + "field": "createdAt" + }, + { + "direction": 1, + "field": "authorId" + } + ], + "kind": "mongo-index" + }, + { + "keys": [ + { + "direction": 1, + "field": "summary" + } + ], + "kind": "mongo-index", + "partialFilterExpression": { + "kind": "article" + }, + "unique": true + } + ], + "kind": "mongo-collection", + "validator": { + "jsonSchema": { + "bsonType": "object", + "oneOf": [ + { + "additionalProperties": false, + "properties": { + "_id": { + "bsonType": "objectId" + }, + "authorId": { + "bsonType": "objectId" + }, + "content": { + "bsonType": "string" + }, + "createdAt": { + "bsonType": "date" + }, + "kind": { + "enum": [ + "article" + ] + }, + "summary": { + "bsonType": "string" + }, + "title": { + "bsonType": "string" + } + }, + "required": [ + "kind", + "summary" + ] + }, + { + "additionalProperties": false, + "properties": { + "_id": { + "bsonType": "objectId" + }, + "authorId": { + "bsonType": "objectId" + }, + "content": { + "bsonType": "string" + }, + "createdAt": { + "bsonType": "date" + }, + "difficulty": { + "bsonType": "string" + }, + "duration": { + "bsonType": "int" + }, + "kind": { + "enum": [ + "tutorial" + ] + }, + "title": { + "bsonType": "string" + } + }, + "required": [ + "difficulty", + "duration", + "kind" + ] + } + ], + "properties": { + "_id": { + "bsonType": "objectId" + }, + "authorId": { + "bsonType": "objectId" + }, + "content": { + "bsonType": "string" + }, + "createdAt": { + "bsonType": "date" + }, + "kind": { + "bsonType": "string" + }, + "title": { + "bsonType": "string" + } + }, + "required": [ + "_id", + "authorId", + "content", + "createdAt", + "kind", + "title" + ] + }, + "kind": "mongo-validator", + "validationAction": "error", + "validationLevel": "strict" + } + }, + "users": { + "kind": "mongo-collection", + "validator": { + "jsonSchema": { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "_id": { + "bsonType": "objectId" + }, + "address": { + "oneOf": [ + { + "bsonType": "null" + }, + { + "additionalProperties": false, + "bsonType": "object", + "properties": { + "city": { + "bsonType": "string" + }, + "country": { + "bsonType": "string" + }, + "street": { + "bsonType": "string" + }, + "zip": { + "bsonType": [ + "null", + "string" + ] + } + }, + "required": [ + "city", + "country", + "street" + ] + } + ] + }, + "bio": { + "bsonType": [ + "null", + "string" + ] + }, + "email": { + "bsonType": "string" + }, + "name": { + "bsonType": "string" + }, + "role": { + "bsonType": "string", + "enum": [ + "admin", + "author", + "reader" + ] + } + }, + "required": [ + "_id", + "email", + "name", + "role" + ] + }, + "kind": "mongo-validator", + "validationAction": "error", + "validationLevel": "strict" + } + } + }, + "valueSet": { + "UserRole": { + "kind": "valueSet", + "values": [ + "admin", + "author", + "reader" + ] + } + } + }, + "id": "__unbound__", + "kind": "mongo-database" + } + }, + "storageHash": "d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0" + }, + "capabilities": {}, + "extensions": {}, + "meta": {}, + "_generated": { + "warning": "⚠️ GENERATED FILE - DO NOT EDIT", + "message": "This file is automatically generated by \"prisma contract emit\".", + "regenerate": "To regenerate, run: prisma contract emit" + } +} \ No newline at end of file diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/migration.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/migration.json new file mode 100644 index 000000000000..21aa5ae9040c --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/migration.json @@ -0,0 +1,7 @@ +{ + "from": null, + "to": "58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b", + "providedInvariants": [], + "createdAt": "2026-06-19T15:33:14.992Z", + "migrationHash": "bde42f97a6cbf4ca06e4507692274c8e3ece1e1b0672f050bb78a6a2eaa0ff0d" +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/migration.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/migration.ts new file mode 100644 index 000000000000..5f113150356b --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/migration.ts @@ -0,0 +1,215 @@ +#!/usr/bin/env -S node +import { + checkExpression, + col, + fn, + lit, + Migration, + MigrationCLI, + primaryKey, + rawSql, + unique, +} from '@prisma/orm-postgres/target/migration'; +import type { Contract as End } from '../../snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract'; +import endContract from '../../snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json' with { + type: 'json', +}; + +export default class M extends Migration { + override readonly endContractJson = endContract; + + override get operations() { + return [ + rawSql({ + id: 'extension.vector', + label: 'Enable extension "vector"', + summary: 'Ensures the vector extension is available for pgvector operations', + operationClass: 'additive', + target: { id: 'postgres' }, + precheck: [ + { + description: 'verify extension "vector" is not already enabled', + sql: "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + expectation: { expects: 'absent', object: 'extension "vector"' }, + }, + ], + execute: [ + { + description: 'create extension "vector"', + sql: 'CREATE EXTENSION IF NOT EXISTS vector', + }, + ], + postcheck: [ + { + description: 'confirm extension "vector" is enabled', + sql: "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + expectation: { expects: 'present', object: 'extension "vector"' }, + }, + ], + }), + this.createTable({ + schema: 'public', + table: 'bug', + columns: [ + col('id', 'uuid', { notNull: true }), + col('severity', 'text', { notNull: true }), + col('stepsToRepro', 'text'), + ], + constraints: [primaryKey(['id'])], + }), + this.createTable({ + schema: 'public', + table: 'feature', + columns: [ + col('id', 'uuid', { notNull: true }), + col('priority', 'text', { notNull: true }), + col('targetRelease', 'text'), + ], + constraints: [primaryKey(['id'])], + }), + this.createTable({ + schema: 'public', + table: 'tag', + columns: [col('id', 'uuid', { notNull: true }), col('label', 'text', { notNull: true })], + constraints: [primaryKey(['id']), unique(['label'], { name: 'tag_label_key' })], + }), + this.createTable({ + schema: 'public', + table: 'user', + columns: [ + col('address', 'jsonb'), + col('createdAt', 'timestamptz', { notNull: true, default: fn('now()') }), + col('displayName', 'text', { notNull: true }), + col('email', 'text', { notNull: true }), + col('id', 'uuid', { notNull: true }), + col('kind', 'text', { notNull: true }), + ], + constraints: [ + primaryKey(['id']), + checkExpression('user_kind_check_836d43ef', "\"kind\" IN ('admin', 'user')"), + ], + }), + this.createTable({ + schema: 'public', + table: 'post', + columns: [ + col('createdAt', 'timestamptz', { notNull: true, default: fn('now()') }), + col('embedding', 'vector(1536)'), + col('id', 'uuid', { notNull: true }), + col('priority', 'int4', { notNull: true, default: lit(0) }), + col('title', 'text', { notNull: true }), + col('userId', 'uuid', { notNull: true }), + ], + constraints: [ + primaryKey(['id']), + checkExpression('post_priority_check_b236ace9', '"priority" IN (0, 1, 2)'), + ], + }), + this.createTable({ + schema: 'public', + table: 'task', + columns: [ + col('createdAt', 'timestamptz', { notNull: true, default: fn('now()') }), + col('description', 'text'), + col('id', 'uuid', { notNull: true }), + col('status', 'text', { notNull: true, default: lit('open') }), + col('title', 'text', { notNull: true }), + col('type', 'text', { notNull: true }), + col('userId', 'uuid', { notNull: true }), + ], + constraints: [primaryKey(['id'])], + }), + this.createTable({ + schema: 'public', + table: 'post_tag', + columns: [ + col('postId', 'uuid', { notNull: true }), + col('tagId', 'uuid', { notNull: true }), + ], + constraints: [primaryKey(['postId', 'tagId'])], + }), + this.addForeignKey({ + schema: 'public', + table: 'post', + foreignKey: { + name: 'post_userId_fkey', + columns: ['userId'], + references: { schema: 'public', table: 'user', columns: ['id'] }, + }, + }), + this.createIndex({ + schema: 'public', + table: 'post', + index: 'post_userId_idx_a489d58a', + columns: ['userId'], + }), + this.addForeignKey({ + schema: 'public', + table: 'task', + foreignKey: { + name: 'task_userId_fkey', + columns: ['userId'], + references: { schema: 'public', table: 'user', columns: ['id'] }, + }, + }), + this.createIndex({ + schema: 'public', + table: 'task', + index: 'task_userId_idx_a489d58a', + columns: ['userId'], + }), + this.addForeignKey({ + schema: 'public', + table: 'bug', + foreignKey: { + name: 'bug_id_fkey', + columns: ['id'], + references: { schema: 'public', table: 'task', columns: ['id'] }, + onDelete: 'cascade', + }, + }), + this.addForeignKey({ + schema: 'public', + table: 'feature', + foreignKey: { + name: 'feature_id_fkey', + columns: ['id'], + references: { schema: 'public', table: 'task', columns: ['id'] }, + onDelete: 'cascade', + }, + }), + this.createIndex({ + schema: 'public', + table: 'post_tag', + index: 'post_tag_postId_idx_a7a72715', + columns: ['postId'], + }), + this.createIndex({ + schema: 'public', + table: 'post_tag', + index: 'post_tag_tagId_idx_86854244', + columns: ['tagId'], + }), + this.addForeignKey({ + schema: 'public', + table: 'post_tag', + foreignKey: { + name: 'post_tag_postId_fkey', + columns: ['postId'], + references: { schema: 'public', table: 'post', columns: ['id'] }, + }, + }), + this.addForeignKey({ + schema: 'public', + table: 'post_tag', + foreignKey: { + name: 'post_tag_tagId_fkey', + columns: ['tagId'], + references: { schema: 'public', table: 'tag', columns: ['id'] }, + }, + }), + ]; + } +} + +MigrationCLI.run(import.meta.url, M); diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/ops.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/ops.json new file mode 100644 index 000000000000..3843b04f54cb --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/app/20260422T0720_initial/ops.json @@ -0,0 +1,810 @@ +[ + { + "id": "extension.vector", + "label": "Enable extension \"vector\"", + "summary": "Ensures the vector extension is available for pgvector operations", + "operationClass": "additive", + "target": { + "id": "postgres" + }, + "precheck": [ + { + "description": "verify extension \"vector\" is not already enabled", + "sql": "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + "expectation": { + "expects": "absent", + "object": "extension \"vector\"" + } + } + ], + "execute": [ + { + "description": "create extension \"vector\"", + "sql": "CREATE EXTENSION IF NOT EXISTS vector" + } + ], + "postcheck": [ + { + "description": "confirm extension \"vector\" is enabled", + "sql": "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector')", + "expectation": { + "expects": "present", + "object": "extension \"vector\"" + } + } + ] + }, + { + "id": "table.bug", + "label": "Create table \"bug\"", + "summary": "Creates table \"bug\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "bug" + } + }, + "precheck": [ + { + "description": "ensure table \"bug\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"bug\""], + "expectation": { + "expects": "absent", + "object": "table \"bug\"" + } + } + ], + "execute": [ + { + "description": "create table \"bug\"", + "sql": "CREATE TABLE \"public\".\"bug\" (\n \"id\" uuid NOT NULL,\n \"severity\" text NOT NULL,\n \"stepsToRepro\" text,\n PRIMARY KEY (\"id\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"bug\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"bug\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"bug\"" + } + } + ] + }, + { + "id": "table.feature", + "label": "Create table \"feature\"", + "summary": "Creates table \"feature\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "feature" + } + }, + "precheck": [ + { + "description": "ensure table \"feature\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"feature\""], + "expectation": { + "expects": "absent", + "object": "table \"feature\"" + } + } + ], + "execute": [ + { + "description": "create table \"feature\"", + "sql": "CREATE TABLE \"public\".\"feature\" (\n \"id\" uuid NOT NULL,\n \"priority\" text NOT NULL,\n \"targetRelease\" text,\n PRIMARY KEY (\"id\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"feature\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"feature\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"feature\"" + } + } + ] + }, + { + "id": "table.tag", + "label": "Create table \"tag\"", + "summary": "Creates table \"tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "tag" + } + }, + "precheck": [ + { + "description": "ensure table \"tag\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"tag\""], + "expectation": { + "expects": "absent", + "object": "table \"tag\"" + } + } + ], + "execute": [ + { + "description": "create table \"tag\"", + "sql": "CREATE TABLE \"public\".\"tag\" (\n \"id\" uuid NOT NULL,\n \"label\" text NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"tag_label_key\" UNIQUE (\"label\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"tag\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"tag\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"tag\"" + } + } + ] + }, + { + "id": "table.user", + "label": "Create table \"user\"", + "summary": "Creates table \"user\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "user" + } + }, + "precheck": [ + { + "description": "ensure table \"user\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"user\""], + "expectation": { + "expects": "absent", + "object": "table \"user\"" + } + } + ], + "execute": [ + { + "description": "create table \"user\"", + "sql": "CREATE TABLE \"public\".\"user\" (\n \"address\" jsonb,\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"displayName\" text NOT NULL,\n \"email\" text NOT NULL,\n \"id\" uuid NOT NULL,\n \"kind\" text NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"user_kind_check_836d43ef\" CHECK (\"kind\" IN ('admin', 'user'))\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"user\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"user\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"user\"" + } + } + ] + }, + { + "id": "table.post", + "label": "Create table \"post\"", + "summary": "Creates table \"post\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "post" + } + }, + "precheck": [ + { + "description": "ensure table \"post\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post\""], + "expectation": { + "expects": "absent", + "object": "table \"post\"" + } + } + ], + "execute": [ + { + "description": "create table \"post\"", + "sql": "CREATE TABLE \"public\".\"post\" (\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"embedding\" vector(1536),\n \"id\" uuid NOT NULL,\n \"priority\" int4 DEFAULT 0 NOT NULL,\n \"title\" text NOT NULL,\n \"userId\" uuid NOT NULL,\n PRIMARY KEY (\"id\"),\n CONSTRAINT \"post_priority_check_b236ace9\" CHECK (\"priority\" IN (0, 1, 2))\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"post\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"post\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"post\"" + } + } + ] + }, + { + "id": "table.task", + "label": "Create table \"task\"", + "summary": "Creates table \"task\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "task" + } + }, + "precheck": [ + { + "description": "ensure table \"task\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"task\""], + "expectation": { + "expects": "absent", + "object": "table \"task\"" + } + } + ], + "execute": [ + { + "description": "create table \"task\"", + "sql": "CREATE TABLE \"public\".\"task\" (\n \"createdAt\" timestamptz DEFAULT (now()) NOT NULL,\n \"description\" text,\n \"id\" uuid NOT NULL,\n \"status\" text DEFAULT 'open' NOT NULL,\n \"title\" text NOT NULL,\n \"type\" text NOT NULL,\n \"userId\" uuid NOT NULL,\n PRIMARY KEY (\"id\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"task\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"task\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"task\"" + } + } + ] + }, + { + "id": "table.post_tag", + "label": "Create table \"post_tag\"", + "summary": "Creates table \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "table", + "name": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure table \"post_tag\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_tag\""], + "expectation": { + "expects": "absent", + "object": "table \"post_tag\"" + } + } + ], + "execute": [ + { + "description": "create table \"post_tag\"", + "sql": "CREATE TABLE \"public\".\"post_tag\" (\n \"postId\" uuid NOT NULL,\n \"tagId\" uuid NOT NULL,\n PRIMARY KEY (\"postId\", \"tagId\")\n)", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify table \"post_tag\" exists", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3))) AS \"result\"", + "params": ["\"public\".\"post_tag\"", "r", "p"], + "expectation": { + "expects": "present", + "object": "table \"post_tag\"" + } + } + ] + }, + { + "id": "foreignKey.post.post_userId_fkey", + "label": "Add foreign key \"post_userId_fkey\" on \"post\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "post_userId_fkey", + "table": "post" + } + }, + "precheck": [ + { + "description": "ensure FK \"post_userId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["post_userId_fkey", "public", "\"public\".\"post\""], + "expectation": { + "expects": "absent", + "object": "FK \"post_userId_fkey\" on \"post\"" + } + } + ], + "execute": [ + { + "description": "add FK \"post_userId_fkey\"", + "sql": "ALTER TABLE \"public\".\"post\"\nADD CONSTRAINT \"post_userId_fkey\"\nFOREIGN KEY (\"userId\")\nREFERENCES \"public\".\"user\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"post_userId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "post_userId_fkey", + "public", + "\"public\".\"post\"", + "f", + "[\"userId\"]", + "\"public\".\"user\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + }, + { + "id": "index.post.post_userId_idx_a489d58a", + "label": "Create index \"post_userId_idx_a489d58a\" on \"post\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "post_userId_idx_a489d58a", + "table": "post" + } + }, + "precheck": [ + { + "description": "ensure index \"post_userId_idx_a489d58a\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_userId_idx_a489d58a\""], + "expectation": { + "expects": "absent", + "object": "index \"post_userId_idx_a489d58a\"" + } + } + ], + "execute": [ + { + "description": "create index \"post_userId_idx_a489d58a\"", + "sql": "CREATE INDEX \"post_userId_idx_a489d58a\" ON \"public\".\"post\" (\"userId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"post_userId_idx_a489d58a\" exists on \"post\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"post_userId_idx_a489d58a\"", + "i", + "I", + "\"public\".\"post\"", + "btree", + "[\"userId\"]" + ] + } + ] + }, + { + "id": "foreignKey.task.task_userId_fkey", + "label": "Add foreign key \"task_userId_fkey\" on \"task\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "task_userId_fkey", + "table": "task" + } + }, + "precheck": [ + { + "description": "ensure FK \"task_userId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["task_userId_fkey", "public", "\"public\".\"task\""], + "expectation": { + "expects": "absent", + "object": "FK \"task_userId_fkey\" on \"task\"" + } + } + ], + "execute": [ + { + "description": "add FK \"task_userId_fkey\"", + "sql": "ALTER TABLE \"public\".\"task\"\nADD CONSTRAINT \"task_userId_fkey\"\nFOREIGN KEY (\"userId\")\nREFERENCES \"public\".\"user\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"task_userId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "task_userId_fkey", + "public", + "\"public\".\"task\"", + "f", + "[\"userId\"]", + "\"public\".\"user\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + }, + { + "id": "index.task.task_userId_idx_a489d58a", + "label": "Create index \"task_userId_idx_a489d58a\" on \"task\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "task_userId_idx_a489d58a", + "table": "task" + } + }, + "precheck": [ + { + "description": "ensure index \"task_userId_idx_a489d58a\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"task_userId_idx_a489d58a\""], + "expectation": { + "expects": "absent", + "object": "index \"task_userId_idx_a489d58a\"" + } + } + ], + "execute": [ + { + "description": "create index \"task_userId_idx_a489d58a\"", + "sql": "CREATE INDEX \"task_userId_idx_a489d58a\" ON \"public\".\"task\" (\"userId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"task_userId_idx_a489d58a\" exists on \"task\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"task_userId_idx_a489d58a\"", + "i", + "I", + "\"public\".\"task\"", + "btree", + "[\"userId\"]" + ] + } + ] + }, + { + "id": "foreignKey.bug.bug_id_fkey", + "label": "Add foreign key \"bug_id_fkey\" on \"bug\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "bug_id_fkey", + "table": "bug" + } + }, + "precheck": [ + { + "description": "ensure FK \"bug_id_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["bug_id_fkey", "public", "\"public\".\"bug\""], + "expectation": { + "expects": "absent", + "object": "FK \"bug_id_fkey\" on \"bug\"" + } + } + ], + "execute": [ + { + "description": "add FK \"bug_id_fkey\"", + "sql": "ALTER TABLE \"public\".\"bug\"\nADD CONSTRAINT \"bug_id_fkey\"\nFOREIGN KEY (\"id\")\nREFERENCES \"public\".\"task\" (\"id\")\nON DELETE CASCADE" + } + ], + "postcheck": [ + { + "description": "verify FK \"bug_id_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "bug_id_fkey", + "public", + "\"public\".\"bug\"", + "f", + "[\"id\"]", + "\"public\".\"task\"", + "[\"id\"]", + "c", + "a" + ] + } + ] + }, + { + "id": "foreignKey.feature.feature_id_fkey", + "label": "Add foreign key \"feature_id_fkey\" on \"feature\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "feature_id_fkey", + "table": "feature" + } + }, + "precheck": [ + { + "description": "ensure FK \"feature_id_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["feature_id_fkey", "public", "\"public\".\"feature\""], + "expectation": { + "expects": "absent", + "object": "FK \"feature_id_fkey\" on \"feature\"" + } + } + ], + "execute": [ + { + "description": "add FK \"feature_id_fkey\"", + "sql": "ALTER TABLE \"public\".\"feature\"\nADD CONSTRAINT \"feature_id_fkey\"\nFOREIGN KEY (\"id\")\nREFERENCES \"public\".\"task\" (\"id\")\nON DELETE CASCADE" + } + ], + "postcheck": [ + { + "description": "verify FK \"feature_id_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "feature_id_fkey", + "public", + "\"public\".\"feature\"", + "f", + "[\"id\"]", + "\"public\".\"task\"", + "[\"id\"]", + "c", + "a" + ] + } + ] + }, + { + "id": "index.post_tag.post_tag_postId_idx_a7a72715", + "label": "Create index \"post_tag_postId_idx_a7a72715\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "post_tag_postId_idx_a7a72715", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure index \"post_tag_postId_idx_a7a72715\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_tag_postId_idx_a7a72715\""], + "expectation": { + "expects": "absent", + "object": "index \"post_tag_postId_idx_a7a72715\"" + } + } + ], + "execute": [ + { + "description": "create index \"post_tag_postId_idx_a7a72715\"", + "sql": "CREATE INDEX \"post_tag_postId_idx_a7a72715\" ON \"public\".\"post_tag\" (\"postId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"post_tag_postId_idx_a7a72715\" exists on \"post_tag\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"post_tag_postId_idx_a7a72715\"", + "i", + "I", + "\"public\".\"post_tag\"", + "btree", + "[\"postId\"]" + ] + } + ] + }, + { + "id": "index.post_tag.post_tag_tagId_idx_86854244", + "label": "Create index \"post_tag_tagId_idx_86854244\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "index", + "name": "post_tag_tagId_idx_86854244", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure index \"post_tag_tagId_idx_86854244\" does not exist", + "sql": "SELECT (to_regclass($1)) IS NULL AS \"result\"", + "params": ["\"public\".\"post_tag_tagId_idx_86854244\""], + "expectation": { + "expects": "absent", + "object": "index \"post_tag_tagId_idx_86854244\"" + } + } + ], + "execute": [ + { + "description": "create index \"post_tag_tagId_idx_86854244\"", + "sql": "CREATE INDEX \"post_tag_tagId_idx_86854244\" ON \"public\".\"post_tag\" (\"tagId\")", + "params": [] + } + ], + "postcheck": [ + { + "description": "verify index \"post_tag_tagId_idx_86854244\" exists on \"post_tag\" with the planned definition", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_class\" AS \"c\" INNER JOIN \"pg_index\" AS \"i\" ON \"i\".\"indexrelid\" = \"c\".\"oid\" INNER JOIN \"pg_am\" AS \"m\" ON \"m\".\"oid\" = \"c\".\"relam\" WHERE (\"c\".\"oid\" = to_regclass($1) AND (\"c\".\"relkind\" = $2 OR \"c\".\"relkind\" = $3) AND \"i\".\"indrelid\" = to_regclass($4) AND NOT (\"i\".\"indisunique\") AND \"m\".\"amname\" = $5 AND (\"i\".\"indexprs\" IS NULL AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"i\".\"indkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"i\".\"indrelid\" AND key_attribute.attnum = key_column.attnum) = ($6)::jsonb) AND \"i\".\"indpred\" IS NULL)) AS \"result\"", + "params": [ + "\"public\".\"post_tag_tagId_idx_86854244\"", + "i", + "I", + "\"public\".\"post_tag\"", + "btree", + "[\"tagId\"]" + ] + } + ] + }, + { + "id": "foreignKey.post_tag.post_tag_postId_fkey", + "label": "Add foreign key \"post_tag_postId_fkey\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "post_tag_postId_fkey", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure FK \"post_tag_postId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["post_tag_postId_fkey", "public", "\"public\".\"post_tag\""], + "expectation": { + "expects": "absent", + "object": "FK \"post_tag_postId_fkey\" on \"post_tag\"" + } + } + ], + "execute": [ + { + "description": "add FK \"post_tag_postId_fkey\"", + "sql": "ALTER TABLE \"public\".\"post_tag\"\nADD CONSTRAINT \"post_tag_postId_fkey\"\nFOREIGN KEY (\"postId\")\nREFERENCES \"public\".\"post\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"post_tag_postId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "post_tag_postId_fkey", + "public", + "\"public\".\"post_tag\"", + "f", + "[\"postId\"]", + "\"public\".\"post\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + }, + { + "id": "foreignKey.post_tag.post_tag_tagId_fkey", + "label": "Add foreign key \"post_tag_tagId_fkey\" on \"post_tag\"", + "operationClass": "additive", + "target": { + "id": "postgres", + "details": { + "schema": "public", + "objectType": "foreignKey", + "name": "post_tag_tagId_fkey", + "table": "post_tag" + } + }, + "precheck": [ + { + "description": "ensure FK \"post_tag_tagId_fkey\" does not exist", + "sql": "SELECT NOT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3))) AS \"result\"", + "params": ["post_tag_tagId_fkey", "public", "\"public\".\"post_tag\""], + "expectation": { + "expects": "absent", + "object": "FK \"post_tag_tagId_fkey\" on \"post_tag\"" + } + } + ], + "execute": [ + { + "description": "add FK \"post_tag_tagId_fkey\"", + "sql": "ALTER TABLE \"public\".\"post_tag\"\nADD CONSTRAINT \"post_tag_tagId_fkey\"\nFOREIGN KEY (\"tagId\")\nREFERENCES \"public\".\"tag\" (\"id\")" + } + ], + "postcheck": [ + { + "description": "verify FK \"post_tag_tagId_fkey\" exists with the planned columns, referenced table and actions", + "sql": "SELECT EXISTS (SELECT 1 AS \"one\" FROM \"pg_constraint\" AS \"c\" INNER JOIN \"pg_namespace\" AS \"n\" ON \"n\".\"oid\" = \"c\".\"connamespace\" WHERE (\"c\".\"conname\" = $1 AND \"n\".\"nspname\" = $2 AND \"c\".\"conrelid\" = to_regclass($3) AND \"c\".\"contype\" = $4 AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"conkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"conrelid\" AND key_attribute.attnum = key_column.attnum) = ($5)::jsonb AND \"c\".\"confrelid\" = to_regclass($6) AND (SELECT coalesce(jsonb_agg(key_attribute.attname ORDER BY key_column.position), '[]'::jsonb) FROM unnest(\"c\".\"confkey\"::int2[]) WITH ORDINALITY AS key_column(attnum, position) JOIN pg_attribute key_attribute ON key_attribute.attrelid = \"c\".\"confrelid\" AND key_attribute.attnum = key_column.attnum) = ($7)::jsonb AND \"c\".\"confdeltype\" = $8 AND \"c\".\"confupdtype\" = $9)) AS \"result\"", + "params": [ + "post_tag_tagId_fkey", + "public", + "\"public\".\"post_tag\"", + "f", + "[\"tagId\"]", + "\"public\".\"tag\"", + "[\"id\"]", + "a", + "a" + ] + } + ] + } +] diff --git a/examples/prisma-8-demo/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.d.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.d.ts similarity index 98% rename from examples/prisma-8-demo/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.d.ts rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.d.ts index d80ae3a33b2b..64fd0c6ba064 100644 --- a/examples/prisma-8-demo/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.d.ts +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.d.ts @@ -50,10 +50,6 @@ export type AggregateTypes = { readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; - readonly 'pg/interval-string@1': { - readonly output: 'pg/interval-string@1'; - readonly nullable: true; - }; readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; @@ -103,10 +99,6 @@ export type AggregateTypes = { readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval-string@1': { - readonly output: 'pg/interval-string@1'; - readonly nullable: true; - }; readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; @@ -116,14 +108,6 @@ export type AggregateTypes = { readonly output: 'pg/time-temporal@1'; readonly nullable: true; }; - readonly 'pg/timestamp-date@1': { - readonly output: 'pg/timestamp-date@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-instant@1': { - readonly output: 'pg/timestamp-instant@1'; - readonly nullable: true; - }; readonly 'pg/timestamp-string@1': { readonly output: 'pg/timestamp-string@1'; readonly nullable: true; @@ -175,10 +159,6 @@ export type AggregateTypes = { readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval-string@1': { - readonly output: 'pg/interval-string@1'; - readonly nullable: true; - }; readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; @@ -188,14 +168,6 @@ export type AggregateTypes = { readonly output: 'pg/time-temporal@1'; readonly nullable: true; }; - readonly 'pg/timestamp-date@1': { - readonly output: 'pg/timestamp-date@1'; - readonly nullable: true; - }; - readonly 'pg/timestamp-instant@1': { - readonly output: 'pg/timestamp-instant@1'; - readonly nullable: true; - }; readonly 'pg/timestamp-string@1': { readonly output: 'pg/timestamp-string@1'; readonly nullable: true; @@ -255,10 +227,6 @@ export type AggregateTypes = { readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; - readonly 'pg/interval-string@1': { - readonly output: 'pg/interval-string@1'; - readonly nullable: true; - }; readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; diff --git a/examples/prisma-8-demo/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json b/test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json similarity index 100% rename from examples/prisma-8-demo/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json rename to test/integration/test/upgrade-instructions/value-objects-in-storage/fixtures/before/sql/migrations/snapshots/58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b/contract.json diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/test-helpers.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/test-helpers.ts new file mode 100644 index 000000000000..e15c68879573 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/test-helpers.ts @@ -0,0 +1,73 @@ +import { spawnSync } from 'node:child_process'; +import { + cpSync, + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, relative } from 'pathe'; + +const SCRIPT_PATHS = { + app: 'upgrade-instructions/pending/value-objects-in-storage/app/scripts/value-objects-in-storage/value-objects-in-storage.ts', +} as const; + +const here = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(here, '../../../../..'); +export const fixturesDir = join(here, 'fixtures'); +const appScript = join(repoRoot, SCRIPT_PATHS.app); +const workDirs: string[] = []; + +export type Tree = Record; + +export function removeWorkDirs(): void { + for (const dir of workDirs.splice(0)) rmSync(dir, { recursive: true, force: true }); +} + +export function readTree(root: string, dir = root): Tree { + const files: Tree = {}; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const path = join(dir, entry.name); + if (entry.isDirectory()) Object.assign(files, readTree(root, path)); + else if (entry.isFile()) files[relative(root, path)] = readFileSync(path, 'utf8'); + } + return files; +} + +export function fixtureTree(side: 'before' | 'after'): Tree { + return readTree(join(fixturesDir, side)); +} + +export function copyFixture(side: 'before' | 'after'): string { + const root = mkdtempSync(join(tmpdir(), 'value-objects-in-storage-')); + workDirs.push(root); + cpSync(join(fixturesDir, side), root, { recursive: true }); + return root; +} + +export function editJson( + root: string, + path: string, + edit: (json: unknown) => unknown, + writtenPath = path, +): void { + const json: unknown = JSON.parse(readFileSync(join(root, path), 'utf8')); + const target = join(root, writtenPath); + mkdirSync(dirname(target), { recursive: true }); + writeFileSync(target, `${JSON.stringify(edit(json), null, 2)}\n`); +} + +export interface Run { + readonly status: number | null; + readonly stdout: string; + readonly stderr: string; +} + +export function runScript(root: string): Run { + const result = spawnSync(process.execPath, [appScript, root], { encoding: 'utf8' }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} diff --git a/test/integration/test/upgrade-instructions/value-objects-in-storage/value-objects-in-storage.test.ts b/test/integration/test/upgrade-instructions/value-objects-in-storage/value-objects-in-storage.test.ts new file mode 100644 index 000000000000..bc2259028bc7 --- /dev/null +++ b/test/integration/test/upgrade-instructions/value-objects-in-storage/value-objects-in-storage.test.ts @@ -0,0 +1,344 @@ +import { chmodSync, readFileSync, writeFileSync } from 'node:fs'; +import { computeStorageHash } from '@internal/contract/hashing'; +import { MongoContractSerializer } from '@internal/family-mongo/ir'; +import { mongoContractCanonicalizationHooks } from '@internal/mongo-contract/canonicalization-hooks'; +import { sqlContractCanonicalizationHooks } from '@internal/sql-contract/canonicalization-hooks'; +import { PostgresContractSerializer } from '@internal/target-postgres/runtime'; +import { join } from 'pathe'; +import { afterAll, describe, expect, it } from 'vitest'; +import { + copyFixture, + editJson, + fixturesDir, + fixtureTree, + readTree, + removeWorkDirs, + runScript, +} from './test-helpers'; + +afterAll(removeWorkDirs); + +const SQL_BEFORE = '58888a20cba5529ede90056ecd1318183f0e5cf280c88cd58aeadd94bbf52c1b'; +const SQL_AFTER = '1cb334a7b32d249d4fea0934dbbaa48c473b5946cd77ee1ce7442272479d6cf3'; +const SQL_SNAPSHOT = `sql/migrations/snapshots/${SQL_BEFORE}/contract.json`; + +interface StoredContract { + readonly targetFamily: string; + readonly target: string; + readonly storage: Record & { readonly storageHash: string }; +} + +function readContract(path: string): StoredContract { + return JSON.parse(readFileSync(path, 'utf8')); +} + +function recomputedHash(contract: StoredContract): string { + const { storageHash: _stored, ...storage } = contract.storage; + return computeStorageHash({ + target: contract.target, + targetFamily: contract.targetFamily, + storage, + ...(contract.targetFamily === 'mongo' + ? mongoContractCanonicalizationHooks + : sqlContractCanonicalizationHooks), + }); +} + +describe('a project whose contracts keep value objects in the domain plane', () => { + const root = copyFixture('before'); + const run = runScript(root); + + it('moves the value objects into storage and renames each snapshot to its new storage hash', () => { + expect({ run, tree: readTree(root) }).toEqual({ + run: { + status: 0, + stdout: [ + 'Rewrote 10 files and renamed 4 snapshot directories. Storage hashes changed (old -> new):', + ` ${SQL_BEFORE} -> ${SQL_AFTER}`, + ' 62671fbf016c515d808ac613743fddbf3db80384c1997251eac9d6f5fc063590 -> 95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9', + ' d264cabd6a02546054a776ed3c04438b9a22989f406c49f5590f7f2101156eb0 -> 406b672b68a6b01f04c0d6f910f5fefa4ac16d48b26e6c91b43856cd42c8cc5d', + ' da1339e341177f79b37f765f08200844d7cb4d59d26fe27fe4d95b0112b0c2cd -> 396942f6c9ee5f946d7f67b397929bab327fe84dd8f7f80e87b8097f416fb901', + '', + ].join('\n'), + stderr: '', + }, + tree: fixtureTree('after'), + }); + }); + + it('changes nothing on a second run', () => { + expect(runScript(root)).toEqual({ + status: 0, + stdout: 'No contract keeps value objects in the domain plane; nothing changed.\n', + stderr: '', + }); + }); +}); + +describe('an old MongoDB contract whose value object members leave nullable out or say dict false', () => { + it('is written with nullable false and without dict, as this release reads it', () => { + const path = 'mongo/src/contract.json'; + const root = copyFixture('before'); + editJson(root, path, (json) => { + const contract = json as { + domain: { + namespaces: { + __unbound__: { + valueObjects: { + Address: { fields: Record }; + }; + }; + }; + }; + }; + const fields = contract.domain.namespaces.__unbound__.valueObjects.Address.fields; + if (fields['city'] !== undefined) delete fields['city'].nullable; + if (fields['street'] !== undefined) fields['street'].dict = false; + return contract; + }); + runScript(root); + expect(readContract(join(root, path))).toEqual(readContract(join(fixturesDir, 'after', path))); + }); + + it('keeps a nullable that is not a boolean, so validation refuses it', () => { + const path = 'mongo/src/contract.json'; + const root = copyFixture('before'); + editJson(root, path, (json) => { + const contract = json as { + domain: { + namespaces: { + __unbound__: { + valueObjects: { Address: { fields: Record } }; + }; + }; + }; + }; + const city = contract.domain.namespaces.__unbound__.valueObjects.Address.fields['city']; + if (city !== undefined) city.nullable = 'yes'; + return contract; + }); + runScript(root); + const rewritten = JSON.parse(readFileSync(join(root, path), 'utf8')) as { + storage: { + namespaces: Record< + string, + { entries: { valueObject: { Address: { fields: { city: unknown } } } } } + >; + }; + }; + const city = Object.values(rewritten.storage.namespaces) + .map((namespace) => namespace.entries.valueObject?.Address?.fields.city) + .find((member) => member !== undefined); + expect(city).toMatchObject({ nullable: 'yes' }); + }); +}); + +describe('a contract.d.ts whose contract the script leaves unchanged', () => { + it('gains the storage namespaces argument of FieldTypesFromDomain, which is now required', () => { + const root = copyFixture('after'); + const path = join( + root, + 'mongo/migrations/snapshots/95332b0bd6fbbfab4462d882c037a574c92e51897249c333ba681b12c4d603c9/contract.d.ts', + ); + writeFileSync( + path, + readFileSync(path, 'utf8').replaceAll(",\n ContractBase['storage']['namespaces']\n>", '\n>'), + ); + expect({ run: runScript(root), tree: readTree(root) }).toEqual({ + run: { + status: 0, + stdout: 'Rewrote 1 file and renamed 0 snapshot directories.\n', + stderr: '', + }, + tree: fixtureTree('after'), + }); + }); +}); + +describe('the rewritten contracts', () => { + const after = fixtureTree('after'); + const contracts = Object.keys(after) + .filter((path) => path.endsWith('contract.json')) + .map((path) => ({ path, contract: readContract(join(fixturesDir, 'after', path)) })); + + it('store the hash their storage recomputes to, and each snapshot sits under it', () => { + expect( + contracts.map(({ path, contract }) => ({ + path, + stored: contract.storage.storageHash, + recomputed: recomputedHash(contract), + directory: path.includes('/snapshots/') ? path.split('/').at(-2) : undefined, + })), + ).toEqual( + contracts.map(({ path, contract }) => ({ + path, + stored: contract.storage.storageHash, + recomputed: contract.storage.storageHash, + directory: path.includes('/snapshots/') ? contract.storage.storageHash : undefined, + })), + ); + }); + + it('are read by this release', () => { + const read = contracts.map(({ path, contract }) => { + const json: unknown = contract; + const deserialized = + contract.targetFamily === 'mongo' + ? new MongoContractSerializer().deserializeContract(json) + : new PostgresContractSerializer().deserializeContract(json); + return { path, storageHash: deserialized.storage.storageHash }; + }); + expect(read).toEqual( + contracts.map(({ path, contract }) => ({ path, storageHash: contract.storage.storageHash })), + ); + }); + + it('give the SQL discriminator a value set and point its column at it', () => { + const sql = readContract( + join(fixturesDir, 'after', `sql/migrations/snapshots/${SQL_AFTER}/contract.json`), + ); + expect(sql.storage).toMatchObject({ + namespaces: { + public: { + entries: { + valueSet: { 'Task.type': { kind: 'valueSet', values: ['bug', 'feature'] } }, + table: { + task: { + columns: { + type: { + valueSet: { + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueSet', + entityName: 'Task.type', + }, + }, + }, + }, + }, + }, + }, + }, + }); + }); +}); + +describe('a project the script refuses to rewrite', () => { + function refused(edit: (root: string) => void) { + const root = copyFixture('before'); + edit(root); + const before = readTree(root); + const run = runScript(root); + return { run, unchanged: JSON.stringify(readTree(root)) === JSON.stringify(before) }; + } + + it('stops on a snapshot whose stored hash does not match its content', () => { + const result = refused((root) => + editJson(root, SQL_SNAPSHOT, (json) => { + const contract = json as { + storage: { + namespaces: { + public: { + entries: { + table: Record }>; + }; + }; + }; + }; + }; + const column = contract.storage.namespaces.public.entries.table['task']?.columns['title']; + if (column !== undefined) column.nullable = true; + return contract; + }), + ); + expect(result).toMatchObject({ + run: { + status: 1, + stdout: '', + stderr: expect.stringContaining( + `${SQL_SNAPSHOT}: the stored storage hash ${SQL_BEFORE} does not match the contract's content`, + ), + }, + unchanged: true, + }); + }); + + it('stops on a field that holds a value object the contract does not declare', () => { + const result = refused((root) => + editJson(root, SQL_SNAPSHOT, (json) => { + const contract = json as { + domain: { + namespaces: { + public: { + models: Record }>; + }; + }; + }; + }; + const field = contract.domain.namespaces.public.models['User']?.fields['address']; + if (field !== undefined) field.type.name = 'Missing'; + return contract; + }), + ); + expect(result).toEqual({ + run: { + status: 1, + stdout: '', + stderr: `${SQL_SNAPSHOT}: field User.address holds the value object Missing, which the contract does not declare; restore the file from version control, or re-emit it, and run the script again\n`, + }, + unchanged: true, + }); + }); + + it('stops on a discriminator whose value set name an enum already has', () => { + const result = refused((root) => + editJson( + root, + SQL_SNAPSHOT, + (json) => { + const contract = json as StoredContract & { + storage: { namespaces: { public: { entries: { valueSet: Record } } } }; + }; + contract.storage.namespaces.public.entries.valueSet['Task.type'] = { + kind: 'valueSet', + values: ['bug', 'feature'], + }; + const hash = recomputedHash(contract); + return { ...contract, storage: { ...contract.storage, storageHash: hash } }; + }, + 'sql/src/contract.json', + ), + ); + expect(result.run).toMatchObject({ + status: 1, + stderr: expect.stringContaining( + 'sql/src/contract.json: the discriminator Task.type writes the value set Task.type in storage namespace public, which already holds a value set of that name; rename the enum', + ), + }); + }); + + it('stops with one line on a directory it cannot read', () => { + const root = copyFixture('before'); + const locked = join(root, 'sql', 'migrations', 'app'); + chmodSync(locked, 0o000); + try { + expect(runScript(root)).toEqual({ + status: 1, + stdout: '', + stderr: + 'sql/migrations/app: cannot read the directory (EACCES); give the script read access to it, or move it out of the project, and run the script again\n', + }); + } finally { + chmodSync(locked, 0o755); + } + }); + + it('says when it finds no contract', () => { + const root = join(copyFixture('before'), 'sql', 'migrations', 'app'); + expect(runScript(root)).toEqual({ + status: 0, + stdout: `No SQL or MongoDB contract was found under ${root}; nothing changed.\n`, + stderr: '', + }); + }); +}); diff --git a/test/integration/test/utils/journey-workspace-helpers.ts b/test/integration/test/utils/journey-workspace-helpers.ts index c1f9b8b81a01..0771b1eaedcc 100644 --- a/test/integration/test/utils/journey-workspace-helpers.ts +++ b/test/integration/test/utils/journey-workspace-helpers.ts @@ -155,6 +155,34 @@ export async function selectRows(options: { } } +/** Creates a row of `model` in the `public` namespace through a Postgres client built from the emitted contract. */ +export async function createRowThroughOrm(options: { + readonly contractJson: unknown; + readonly connectionString: string; + readonly model: string; + readonly row: Record; +}): Promise { + const client = postgres({ + contract: contractFromJson>(options.contractJson), + url: options.connectionString, + }); + const runtime = await client.connect(); + try { + const models = ( + client.orm as unknown as { + readonly public: Record): Promise }>; + } + ).public; + const collection = models[options.model]; + if (collection === undefined) { + throw new Error(`the ORM exposes no ${options.model}; it has ${Object.keys(models)}`); + } + return await collection.create(options.row); + } finally { + await runtime.close(); + } +} + /** * The PSL `contract infer` would write for the Postgres schema `namespace`. * diff --git a/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts b/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts index 312ae5eb07bb..516135459811 100644 --- a/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts +++ b/test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts @@ -60,35 +60,29 @@ type ContractBase = { }; readonly location: { readonly many: false; readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Location' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Location'; + }; }; readonly notes: { readonly many: false; readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Location' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: '__unbound__'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Location'; + }; }; }; readonly relations: Record; readonly storage: { readonly collection: 'shops'; readonly fields: { readonly _id: { readonly field: '_id' }; readonly name: { readonly field: 'name' }; readonly location: { readonly field: 'location' }; readonly notes: { readonly field: 'notes' } } }; }; }; - readonly valueObjects: { - readonly Location: { - readonly fields: { - readonly street: { readonly many: false; - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly city: { readonly many: false; - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - readonly zip: { readonly many: false; - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; - }; - }; - }; - }; }; }; }; @@ -101,6 +95,24 @@ type ContractBase = { readonly collection: { readonly shops: { readonly kind: 'mongo-collection' }; }; + readonly valueObject: { + readonly Location: { + readonly fields: { + readonly street: { readonly many: false; + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly city: { readonly many: false; + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + readonly zip: { readonly many: false; + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'mongo/string@1' }; + }; + }; + }; + }; }; }; }; diff --git a/test/integration/test/value-objects/fixtures/generated/mongo-contract.json b/test/integration/test/value-objects/fixtures/generated/mongo-contract.json index 64b418080e34..1d00154fceee 100644 --- a/test/integration/test/value-objects/fixtures/generated/mongo-contract.json +++ b/test/integration/test/value-objects/fixtures/generated/mongo-contract.json @@ -19,6 +19,36 @@ "shops": { "kind": "mongo-collection" } + }, + "valueObject": { + "Location": { + "fields": { + "street": { + "many": false, + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false + }, + "city": { + "many": false, + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false + }, + "zip": { + "many": false, + "type": { + "kind": "scalar", + "codecId": "mongo/string@1" + }, + "nullable": false + } + } + } } } } @@ -54,7 +84,10 @@ "many": false, "type": { "kind": "valueObject", - "name": "Location" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Location" }, "nullable": false }, @@ -62,7 +95,10 @@ "many": false, "type": { "kind": "valueObject", - "name": "Location" + "plane": "storage", + "namespaceId": "__unbound__", + "entityKind": "valueObject", + "entityName": "Location" }, "nullable": true } @@ -86,36 +122,6 @@ } } } - }, - "valueObjects": { - "Location": { - "fields": { - "street": { - "many": false, - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false - }, - "city": { - "many": false, - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false - }, - "zip": { - "many": false, - "type": { - "kind": "scalar", - "codecId": "mongo/string@1" - }, - "nullable": false - } - } - } } } } diff --git a/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts b/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts index 2e317e716bdc..3404cacfb94e 100644 --- a/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts +++ b/test/integration/test/value-objects/fixtures/generated/sql-contract.d.ts @@ -1,29 +1,30 @@ // ⚠️ GENERATED FILE - DO NOT EDIT // This file is automatically generated by 'prisma contract emit'. // To regenerate, run: prisma contract emit -import type { QueryOperationTypes as PgTargetQueryOps } from '@internal/target-postgres/operation-types'; import type { - Bit, - Char, CodecTypes as PgTypes, - JsonValue, - Numeric, - Timetz, - VarBit, - Varchar, + ParameterizedOutputTypesForTypeParams as PgParameterizedOutputTypes, } from '@internal/target-postgres/codec-types'; +import type { QueryOperationTypes as PgTargetQueryOps } from '@internal/target-postgres/operation-types'; -import type { ContractWithTypeMaps, TypeMaps as TypeMapsType } from '@internal/sql-contract/types'; +import type { + ColumnTypesFromStorage, + ContractWithTypeMaps, + RelationKeys, + TypeMaps as TypeMapsType, +} from '@internal/sql-contract/types'; import type { Contract as ContractType, ExecutionHashBase, + FieldTypesFromDomain, NamespaceId, ProfileHashBase, StorageHashBase, + ValueObjectTypesFromStorage, } from '@internal/contract/types'; export type StorageHash = - StorageHashBase<'8334dda9271ee13d092ca25003aed6d6507dbad3e83de89f994a48661d16bbf4'>; + StorageHashBase<'d1f566293d77a0d2d3dc4b1fbcec129b2a6a25ac509570d6f757dc83665939b4'>; export type ExecutionHash = ExecutionHashBase; export type ProfileHash = ProfileHashBase<'3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2'>; @@ -31,66 +32,316 @@ export type ProfileHash = export type CodecTypes = PgTypes; export type LaneCodecTypes = CodecTypes; export type QueryOperationTypes = PgTargetQueryOps; -type DefaultLiteralValue = CodecId extends keyof CodecTypes - ? CodecTypes[CodecId]['output'] - : _Encoded; -export type AddressOutput = { - readonly street: CodecTypes['pg/text@1']['output']; - readonly city: CodecTypes['pg/text@1']['output']; - readonly zip: CodecTypes['pg/text@1']['output']; -}; -export type AddressInput = { - readonly street: CodecTypes['pg/text@1']['input']; - readonly city: CodecTypes['pg/text@1']['input']; - readonly zip: CodecTypes['pg/text@1']['input']; -}; -export type FieldOutputTypes = { - readonly public: { - readonly Shop: { - readonly id: CodecTypes['pg/int4@1']['output']; - readonly name: CodecTypes['pg/text@1']['output']; - readonly location: AddressOutput; - readonly notes: AddressOutput | null; +export type AggregateTypes = { + readonly avg: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; }; }; -}; -export type FieldInputTypes = { - readonly public: { - readonly Shop: { - readonly id: CodecTypes['pg/int4@1']['input']; - readonly name: CodecTypes['pg/text@1']['input']; - readonly location: AddressInput; - readonly notes: AddressInput | null; + readonly avgDecimal: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; }; }; -}; -export type StorageColumnTypes = { - readonly public: { - readonly shop: { - readonly id: CodecTypes['pg/int4@1']['output']; - readonly location: CodecTypes['pg/jsonb@1']['output']; - readonly name: CodecTypes['pg/text@1']['output']; - readonly notes: CodecTypes['pg/jsonb@1']['output'] | null; + readonly count: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8number@1'; readonly nullable: false }; + }; + readonly countBigInt: { + readonly byCodec: {}; + readonly withoutInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + readonly anyInput: { readonly output: 'pg/int8@1'; readonly nullable: false }; + }; + readonly max: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; }; }; -}; -export type StorageColumnInputTypes = { - readonly public: { - readonly shop: { - readonly id: CodecTypes['pg/int4@1']['input']; - readonly location: CodecTypes['pg/jsonb@1']['input']; - readonly name: CodecTypes['pg/text@1']['input']; - readonly notes: CodecTypes['pg/jsonb@1']['input'] | null; + readonly min: { + readonly byCodec: { + readonly 'pg/char@1': { readonly output: 'pg/char@1'; readonly nullable: true }; + readonly 'pg/date-string@1': { readonly output: 'pg/date-string@1'; readonly nullable: true }; + readonly 'pg/date-temporal@1': { + readonly output: 'pg/date-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/enum@1': { readonly output: 'pg/enum@1'; readonly nullable: true }; + readonly 'pg/float@1': { readonly output: 'pg/float@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/inet@1': { readonly output: 'pg/inet@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int2@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int4@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/text-array@1': { readonly output: 'pg/text-array@1'; readonly nullable: true }; + readonly 'pg/text@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/time-string@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { + readonly output: 'pg/time-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-date@1': { + readonly output: 'pg/timestamp-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-instant@1': { + readonly output: 'pg/timestamp-instant@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-string@1': { + readonly output: 'pg/timestamp-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamp-temporal@1': { + readonly output: 'pg/timestamp-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-date@1': { + readonly output: 'pg/timestamptz-date@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-string@1': { + readonly output: 'pg/timestamptz-string@1'; + readonly nullable: true; + }; + readonly 'pg/timestamptz-temporal@1': { + readonly output: 'pg/timestamptz-temporal@1'; + readonly nullable: true; + }; + readonly 'pg/timetz@1': { readonly output: 'pg/timetz@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'pg/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + readonly 'sql/char@1': { readonly output: 'sql/char@1'; readonly nullable: true }; + readonly 'sql/float@1': { readonly output: 'sql/float@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'sql/int@1'; readonly nullable: true }; + readonly 'sql/text@1': { readonly output: 'sql/text@1'; readonly nullable: true }; + readonly 'sql/varchar@1': { readonly output: 'pg/text@1'; readonly nullable: true }; + }; + }; + readonly sum: { + readonly byCodec: { + readonly 'pg/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/float4@1': { readonly output: 'pg/float4@1'; readonly nullable: true }; + readonly 'pg/float8@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'pg/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + readonly 'pg/interval-string@1': { + readonly output: 'pg/interval-string@1'; + readonly nullable: true; + }; + readonly 'pg/interval@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/numeric@1': { readonly output: 'pg/numeric@1'; readonly nullable: true }; + readonly 'pg/time-string@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/time-temporal@1': { readonly output: 'pg/interval@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/float@1': { readonly output: 'pg/float8@1'; readonly nullable: true }; + readonly 'sql/int@1': { readonly output: 'pg/int8number@1'; readonly nullable: true }; + }; + }; + readonly sumBigInt: { + readonly byCodec: { + readonly 'pg/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int2@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int4@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; + readonly 'pg/int8@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/int8number@1': { readonly output: 'pg/unboundedint@1'; readonly nullable: true }; + readonly 'pg/unboundedint@1': { + readonly output: 'pg/unboundedint@1'; + readonly nullable: true; + }; + readonly 'sql/int@1': { readonly output: 'pg/int8@1'; readonly nullable: true }; }; }; }; +type DefaultLiteralValue = CodecId extends keyof CodecTypes + ? Encoded extends CodecTypes[CodecId]['json'] + ? Encoded + : CodecTypes[CodecId]['json'] + : Encoded; +type ParameterizedOutputTypeFunctions = PgParameterizedOutputTypes; +type ValueObjectOutputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output' +>; +type ValueObjectInputTypes = ValueObjectTypesFromStorage< + ContractBase['storage']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input' +>; +export type AddressOutput = ValueObjectOutputTypes['public']['Address']; +export type AddressInput = ValueObjectInputTypes['public']['Address']; +type FieldTypeOverrides = {}; +export type FieldOutputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'output', + ContractBase['storage']['namespaces'] +>; +export type FieldInputTypes = FieldTypesFromDomain< + ContractBase['domain']['namespaces'], + CodecTypes, + ParameterizedOutputTypeFunctions, + FieldTypeOverrides, + 'input', + ContractBase['storage']['namespaces'] +>; +type ColumnTypeOverrides = {}; +export type StorageColumnTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'output' +>; +export type StorageColumnInputTypes = ColumnTypesFromStorage< + ContractBase['storage'], + CodecTypes, + ParameterizedOutputTypeFunctions, + ColumnTypeOverrides, + 'input' +>; + +export namespace Models { + export type public_Shop = { + id: FieldOutputTypes['public']['Shop']['id']; + location: FieldOutputTypes['public']['Shop']['location']; + name: FieldOutputTypes['public']['Shop']['name']; + notes: FieldOutputTypes['public']['Shop']['notes']; + readonly [RelationKeys]?: never; + }; +} + +export declare const models: { + public: { + Shop: Models.public_Shop; + }; +}; + export type TypeMaps = TypeMapsType< CodecTypes, QueryOperationTypes, FieldOutputTypes, FieldInputTypes, StorageColumnTypes, - StorageColumnInputTypes + StorageColumnInputTypes, + AggregateTypes >; type ContractBase = Omit< @@ -104,28 +355,28 @@ type ContractBase = Omit< readonly shop: { columns: { readonly id: { - readonly many: false; readonly dataType: 'pg/int4'; readonly codecId: 'pg/int4@1'; readonly nullable: false; - }; - readonly name: { readonly many: false; - readonly dataType: 'pg/text'; - readonly codecId: 'pg/text@1'; - readonly nullable: false; }; readonly location: { - readonly many: false; readonly dataType: 'pg/jsonb'; readonly codecId: 'pg/jsonb@1'; readonly nullable: false; + readonly many: false; }; - readonly notes: { + readonly name: { + readonly dataType: 'pg/text'; + readonly codecId: 'pg/text@1'; + readonly nullable: false; readonly many: false; + }; + readonly notes: { readonly dataType: 'pg/jsonb'; readonly codecId: 'pg/jsonb@1'; readonly nullable: true; + readonly many: false; }; }; primaryKey: { readonly columns: readonly ['id'] }; @@ -134,6 +385,24 @@ type ContractBase = Omit< foreignKeys: readonly []; }; }; + readonly valueObject: { + readonly Address: { + readonly fields: { + readonly city: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly street: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + readonly zip: { + readonly nullable: false; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + }; + }; + }; + }; }; }; }; @@ -156,17 +425,29 @@ type ContractBase = Omit< readonly nullable: false; readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/int4@1' }; }; - readonly name: { + readonly location: { readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; - readonly location: { + readonly name: { readonly nullable: false; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; }; readonly notes: { readonly nullable: true; - readonly type: { readonly kind: 'valueObject'; readonly name: 'Address' }; + readonly type: { + readonly kind: 'valueObject'; + readonly plane: 'storage'; + readonly namespaceId: 'public'; + readonly entityKind: 'valueObject'; + readonly entityName: 'Address'; + }; }; }; readonly relations: Record; @@ -175,37 +456,21 @@ type ContractBase = Omit< readonly namespaceId: 'public'; readonly fields: { readonly id: { readonly column: 'id' }; - readonly name: { readonly column: 'name' }; readonly location: { readonly column: 'location' }; + readonly name: { readonly column: 'name' }; readonly notes: { readonly column: 'notes' }; }; }; }; }; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; }; }; }; readonly capabilities: { readonly postgres: { readonly distinctOn: true; + readonly forKeyShare: true; + readonly forNoKeyUpdate: true; readonly jsonAgg: true; readonly lateral: true; readonly limit: true; @@ -213,33 +478,23 @@ type ContractBase = Omit< readonly returning: true; }; readonly sql: { + readonly checkConstraint: true; readonly defaultInInsert: true; readonly enums: true; + readonly forShare: true; + readonly forUpdate: true; + readonly insertOnConflictSkip: true; + readonly insertOnConflictWithoutTarget: true; readonly lateral: true; + readonly lockNowait: true; + readonly lockOf: true; + readonly lockSkipLocked: true; readonly returning: true; readonly scalarList: true; }; }; readonly extensions: {}; readonly meta: {}; - readonly valueObjects: { - readonly Address: { - readonly fields: { - readonly street: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly city: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - readonly zip: { - readonly nullable: false; - readonly type: { readonly kind: 'scalar'; readonly codecId: 'pg/text@1' }; - }; - }; - }; - }; readonly profileHash: ProfileHash; }; diff --git a/test/integration/test/value-objects/fixtures/generated/sql-contract.json b/test/integration/test/value-objects/fixtures/generated/sql-contract.json index 92a473b18cad..f61ae8700f77 100644 --- a/test/integration/test/value-objects/fixtures/generated/sql-contract.json +++ b/test/integration/test/value-objects/fixtures/generated/sql-contract.json @@ -16,7 +16,6 @@ "Shop": { "fields": { "id": { - "many": false, "nullable": false, "type": { "codecId": "pg/int4@1", @@ -24,15 +23,16 @@ } }, "location": { - "many": false, "nullable": false, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "public", + "plane": "storage" } }, "name": { - "many": false, "nullable": false, "type": { "codecId": "pg/text@1", @@ -40,11 +40,13 @@ } }, "notes": { - "many": false, "nullable": true, "type": { + "entityKind": "valueObject", + "entityName": "Address", "kind": "valueObject", - "name": "Address" + "namespaceId": "public", + "plane": "storage" } } }, @@ -68,36 +70,6 @@ "table": "shop" } } - }, - "valueObjects": { - "Address": { - "fields": { - "city": { - "many": false, - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "street": { - "many": false, - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - }, - "zip": { - "many": false, - "nullable": false, - "type": { - "codecId": "pg/text@1", - "kind": "scalar" - } - } - } - } } } } @@ -108,6 +80,18 @@ "entries": { "table": { "shop": { + "checks": [ + { + "expression": "jsonb_path_match(\"location\", 'strict $.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"zip\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"street\" && @.key != \"zip\"))') IS TRUE", + "name": "shop_location_shape_9799c974", + "prefix": "shop_location_shape" + }, + { + "expression": "\"notes\" IS NULL OR jsonb_path_match(\"notes\", 'strict $.type() == \"null\" || ($.type() == \"object\" && exists($.keyvalue() ? (@.key == \"city\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"street\" && (@.value.type() == \"string\"))) && exists($.keyvalue() ? (@.key == \"zip\" && (@.value.type() == \"string\"))) && !exists($.keyvalue() ? (@.key != \"city\" && @.key != \"street\" && @.key != \"zip\")))') IS TRUE", + "name": "shop_notes_shape_5554921a", + "prefix": "shop_notes_shape" + } + ], "columns": { "id": { "codecId": "pg/int4@1", @@ -139,17 +123,46 @@ }, "uniques": [] } + }, + "valueObject": { + "Address": { + "fields": { + "city": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "street": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + }, + "zip": { + "nullable": false, + "type": { + "codecId": "pg/text@1", + "kind": "scalar" + } + } + } + } } }, "id": "public", "kind": "postgres-schema" } }, - "storageHash": "8334dda9271ee13d092ca25003aed6d6507dbad3e83de89f994a48661d16bbf4" + "storageHash": "d1f566293d77a0d2d3dc4b1fbcec129b2a6a25ac509570d6f757dc83665939b4" }, "capabilities": { "postgres": { "distinctOn": true, + "forKeyShare": true, + "forNoKeyUpdate": true, "jsonAgg": true, "lateral": true, "limit": true, @@ -157,9 +170,17 @@ "returning": true }, "sql": { + "checkConstraint": true, "defaultInInsert": true, "enums": true, + "forShare": true, + "forUpdate": true, + "insertOnConflictSkip": true, + "insertOnConflictWithoutTarget": true, "lateral": true, + "lockNowait": true, + "lockOf": true, + "lockSkipLocked": true, "returning": true, "scalarList": true } diff --git a/test/integration/test/value-objects/fixtures/schema.prisma b/test/integration/test/value-objects/fixtures/schema.prisma index 543086cd05f0..4ef870f3ab00 100644 --- a/test/integration/test/value-objects/fixtures/schema.prisma +++ b/test/integration/test/value-objects/fixtures/schema.prisma @@ -1,3 +1,5 @@ +// use prisma-8 + model Shop { id Int @id name String diff --git a/test/integration/test/value-objects/value-objects.integration.test.ts b/test/integration/test/value-objects/value-objects.integration.test.ts index 798e0b23ad56..db1a7a417e5f 100644 --- a/test/integration/test/value-objects/value-objects.integration.test.ts +++ b/test/integration/test/value-objects/value-objects.integration.test.ts @@ -3,8 +3,8 @@ import { type ContractField, type ContractValueObject, domainModelsAtDefaultNamespace, - domainValueObjectsAtDefaultNamespace, UNBOUND_DOMAIN_NAMESPACE_ID, + valueObjectsWithCoordinates, } from '@internal/contract/types'; import { MongoContractSerializer } from '@internal/family-mongo/ir'; import type { AuthoringTypeConstructorDescriptor } from '@internal/framework-components/authoring'; @@ -194,7 +194,22 @@ function modelsAtDefaultNamespace(contract: Contract) { } function valueObjectsAtDefaultNamespace(contract: Contract) { - return domainValueObjectsAtDefaultNamespace(contract.domain); + return Object.fromEntries( + valueObjectsWithCoordinates(contract.storage).map(({ coordinate, valueObject }) => [ + coordinate.entityName, + valueObject, + ]), + ); +} + +function valueObjectRef(namespaceId: string, entityName: string) { + return { + kind: 'valueObject', + plane: 'storage', + namespaceId, + entityKind: 'valueObject', + entityName, + }; } describeWithMongoDB('value objects: end-to-end Mongo', (ctx) => { @@ -205,13 +220,14 @@ describeWithMongoDB('value objects: end-to-end Mongo', (ctx) => { const contract = result.value; const unboundDomain = contract.domain.namespaces[UNBOUND_DOMAIN_NAMESPACE_ID]!; - expect(unboundDomain.valueObjects).toBeDefined(); - expect(unboundDomain.valueObjects!['Address']).toBeDefined(); + expect(valueObjectsAtDefaultNamespace(contract)['Address']).toBeDefined(); const userFields = unboundDomain.models['User']!.fields as Record; expect(userFields['homeAddress']!.type.kind).toBe('valueObject'); - const validated = { contract: new MongoContractSerializer().deserializeContract(contract) }; + const validated = { + contract: new MongoContractSerializer().deserializeContract(contract), + }; const orm = mongoOrm({ contract: validated.contract, @@ -247,7 +263,9 @@ describeWithMongoDB('value objects: end-to-end Mongo', (ctx) => { const result = interpretMongoPsl(mongoPsl); if (!result.ok) throw new Error(`Interpretation failed: ${result.failure.summary}`); - const validated = { contract: new MongoContractSerializer().deserializeContract(result.value) }; + const validated = { + contract: new MongoContractSerializer().deserializeContract(result.value), + }; const orm = mongoOrm({ contract: validated.contract, executor: ctx.runtime, @@ -290,7 +308,9 @@ type Address { const result = interpretMongoPsl(schema); if (!result.ok) throw new Error(`Interpretation failed: ${result.failure.summary}`); - const validated = { contract: new MongoContractSerializer().deserializeContract(result.value) }; + const validated = { + contract: new MongoContractSerializer().deserializeContract(result.value), + }; const orm = mongoOrm({ contract: validated.contract, executor: ctx.runtime, @@ -315,10 +335,8 @@ describe('value objects: end-to-end SQL pipeline', () => { const contract = result.value; const publicDomain = contract.domain.namespaces['public']!; - expect(publicDomain.valueObjects).toBeDefined(); - expect(publicDomain.valueObjects!['Address']).toBeDefined(); - - const addressVo = publicDomain.valueObjects!['Address'] as ContractValueObject; + const addressVo = valueObjectsAtDefaultNamespace(contract)['Address'] as ContractValueObject; + expect(addressVo).toBeDefined(); const voFields = addressVo.fields; expect(voFields['street']).toBeDefined(); expect(voFields['city']).toBeDefined(); @@ -326,7 +344,7 @@ describe('value objects: end-to-end SQL pipeline', () => { const userFields = publicDomain.models['User']!.fields as Record; const homeAddressField = userFields['homeAddress']!; - expect(homeAddressField.type).toEqual({ kind: 'valueObject', name: 'Address' }); + expect(homeAddressField.type).toEqual(valueObjectRef('public', 'Address')); expect(homeAddressField.nullable).toBe(false); const storage = contract.storage as unknown as { @@ -396,8 +414,8 @@ type Metadata { ContractField >; - expect(mongoItemFields['meta']!.type).toEqual({ kind: 'valueObject', name: 'Metadata' }); - expect(sqlItemFields['meta']!.type).toEqual({ kind: 'valueObject', name: 'Metadata' }); + expect(mongoItemFields['meta']!.type).toEqual(valueObjectRef('__unbound__', 'Metadata')); + expect(sqlItemFields['meta']!.type).toEqual(valueObjectRef('public', 'Metadata')); }); it('nested value objects produce consistent definitions across families', () => { @@ -447,7 +465,7 @@ type Address { const sqlShipping = valueObjectsAtDefaultNamespace(sqlResult.value)!['ShippingInfo']! .fields as Record; - expect(mongoShipping['address']!.type).toEqual({ kind: 'valueObject', name: 'Address' }); - expect(sqlShipping['address']!.type).toEqual({ kind: 'valueObject', name: 'Address' }); + expect(mongoShipping['address']!.type).toEqual(valueObjectRef('__unbound__', 'Address')); + expect(sqlShipping['address']!.type).toEqual(valueObjectRef('public', 'Address')); }); }); diff --git a/test/utils/src/application-domain-of.ts b/test/utils/src/application-domain-of.ts index 0bdbbd4315c6..5ed235b42eca 100644 --- a/test/utils/src/application-domain-of.ts +++ b/test/utils/src/application-domain-of.ts @@ -1,23 +1,13 @@ import { type ApplicationDomain, type ContractModelBase, - type ContractValueObject, UNBOUND_DOMAIN_NAMESPACE_ID, } from '@internal/contract/types'; export function applicationDomainOf(params: { readonly models?: Record; - readonly valueObjects?: Record; readonly namespaceId?: string; }): ApplicationDomain { const namespaceId = params.namespaceId ?? UNBOUND_DOMAIN_NAMESPACE_ID; - const models = params.models ?? {}; - return { - namespaces: { - [namespaceId]: { - models, - ...(params.valueObjects !== undefined ? { valueObjects: params.valueObjects } : {}), - }, - }, - }; + return { namespaces: { [namespaceId]: { models: params.models ?? {} } } }; } diff --git a/test/utils/src/contract-factories.ts b/test/utils/src/contract-factories.ts index 8ff6d11c8ab8..b3ea22d862f9 100644 --- a/test/utils/src/contract-factories.ts +++ b/test/utils/src/contract-factories.ts @@ -26,6 +26,7 @@ type ContractOverrides = { /** Named domain namespaces; replaces the single unbound namespace `models` fills. */ namespaces?: Record; storage?: Omit; + /** Value objects, written to `entries.valueObject` of the storage's unbound namespace. */ valueObjects?: Record; enum?: Record; capabilities?: Record>; @@ -52,6 +53,27 @@ const DEFAULT_SQL_STORAGE = { }, } as const; +type StorageWithoutHash = Omit; + +function withValueObjects( + storage: StorageWithoutHash, + valueObjects: Record | undefined, +): StorageWithoutHash { + if (valueObjects === undefined) return storage; + const namespace = storage.namespaces[UNBOUND_NAMESPACE_ID]; + return { + ...storage, + namespaces: { + ...storage.namespaces, + [UNBOUND_NAMESPACE_ID]: { + id: UNBOUND_NAMESPACE_ID, + ...namespace, + entries: { ...namespace?.entries, valueObject: valueObjects }, + }, + }, + }; +} + export function createContract( overrides: ContractOverrides = {}, ): Contract { @@ -59,7 +81,10 @@ export function createContract( const targetFamily = overrides.targetFamily ?? 'sql'; const capabilities = overrides.capabilities ?? {}; - const rawStorage = overrides.storage ?? DEFAULT_FRAMEWORK_STORAGE; + const rawStorage = withValueObjects( + overrides.storage ?? DEFAULT_FRAMEWORK_STORAGE, + overrides.valueObjects, + ); const storageHash = computeStorageHash({ target, @@ -85,7 +110,6 @@ export function createContract( namespaces: overrides.namespaces ?? { [UNBOUND_DOMAIN_NAMESPACE_ID]: { models: overrides.models ?? {}, - ...ifDefined('valueObjects', overrides.valueObjects), ...ifDefined('enum', overrides.enum), }, }, diff --git a/test/utils/test/contract-factories.test.ts b/test/utils/test/contract-factories.test.ts new file mode 100644 index 000000000000..63b3db0786be --- /dev/null +++ b/test/utils/test/contract-factories.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { applicationDomainOf } from '../src/application-domain-of'; +import { createContract } from '../src/contract-factories'; + +const address = { + fields: { city: { nullable: false, type: { kind: 'scalar', codecId: 'pg/text@1' } } }, +} as const; + +describe('createContract with value objects', () => { + it('puts them in the storage plane of the unbound namespace, next to the given entries', () => { + const contract = createContract({ + storage: { namespaces: { __unbound__: { id: '__unbound__', entries: { table: {} } } } }, + valueObjects: { Address: address }, + }); + expect({ + entries: contract.storage.namespaces['__unbound__']?.entries, + domain: contract.domain.namespaces['__unbound__'], + }).toEqual({ + entries: { table: {}, valueObject: { Address: address } }, + domain: { models: {} }, + }); + }); + + it('covers them in the storage hash', () => { + expect(createContract({ valueObjects: { Address: address } }).storage.storageHash).not.toBe( + createContract({}).storage.storageHash, + ); + }); +}); + +describe('applicationDomainOf', () => { + it('holds models only', () => { + expect(applicationDomainOf({ models: {} })).toEqual({ + namespaces: { __unbound__: { models: {} } }, + }); + }); +}); diff --git a/upgrade-instructions/pending/value-objects-in-storage/app/instructions.md b/upgrade-instructions/pending/value-objects-in-storage/app/instructions.md new file mode 100644 index 000000000000..6ee3840965cd --- /dev/null +++ b/upgrade-instructions/pending/value-objects-in-storage/app/instructions.md @@ -0,0 +1,102 @@ +--- +changes: + - id: value-objects-in-storage + summary: | + A contract keeps its value objects in storage, at `storage.namespaces..entries.valueObject`, and a field holding one names it by its storage coordinate. On SQL, a discriminator column names a value set, `.`, of its variants' values. A `contract.json` that keeps `valueObjects` in the domain plane is refused with `CONTRACT.VALIDATION_FAILED`, saying to re-emit it. Run the colocated script on the project: it rewrites every contract and snapshot, renames the snapshot directories to their new storage hashes, and rewrites the migrations, refs, `migration.ts` files and `contract.d.ts` files that name them. Then sign each database and re-emit the contract. + detection: + glob: "**/*.json" + matches: + - '"valueObjects"\s*:' + script: ./scripts/value-objects-in-storage/value-objects-in-storage.ts + - id: value-object-types-from-storage + summary: | + `ValueObjectTypesFromDomain`, exported by `@prisma/orm-mongo/contract/types`, is now `ValueObjectTypesFromStorage` and takes the contract's storage namespaces. A committed `contract.d.ts` that imports it compiles again once it is re-emitted. Code of your own that imports it changes the name and passes `Contract['storage']['namespaces']`. + detection: + glob: "**/*.{ts,mts,cts,tsx}" + matches: + - '\bValueObjectTypesFromDomain\b' + - id: postgres-value-object-checks + summary: | + On PostgreSQL, the contract derives a check for every value object column and every discriminator column of a managed table. The next `prisma migration plan` adds them, and `prisma db migrate` fails on a row that does not fit, including a row whose value object has a key the value object does not declare, which the SQL ORM never refused. Find such rows before you apply the migration. + - id: discriminator-typed-by-variant-values + summary: | + A SQL discriminator field is typed by its variants' values, such as `'bug' | 'feature'`, instead of `string`. Code that assigns such a field to a `string` still compiles; a type assertion that expects `string` changes to the union. +--- + +# Value objects are storage entities + +## `value-objects-in-storage` + +Before you upgrade, apply every pending migration to each database (development, staging, production) with the release you are upgrading from, so each database is at the last migration. Then commit your work, so the script's changes can be reviewed and undone with git. Then upgrade the Prisma packages to this release. Run the script before you re-emit the contract: `prisma db sign` must see the contract the database was created from before a re-emit adds the new checks. Run it from the project root: + +```sh +node /scripts/value-objects-in-storage/value-objects-in-storage.ts +``` + +Node 24 or later runs the TypeScript script directly; it needs no `tsx`. It reads and writes files only and needs no database. It rewrites every `*.json` file under the root that parses as a SQL or MongoDB contract keeping value objects in the domain plane, skipping `node_modules`, `dist`, `build` and directories whose name starts with a dot. In each contract it: + +- moves each value object from `domain.namespaces..valueObjects` to `storage.namespaces..entries.valueObject`; +- writes each field that holds a value object as `{ kind: 'valueObject', plane: 'storage', namespaceId, entityKind: 'valueObject', entityName }` instead of `{ kind: 'valueObject', name }`; +- points a value object member typed by an enum at the enum's storage value set (`plane: 'storage'`, `entityKind: 'valueSet'`); +- on SQL, writes the value set of each discriminator, keyed `.` in the base table's namespace and holding the variants' values, sorted, and points the discriminator column at it. A column an enum types keeps the enum's value set. + +Storage now holds the value objects, so each contract it rewrites has a new storage hash. The script renames each snapshot directory to the new hash and rewrites every `migration.json` (with its `migrationHash`), ref, `migration.ts` and `contract.d.ts` that names an old one. It does not add the PostgreSQL checks described in `postgres-value-object-checks`: a snapshot records what its migrations created, and none created those checks. + +When it finishes, it prints how many files it rewrote, how many snapshot directories it renamed, and each storage hash it replaced (` -> `). A project already in the new form is left unchanged, and the script says that nothing changed. It changes no file and exits 1 when a file it must read is not valid JSON, when a contract's stored hash does not match its content, when a field holds a value object the contract does not declare, when a member's enum has no storage value set, when a discriminator's value set name is already taken by an enum, when a renamed snapshot directory already exists with different content, or when it cannot read a directory under the root. If it stops on an error partway, for example on a full disk, run it again: it finishes the upgrade. + +The script rewrites a snapshot's `contract.d.ts` for the moved value objects. In every `contract.d.ts` it finds, including those of contracts without value objects, it also passes the storage namespaces to `FieldTypesFromDomain`, which now requires them. It does not add the types of the discriminator value sets. Nothing reads them from a snapshot: a `migration.ts` that imports a snapshot's `contract.d.ts` compiles without them, and `prisma contract emit` writes them for the emitted contract. + +Then: + +1. Sign each database (development, staging, production) with the rewritten contract, before you re-emit: `prisma db sign --db "$DATABASE_URL"`. Its marker still holds the old storage hash, so until it is signed `prisma db migrate` refuses with `MIGRATION.MARKER_MISMATCH` and `prisma db verify` with `CONTRACT.MARKER_MISMATCH`. This works for a database at the last migration. It also points your local `db` ref at the rewritten contract, writing the ref if the project had none; `prisma migration plan` starts from that ref. +2. Re-emit the contract: `prisma contract emit`. +3. On PostgreSQL, plan the migration that adds the new checks with `prisma migration plan`, and apply it to each database with `prisma db migrate --advance-ref db --db "$DATABASE_URL"`, as `postgres-value-object-checks` describes. `--advance-ref db` moves the `db` ref to the new contract. Without it the ref stays at the contract of step 1, and the next `prisma migration plan` starts from there and writes a second migration that adds the same checks. On MongoDB there is nothing to plan: the collection validators do not change. + +A database you could not migrate before the upgrade is behind the last migration, and `prisma db sign --db "$DATABASE_URL"` fails on it with `CONTRACT.SCHEMA_VERIFICATION_FAILED`, because it signs against the last contract. Do not follow its advice to run `prisma db update`: that skips the pending migrations. Instead, sign the database at the new hash of the hash its marker holds, then migrate it: + +```sh +prisma db sign --contract --no-advance-ref --db "$DATABASE_URL" +prisma db migrate --db "$DATABASE_URL" +prisma db verify --strict --db "$DATABASE_URL" +``` + +Find the new hash in the script's output: the marker holds the `` hash of one ` -> ` line. `--no-advance-ref` leaves your local `db` ref where it is. + +## `value-object-types-from-storage` + +```ts +// before +import type { ValueObjectTypesFromDomain } from '@prisma/orm-mongo/contract/types'; +type Outputs = ValueObjectTypesFromDomain; + +// after +import type { ValueObjectTypesFromStorage } from '@prisma/orm-mongo/contract/types'; +type Outputs = ValueObjectTypesFromStorage; +``` + +## `postgres-value-object-checks` + +For each column that holds a value object, the contract derives a check named `
__shape_`. It accepts a JSON object with every required member, each of its JSON type, no other key, and for a list column an array of such objects; a nullable column also accepts SQL `NULL` and JSON `null`. For each discriminator column it derives `
__check_`, which accepts only the variants' values. A table whose control policy is not managed gets neither check. + +After you re-emit the contract, `prisma migration plan` writes a migration that adds the checks. It starts from the `db` ref that `prisma db sign` wrote. Its DDL preview prints each check's expression. Before you run `prisma db migrate`, find the rows that would fail by selecting the rows where the expression is not true, for example: + +```sql +SELECT id, address FROM "public"."user" +WHERE NOT ("address" IS NULL OR jsonb_path_match("address", 'strict … ') IS TRUE); + +SELECT id, type FROM "public"."task" WHERE NOT ("type" IN ('bug', 'feature')); +``` + +Adding a check takes a lock on the table while Postgres reads every row to check it. Any later change to a value object renames the check, so the check is dropped and added again, and the rows are read again. Apply the migration when the table can wait for that read. + +Copy the expression from the preview between `CHECK (` and the closing `)`. Fix or delete each row the query returns, for example by removing the extra keys with `UPDATE "public"."user" SET address = address - 'extra' WHERE address ? 'extra'`, then apply the migration with `prisma db migrate --advance-ref db`. A migration that fails on a row changes nothing, and the error names the check, its table and column, and the query that finds the rows; fix the rows and run `prisma db migrate --advance-ref db` again. + +## `discriminator-typed-by-variant-values` + +```ts +// before +expectTypeOf(task.type).toEqualTypeOf(); + +// after +expectTypeOf(task.type).toEqualTypeOf<'bug' | 'feature'>(); +``` diff --git a/upgrade-instructions/pending/value-objects-in-storage/app/scripts/value-objects-in-storage/value-objects-in-storage.ts b/upgrade-instructions/pending/value-objects-in-storage/app/scripts/value-objects-in-storage/value-objects-in-storage.ts new file mode 100644 index 000000000000..0ba2a3779f9e --- /dev/null +++ b/upgrade-instructions/pending/value-objects-in-storage/app/scripts/value-objects-in-storage/value-objects-in-storage.ts @@ -0,0 +1,1184 @@ +#!/usr/bin/env node +/** + * Moves the value objects of every SQL and MongoDB contract from the domain plane into storage. Contracts emitted before this release keep value objects at `domain.namespaces..valueObjects` and type a field holding one as `{ kind: 'valueObject', name }`; this release keeps them at `storage.namespaces..entries.valueObject` and types the field by the value object's storage coordinate. In each contract the script moves every value object into the storage namespace of the same id, points every field at it by coordinate, and points a member typed by an enum at the enum's storage value set. In a SQL contract it also writes the value set of each discriminator, keyed `.` and holding the variants' values, and points the discriminator column at it, unless an enum already types that column. It makes the same changes in `contract.d.ts`. + * + * Storage now holds the value objects, so each rewritten contract has a new storage hash. The script renames each snapshot directory to its new hash and rewrites every migration, ref, `migration.ts` and `contract.d.ts` that names an old hash, and each migration's `migrationHash`. It does not add the checks PostgreSQL now derives for value object and discriminator columns: a snapshot records what its migrations created, and they created no such check. The next `prisma migration plan` adds them. + * + * Usage: node value-objects-in-storage.ts [project-root] + * + * The project root defaults to the working directory. The script reads and writes files only. It needs no database, network or configured stack, and a project whose contracts keep no value object in the domain plane is left unchanged. When it stops (a file it must read is not valid JSON, a contract whose stored hash does not match its content, a field naming a value object the contract does not declare, a value object whose namespace storage does not have, a member enum without a storage value set, a discriminator value set that already exists, a snapshot directory that already exists with different content, or a directory it cannot read) it changes no file, prints one line per case and exits 1. When it finishes it prints how many files and snapshot directories it changed and each storage hash it replaced. + * + * The storage hash and migration hash rules are copied from `@internal/contract`, `@internal/sql-contract`, `@internal/mongo-contract` and `@internal/migration-tools` because a project's strict `node_modules` does not expose them. + */ +import { createHash } from 'node:crypto'; +import { + chmodSync, + type Dirent, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + realpathSync, + renameSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; +import { basename, dirname, join, relative, resolve, sep } from 'node:path'; + +type Json = null | boolean | number | string | Json[] | JsonRecord; +interface JsonRecord { + [key: string]: Json; +} + +type Family = 'sql' | 'mongo'; + +const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', 'build']); +const HASH = /^[0-9a-f]{64}$/; +const TEMPORARY_SUFFIX = '.value-objects-in-storage-tmp'; +const BYTE_ORDER_MARK = '\uFEFF'; + +function isRecord(value: unknown): value is JsonRecord { + if (typeof value !== 'object' || value === null || Array.isArray(value)) return false; + const prototype: unknown = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; +} + +function withoutByteOrderMark(text: string): string { + return text.startsWith(BYTE_ORDER_MARK) ? text.slice(BYTE_ORDER_MARK.length) : text; +} + +function parseJson(text: string): Json | undefined { + try { + const value: Json = JSON.parse(withoutByteOrderMark(text)); + return value; + } catch { + return undefined; + } +} + +function sha256(text: string): string { + return createHash('sha256').update(text).digest('hex'); +} + +function compareCodeUnits(a: string, b: string): number { + return a < b ? -1 : a > b ? 1 : 0; +} + +function sortKeys(value: Json): Json { + if (Array.isArray(value)) return value.map(sortKeys); + if (!isRecord(value)) return value; + const sorted: JsonRecord = {}; + for (const key of Object.keys(value).sort(compareCodeUnits)) { + const child = value[key]; + if (child !== undefined) sorted[key] = sortKeys(child); + } + return sorted; +} + +function canonicalizeJson(value: Json): string { + return JSON.stringify(sortKeys(value)); +} + +type PathSegment = string | readonly string[]; + +function matchesPath(path: readonly string[], pattern: readonly PathSegment[]): boolean { + if (path.length !== pattern.length) return false; + return pattern.every((segment, index) => { + const value = path[index]; + if (value === undefined) return false; + if (segment === '*') return true; + return typeof segment === 'string' ? value === segment : segment.includes(value); + }); +} + +const SQL_PRESERVED_EMPTY: readonly (readonly PathSegment[])[] = [ + ['storage', 'namespaces', '*', 'entries', 'table'], + ['storage', 'namespaces', '*', 'entries', 'table', '*'], + ['storage', 'namespaces', '*', 'entries', 'table', '*', ['uniques', 'indexes', 'foreignKeys']], + ['storage', 'namespaces', '*', 'entries', 'table', '*', 'indexes', 'unique'], + ['storage', 'namespaces', '*', 'entries', 'table', '*', 'columns', '*', 'default', 'value'], +]; +const COLUMN_DEFAULT_VALUE: readonly PathSegment[] = [ + 'storage', + 'namespaces', + '*', + 'entries', + 'table', + '*', + 'columns', + '*', + 'default', + 'value', +]; +const MONGO_PRESERVED_EMPTY: readonly (readonly PathSegment[])[] = [ + ['storage', 'namespaces', '*', 'entries', 'collection'], + ['storage', 'namespaces', '*', 'entries', 'collection', '*'], +]; +const FRAMEWORK_PRESERVED_EMPTY: readonly (readonly PathSegment[])[] = [ + ['domain', 'namespaces'], + ['domain', 'namespaces', '*'], + ['domain', 'namespaces', '*', 'models'], + ['domain', 'namespaces', '*', 'models', '*', 'relations'], + ['domain', 'namespaces', '*', 'models', '*', 'storage'], + ['storage', 'namespaces'], + ['storage', 'namespaces', '*', 'entries'], + ['roots'], + ['extensions'], + ['extensions', '*'], + ['capabilities'], + ['meta'], + ['execution', 'mutations', 'defaults'], +]; +const TOP_LEVEL_ORDER = [ + 'schemaVersion', + 'canonicalVersion', + 'targetFamily', + 'target', + 'profileHash', + 'roots', + 'domain', + 'storage', + 'execution', + 'capabilities', + 'extensions', + 'defaultControlPolicy', + 'meta', +]; + +function isDefaultValue(value: Json): boolean { + if (value === false) return true; + if (Array.isArray(value)) return value.length === 0; + return isRecord(value) && Object.keys(value).length === 0; +} + +function isFamilyPreservedEmpty(family: Family, path: readonly string[]): boolean { + if (family === 'mongo') { + return ( + path.at(-1) === 'additionalProperties' || + (path.includes('jsonSchema') && (path.at(-2) === 'properties' || path.at(-1) === 'items')) || + MONGO_PRESERVED_EMPTY.some((pattern) => matchesPath(path, pattern)) + ); + } + if ( + path.length >= COLUMN_DEFAULT_VALUE.length && + matchesPath(path.slice(0, COLUMN_DEFAULT_VALUE.length), COLUMN_DEFAULT_VALUE) + ) + return true; + return SQL_PRESERVED_EMPTY.some((pattern) => matchesPath(path, pattern)); +} + +function isPreservedEmpty(family: Family, path: readonly string[], key: string): boolean { + if (key === 'nullable') return true; + if (key === 'elementNullable' && path.at(-2) === 'many') return true; + if (FRAMEWORK_PRESERVED_EMPTY.some((pattern) => matchesPath(path, pattern))) return true; + return isFamilyPreservedEmpty(family, path); +} + +function omitDefaults(family: Family, value: Json, path: readonly string[]): Json { + if (Array.isArray(value)) return value.map((item) => omitDefaults(family, item, path)); + if (!isRecord(value)) return value; + const result: JsonRecord = {}; + for (const [key, child] of Object.entries(value)) { + const childPath = [...path, key]; + if (key === '_generated') continue; + if (key === 'generated' && child === false) continue; + if ((key === 'onDelete' || key === 'onUpdate') && child === 'noAction') continue; + if (isDefaultValue(child) && !isPreservedEmpty(family, childPath, key)) continue; + result[key] = omitDefaults(family, child, childPath); + } + return result; +} + +function compareByName(a: Json, b: Json): number { + const nameA = isRecord(a) && typeof a['name'] === 'string' ? a['name'] : ''; + const nameB = isRecord(b) && typeof b['name'] === 'string' ? b['name'] : ''; + return compareCodeUnits(nameA, nameB); +} + +function sortTableArrays(storage: Json): Json { + if (!isRecord(storage)) return storage; + const namespaces = storage['namespaces']; + if (!isRecord(namespaces)) return storage; + const sortedNamespaces: JsonRecord = {}; + for (const [namespaceId, namespace] of Object.entries(namespaces)) { + const entries = isRecord(namespace) ? namespace['entries'] : undefined; + const tables = isRecord(entries) ? entries['table'] : undefined; + if (!isRecord(namespace) || !isRecord(entries) || !isRecord(tables)) { + sortedNamespaces[namespaceId] = namespace; + continue; + } + const sortedTables: JsonRecord = {}; + for (const [tableName, table] of Object.entries(tables)) { + if (!isRecord(table)) { + sortedTables[tableName] = table; + continue; + } + const sortedTable: JsonRecord = { ...table }; + for (const key of ['checks', 'indexes', 'uniques']) { + const list = table[key]; + if (Array.isArray(list)) sortedTable[key] = [...list].sort(compareByName); + } + sortedTables[tableName] = sortedTable; + } + sortedNamespaces[namespaceId] = { ...namespace, entries: { ...entries, table: sortedTables } }; + } + return { ...storage, namespaces: sortedNamespaces }; +} + +function orderTopLevel(value: JsonRecord): JsonRecord { + const ordered: JsonRecord = {}; + const remaining = new Set(Object.keys(value)); + for (const key of TOP_LEVEL_ORDER) { + const child = value[key]; + if (child !== undefined) { + ordered[key] = child; + remaining.delete(key); + } + } + for (const key of [...remaining].sort(compareCodeUnits)) { + const child = value[key]; + if (child !== undefined) ordered[key] = child; + } + return ordered; +} + +function omitNamespaceKinds(storage: JsonRecord): JsonRecord { + const namespaces = storage['namespaces']; + if (!isRecord(namespaces)) return storage; + const stripped: JsonRecord = {}; + for (const [namespaceId, namespace] of Object.entries(namespaces)) { + if (isRecord(namespace)) { + const { kind: _kind, ...rest } = namespace; + stripped[namespaceId] = rest; + } else { + stripped[namespaceId] = namespace; + } + } + return { ...storage, namespaces: stripped }; +} + +function computeStorageHash(family: Family, contract: JsonRecord): string { + const storage = isRecord(contract['storage']) ? contract['storage'] : {}; + const { storageHash: _published, ...withoutHash } = storage; + const hashed: JsonRecord = { + schemaVersion: '1', + targetFamily: typeof contract['targetFamily'] === 'string' ? contract['targetFamily'] : '', + target: typeof contract['target'] === 'string' ? contract['target'] : '', + profileHash: '', + roots: {}, + domain: { namespaces: {} }, + storage: omitNamespaceKinds(withoutHash), + extensions: {}, + capabilities: {}, + meta: {}, + }; + const withoutDefaults = omitDefaults(family, hashed, []); + if (!isRecord(withoutDefaults)) return ''; + const withSortedStorage = + family === 'sql' + ? { ...withoutDefaults, storage: sortTableArrays(withoutDefaults['storage'] ?? {}) } + : withoutDefaults; + const sorted = sortKeys(withSortedStorage); + return isRecord(sorted) ? sha256(JSON.stringify(orderTopLevel(sorted), null, 2)) : ''; +} + +function computeMigrationHash(metadata: JsonRecord, ops: Json): string { + const { migrationHash: _migrationHash, ...withoutHash } = metadata; + const parts = [canonicalizeJson(withoutHash), canonicalizeJson(ops)].map(sha256); + return sha256(canonicalizeJson(parts)); +} + +interface ProjectFiles { + readonly json: readonly string[]; + readonly migrationJson: readonly string[]; + readonly refs: readonly string[]; + readonly migrationTs: readonly string[]; + readonly leftovers: readonly string[]; + readonly unreadable: readonly { readonly dir: string; readonly code: string }[]; +} + +function isDirectory(path: string): boolean { + try { + return statSync(path).isDirectory(); + } catch { + return false; + } +} + +function isFile(path: string): boolean { + try { + return statSync(path).isFile(); + } catch { + return false; + } +} + +/** A file, or a link to one: the script reads and writes through links to files. */ +function isFileEntry(dir: string, entry: Dirent): boolean { + return entry.isFile() || (entry.isSymbolicLink() && isFile(join(dir, entry.name))); +} + +/** A file the script reads, by the path it was reached through: its own path rather than a link's. */ +interface FoundFile { + readonly path: string; + readonly linked: boolean; + readonly inMigrationPackage: boolean; +} + +function listFiles(root: string): ProjectFiles { + const leftovers: string[] = []; + const visited = new Set(); + const found = new Map(); + const unreadable: { dir: string; code: string }[] = []; + const visit = (dir: string): void => { + const realDir = realpathSync(dir); + if (visited.has(realDir)) return; + visited.add(realDir); + const listed = listDirectory(dir); + if (!listed.ok) { + unreadable.push({ dir, code: listed.code }); + return; + } + const entries = listed.entries.sort((a, b) => compareCodeUnits(a.name, b.name)); + const inMigrationPackage = entries.some( + (entry) => entry.name === 'migration.json' && isFileEntry(dir, entry), + ); + for (const entry of entries) { + const path = join(dir, entry.name); + if (entry.name.endsWith(TEMPORARY_SUFFIX)) { + leftovers.push(path); + continue; + } + if (entry.isDirectory() || (entry.isSymbolicLink() && isDirectory(path))) { + if (!SKIPPED_DIRECTORIES.has(entry.name) && !entry.name.startsWith('.')) visit(path); + continue; + } + if (!isFileEntry(dir, entry)) continue; + const realFile = realpathSync(path); + const linked = entry.isSymbolicLink(); + const known = found.get(realFile); + if (known === undefined || (known.linked && !linked)) + found.set(realFile, { path, linked, inMigrationPackage }); + } + }; + visit(root); + const files = [...found.values()]; + const named = (test: (file: FoundFile, name: string) => boolean): string[] => + files.filter((file) => test(file, basename(file.path))).map((file) => file.path); + return { + json: named((_file, name) => name.endsWith('.json')), + migrationJson: named((_file, name) => name === 'migration.json'), + refs: named((file, name) => name.endsWith('.json') && basename(dirname(file.path)) === 'refs'), + migrationTs: named((file, name) => name === 'migration.ts' && file.inMigrationPackage), + leftovers, + unreadable, + }; +} + +type DirectoryListing = + | { readonly ok: true; readonly entries: Dirent[] } + | { readonly ok: false; readonly code: string }; + +function listDirectory(dir: string): DirectoryListing { + try { + return { ok: true, entries: readdirSync(dir, { withFileTypes: true }) }; + } catch (error) { + const code = error instanceof Error && 'code' in error ? String(error.code) : 'unknown error'; + return { ok: false, code }; + } +} + +function isSnapshotContract(path: string): boolean { + const snapshotDir = dirname(path); + return ( + basename(path) === 'contract.json' && + HASH.test(basename(snapshotDir)) && + basename(dirname(snapshotDir)) === 'snapshots' + ); +} + +function replaceQuotedHashes(text: string, hashes: ReadonlyMap): string { + return text.replace(/"([0-9a-f]{64})"/g, (match: string, hash: string) => { + const next = hashes.get(hash); + return next === undefined ? match : `"${next}"`; + }); +} + +function replaceHashTokens(text: string, hashes: ReadonlyMap): string { + return text.replace( + /(? hashes.get(hash) ?? hash, + ); +} + +function readDirectory(dir: string): Map { + const files = new Map(); + if (!existsSync(dir)) return files; + for (const entry of readdirSync(dir, { withFileTypes: true })) + if (isFileEntry(dir, entry) && !entry.name.endsWith(TEMPORARY_SUFFIX)) + files.set(entry.name, readFileSync(join(dir, entry.name), 'utf8')); + return files; +} + +function sameFiles(a: ReadonlyMap, b: ReadonlyMap): boolean { + return a.size === b.size && [...a].every(([name, content]) => b.get(name) === content); +} + +function temporaryPath(path: string): string { + return join(dirname(path), `.${basename(path)}${TEMPORARY_SUFFIX}`); +} + +function writeWithModeOf(path: string, text: string, modeSource: string): void { + writeFileSync(path, text); + if (existsSync(modeSource)) chmodSync(path, statSync(modeSource).mode & 0o7777); +} + +function writeFile(path: string, text: string): void { + const target = realpathSync(path); + const temporary = temporaryPath(target); + writeWithModeOf(temporary, text, target); + renameSync(temporary, target); +} + +function writeDirectory(dir: string, files: ReadonlyMap, sourceDir: string): void { + const temporary = temporaryPath(dir); + mkdirSync(temporary); + for (const [name, text] of files) + writeWithModeOf(join(temporary, name), text, join(sourceDir, name)); + renameSync(temporary, dir); +} + +function removeDirectory(dir: string): void { + const temporary = temporaryPath(dir); + renameSync(dir, temporary); + rmSync(temporary, { recursive: true, force: true }); +} + +function count(amount: number, singular: string, plural: string): string { + return `${amount} ${amount === 1 ? singular : plural}`; +} + +function recordsOf(value: Json | undefined): [string, JsonRecord][] { + return isRecord(value) + ? Object.entries(value).filter((entry): entry is [string, JsonRecord] => isRecord(entry[1])) + : []; +} + +function coordinate(namespaceId: string, entityKind: string, entityName: string): JsonRecord { + return { plane: 'storage', namespaceId, entityKind, entityName }; +} + +interface Rewrite { + readonly contract: JsonRecord; + readonly changed: boolean; + /** The storage namespace each moved value object now lives in, by name. */ + readonly valueObjectNamespaces: ReadonlyMap; + readonly problems: readonly string[]; +} + +/** Whether the contract keeps value objects in the domain plane, or refers to one by name. */ +function holdsOldValueObjects(contract: JsonRecord): boolean { + const domain = isRecord(contract['domain']) ? contract['domain'] : {}; + for (const [, namespace] of recordsOf(domain['namespaces'])) { + if (Object.hasOwn(namespace, 'valueObjects')) return true; + for (const [, model] of recordsOf(namespace['models'])) + for (const [, field] of recordsOf(model['fields'])) { + const type = field['type']; + if (isRecord(type) && type['kind'] === 'valueObject' && typeof type['name'] === 'string') + return true; + } + } + return false; +} + +/** A field that names a value object by `name`, pointed at it by its storage coordinate. */ +function withValueObjectCoordinate( + field: JsonRecord, + namespaceOf: (name: string) => string | undefined, + where: string, + problems: string[], +): JsonRecord { + const type = field['type']; + if (!isRecord(type) || type['kind'] !== 'valueObject' || typeof type['name'] !== 'string') + return field; + const namespaceId = namespaceOf(type['name']); + if (namespaceId === undefined) { + problems.push( + `${where} holds the value object ${type['name']}, which the contract does not declare`, + ); + return field; + } + return { + ...field, + type: { kind: 'valueObject', ...coordinate(namespaceId, 'valueObject', type['name']) }, + }; +} + +function valueSetsOf(storageNamespace: JsonRecord): JsonRecord { + const entries = isRecord(storageNamespace['entries']) ? storageNamespace['entries'] : {}; + return isRecord(entries['valueSet']) ? entries['valueSet'] : {}; +} + +/** The old MongoDB schema let a member leave `nullable` out, meaning `false`, and say `dict: false`; storage requires `nullable` and keeps `dict` only when it is `true`. */ +function withStorageModifiers(member: JsonRecord): JsonRecord { + const { dict, ...rest } = member; + return { + ...rest, + nullable: member['nullable'] === undefined ? false : member['nullable'], + ...(dict === undefined || dict === false ? {} : { dict }), + }; +} + +/** A value object member, with an enum reference pointed at the enum's storage value set. */ +function rewrittenMember( + stored: JsonRecord, + storageNamespaces: JsonRecord, + where: string, + problems: string[], +): JsonRecord { + const member = withStorageModifiers(stored); + const valueSet = member['valueSet']; + if (!isRecord(valueSet) || valueSet['plane'] === 'storage') return member; + const namespaceId = valueSet['namespaceId']; + const name = valueSet['entityName']; + const storageNamespace = + typeof namespaceId === 'string' ? storageNamespaces[namespaceId] : undefined; + if ( + typeof namespaceId !== 'string' || + typeof name !== 'string' || + !isRecord(storageNamespace) || + !Object.hasOwn(valueSetsOf(storageNamespace), name) + ) { + problems.push( + `${where} is typed by the enum ${String(name)}, which has no value set in storage namespace ${String(namespaceId)}`, + ); + return member; + } + return { ...member, valueSet: { ...valueSet, ...coordinate(namespaceId, 'valueSet', name) } }; +} + +/** Writes each discriminator's value set, `.`, in its base table's namespace, and points the column at it, unless an enum already types the column. */ +function writeDiscriminatorValueSets( + domainNamespaces: JsonRecord, + storageNamespaces: JsonRecord, + problems: string[], +): boolean { + let changed = false; + for (const [modelNamespaceId, namespace] of recordsOf(domainNamespaces)) { + for (const [modelName, model] of recordsOf(namespace['models'])) { + const discriminator = model['discriminator']; + const variants = model['variants']; + const storage = model['storage']; + if (!isRecord(discriminator) || !isRecord(variants) || !isRecord(storage)) continue; + const field = discriminator['field']; + const fields = isRecord(storage['fields']) ? storage['fields'] : {}; + const fieldStorage = typeof field === 'string' ? fields[field] : undefined; + const columnName = isRecord(fieldStorage) ? fieldStorage['column'] : undefined; + const tableName = storage['table']; + const namespaceId = + typeof storage['namespaceId'] === 'string' ? storage['namespaceId'] : modelNamespaceId; + const storageNamespace = storageNamespaces[namespaceId]; + const entries = isRecord(storageNamespace) ? storageNamespace['entries'] : undefined; + const tables = isRecord(entries) ? entries['table'] : undefined; + const table = + isRecord(tables) && typeof tableName === 'string' ? tables[tableName] : undefined; + const columns = isRecord(table) ? table['columns'] : undefined; + const column = + isRecord(columns) && typeof columnName === 'string' ? columns[columnName] : undefined; + if ( + typeof field !== 'string' || + typeof columnName !== 'string' || + !isRecord(entries) || + !isRecord(columns) || + !isRecord(column) || + column['valueSet'] !== undefined + ) + continue; + const name = `${modelName}.${field}`; + const valueSets = isRecord(entries['valueSet']) ? entries['valueSet'] : {}; + if (Object.hasOwn(valueSets, name)) { + problems.push( + `the discriminator ${name} writes the value set ${name} in storage namespace ${namespaceId}, which already holds a value set of that name; rename the enum`, + ); + continue; + } + entries['valueSet'] = { + ...valueSets, + [name]: { kind: 'valueSet', values: Object.keys(variants).sort(compareCodeUnits) }, + }; + columns[columnName] = { ...column, valueSet: coordinate(namespaceId, 'valueSet', name) }; + changed = true; + } + } + return changed; +} + +/** The contract with its value objects moved into storage, and on SQL each discriminator's value set written. */ +function rewriteContract(contract: JsonRecord, family: Family): Rewrite { + const unchanged: Rewrite = { + contract, + changed: false, + valueObjectNamespaces: new Map(), + problems: [], + }; + if (!holdsOldValueObjects(contract)) return unchanged; + const copy = parseJson(JSON.stringify(contract)); + if (!isRecord(copy) || !isRecord(copy['domain']) || !isRecord(copy['storage'])) return unchanged; + const domainNamespaces = isRecord(copy['domain']['namespaces']) + ? copy['domain']['namespaces'] + : {}; + const storageNamespaces = isRecord(copy['storage']['namespaces']) + ? copy['storage']['namespaces'] + : {}; + const problems: string[] = []; + const valueObjectNamespaces = new Map(); + const declaredIn = new Map>(); + for (const [namespaceId, namespace] of recordsOf(domainNamespaces)) + for (const [name] of recordsOf(namespace['valueObjects'])) + declaredIn.set(name, new Set([...(declaredIn.get(name) ?? []), namespaceId])); + const namespaceFor = + (ownNamespaceId: string) => + (name: string): string | undefined => { + const namespaces = declaredIn.get(name); + if (namespaces === undefined) return undefined; + if (namespaces.has(ownNamespaceId)) return ownNamespaceId; + return namespaces.size === 1 ? [...namespaces][0] : undefined; + }; + + for (const [namespaceId, namespace] of recordsOf(domainNamespaces)) { + const valueObjects = namespace['valueObjects']; + delete namespace['valueObjects']; + for (const [modelName, model] of recordsOf(namespace['models'])) { + const fields = model['fields']; + for (const [fieldName, field] of recordsOf(fields)) + if (isRecord(fields)) + fields[fieldName] = withValueObjectCoordinate( + field, + namespaceFor(namespaceId), + `field ${modelName}.${fieldName}`, + problems, + ); + } + if (!isRecord(valueObjects)) continue; + const storageNamespace = storageNamespaces[namespaceId]; + if (!isRecord(storageNamespace)) { + problems.push( + `the value objects of domain namespace ${namespaceId} have no storage namespace ${namespaceId} to move to`, + ); + continue; + } + const moved: JsonRecord = {}; + for (const [name, valueObject] of recordsOf(valueObjects)) { + valueObjectNamespaces.set(name, namespaceId); + const members: JsonRecord = {}; + for (const [memberName, member] of recordsOf(valueObject['fields'])) { + const where = `value object member ${name}.${memberName}`; + members[memberName] = rewrittenMember( + withValueObjectCoordinate(member, namespaceFor(namespaceId), where, problems), + storageNamespaces, + where, + problems, + ); + } + moved[name] = { ...valueObject, fields: members }; + } + const entries = isRecord(storageNamespace['entries']) ? storageNamespace['entries'] : {}; + storageNamespace['entries'] = { ...entries, valueObject: moved }; + } + if (family === 'sql') writeDiscriminatorValueSets(domainNamespaces, storageNamespaces, problems); + if (Object.hasOwn(copy, 'valueObjects')) delete copy['valueObjects']; + return { contract: copy, changed: true, valueObjectNamespaces, problems }; +} + +function withStorageHash(contract: JsonRecord, hash: string): JsonRecord { + const storage = contract['storage']; + if (!isRecord(storage) || typeof storage['storageHash'] !== 'string') return contract; + return { ...contract, storage: { ...storage, storageHash: hash } }; +} + +function lineEndingOf(text: string): string { + return text.includes('\r\n') ? '\r\n' : '\n'; +} + +/** The contract written the way the file held it: a snapshot's canonical line, or indented JSON, with the file's byte order mark and line ending. */ +function writtenForm(contract: JsonRecord, original: JsonRecord, text: string): string { + const mark = text.startsWith(BYTE_ORDER_MARK) ? BYTE_ORDER_MARK : ''; + const body = withoutByteOrderMark(text); + const lineEnding = lineEndingOf(body); + const finalNewline = body.endsWith('\n') ? lineEnding : ''; + if (body === `${canonicalizeJson(original)}${finalNewline}`) + return `${mark}${canonicalizeJson(contract)}${finalNewline}`; + const indented = JSON.stringify(contract, null, 2).replaceAll('\n', lineEnding); + return `${mark}${indented}${finalNewline}`; +} + +interface Block { + readonly start: number; + end: number; + readonly label: string; + readonly parent: Block | undefined; +} + +function scanBlocks(text: string): Block[] { + const blocks: Block[] = []; + const open: Block[] = []; + let quote: string | undefined; + for (let index = 0; index < text.length; index++) { + const char = text[index]; + if (quote !== undefined) { + if (char === '\\') index++; + else if (char === quote) quote = undefined; + continue; + } + if (char === '/' && text[index + 1] === '/') { + const end = text.indexOf('\n', index); + index = end === -1 ? text.length : end; + continue; + } + if (char === '/' && text[index + 1] === '*') { + const end = text.indexOf('*/', index + 2); + index = end === -1 ? text.length : end + 1; + continue; + } + if (char === "'" || char === '"' || char === '`') { + quote = char; + continue; + } + if (char === '{') { + const key = + /(?:readonly\s+)?([\p{ID_Start}_$][\p{ID_Continue}$]*|'[^']*'|"[^"]*")\s*\??\s*:\s*$/u.exec( + text.slice(Math.max(0, index - 200), index), + ); + const block: Block = { + start: index, + end: text.length, + label: key?.[1]?.replace(/^['"]|['"]$/g, '') ?? '', + parent: open.at(-1), + }; + open.push(block); + blocks.push(block); + } else if (char === '}') { + const block = open.pop(); + if (block !== undefined) block.end = index; + } + } + return blocks; +} + +function lineStart(text: string, position: number): number { + return text.lastIndexOf('\n', position - 1) + 1; +} + +/** The end of the member a block closes: past its `;` and the line break after it. */ +function memberEnd(text: string, block: Block): number { + const rest = /^\s*;?[ \t]*(?:\r?\n)?/.exec(text.slice(block.end + 1)); + return block.end + 1 + (rest?.[0].length ?? 0); +} + +function indentOf(text: string, position: number): string { + return /^[ \t]*/.exec(text.slice(lineStart(text, position)))?.[0] ?? ''; +} + +function childBlocks(blocks: readonly Block[], parent: Block): Block[] { + return blocks.filter((block) => block.parent === parent); +} + +function isUnder(block: Block, labels: readonly string[]): boolean { + let current = block.parent; + for (const label of labels) { + if (current?.label !== label) return false; + current = current.parent; + } + return true; +} + +/** A block re-indented from `from` to `to`, its first line left as it starts. */ +function reindent(literal: string, from: string, to: string): string { + return literal + .split('\n') + .map((line, index) => + index === 0 || !line.startsWith(from) ? line : `${to}${line.slice(from.length)}`, + ) + .join('\n'); +} + +interface Edit { + readonly start: number; + readonly end: number; + readonly text: string; +} + +const STORAGE_NAMESPACES = "ContractBase['storage']['namespaces']"; + +function dtsPathOf(contractPath: string, snapshot: boolean): string { + return snapshot + ? join(dirname(contractPath), 'contract.d.ts') + : `${contractPath.slice(0, -'.json'.length)}.d.ts`; +} +const DTS_LINE_WIDTH = 100; + +/** + * The same move in `contract.d.ts`: each `valueObjects` type of a domain namespace goes into its storage namespace's `entries` as `valueObject`, a member's enum reference names the storage value set, a field's value object type names its coordinate, the top-level `valueObjects` type goes, and the value object and field type aliases read storage. The discriminator value sets are not typed: a re-emit types them. + */ +function rewriteDts( + text: string, + valueObjectNamespaces: ReadonlyMap, + hashes: ReadonlyMap, +): string { + const lineEnding = lineEndingOf(text); + const blocks = scanBlocks(text); + const edits: Edit[] = []; + const moved = new Map(); + for (const block of blocks) { + if (block.label !== 'valueObjects') continue; + const namespace = block.parent; + if (namespace !== undefined && isUnder(namespace, ['namespaces', 'domain'])) { + const literal = text + .slice(block.start, block.end + 1) + .replace(/readonly plane: (['"])domain\1/g, "readonly plane: 'storage'") + .replace(/readonly entityKind: (['"])enum\1/g, "readonly entityKind: 'valueSet'"); + moved.set(namespace.label, reindent(literal, indentOf(text, block.start), '')); + edits.push({ start: lineStart(text, block.start), end: memberEnd(text, block), text: '' }); + } else if ( + namespace !== undefined && + childBlocks(blocks, namespace).some((sibling) => sibling.label === 'domain') + ) { + edits.push({ start: lineStart(text, block.start), end: memberEnd(text, block), text: '' }); + } + } + for (const block of blocks) { + if (block.label !== 'entries' || block.parent === undefined) continue; + if (!isUnder(block.parent, ['namespaces']) || isUnder(block.parent, ['namespaces', 'domain'])) + continue; + const literal = moved.get(block.parent.label); + if (literal === undefined) continue; + const indent = `${indentOf(text, block.start)} `; + const closing = lineStart(text, block.end); + edits.push({ + start: closing, + end: closing, + text: `${indent}readonly valueObject: ${reindent(literal, '', indent)};${lineEnding}`, + }); + } + let result = text; + for (const edit of edits.sort((a, b) => b.start - a.start)) + result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`; + result = result + .replace( + /\{\s*readonly kind: (['"])valueObject\1;\s*readonly name: (['"])([^'"]*)\2;?\s*\}/g, + ( + match: string, + _quote: string, + _nameQuote: string, + name: string, + offset: number, + whole: string, + ) => { + const namespaceId = valueObjectNamespaces.get(name); + return namespaceId === undefined + ? match + : coordinateTypeLiteral(namespaceId, name, whole, offset, lineEnding); + }, + ) + .replace( + /ValueObjectTypesFromDomain<(\s*)ContractBase\['domain'\]\['namespaces'\]/g, + `ValueObjectTypesFromStorage<$1${STORAGE_NAMESPACES}`, + ) + .replace(/\bValueObjectTypesFromDomain\b/g, 'ValueObjectTypesFromStorage'); + return replaceHashTokens(withStorageNamespacesArgument(result), hashes); +} + +/** `FieldTypesFromDomain` with the storage namespaces as its sixth argument, which it now requires of every contract. */ +function withStorageNamespacesArgument(text: string): string { + const lineEnding = lineEndingOf(text); + return text.replace( + /(FieldTypesFromDomain<[^<>]*?(['"])(?:output|input)\2)(\s*)>/g, + (_match: string, head: string, _quote: string, space: string) => + space.includes('\n') + ? `${head},${lineEnding} ${STORAGE_NAMESPACES}${lineEnding}>` + : `${head}, ${STORAGE_NAMESPACES}>`, + ); +} + +/** A value object field's coordinate type, on one line when it fits the emitter's line width, else one member per line. */ +function coordinateTypeLiteral( + namespaceId: string, + name: string, + text: string, + offset: number, + lineEnding: string, +): string { + const members = [ + "readonly kind: 'valueObject'", + "readonly plane: 'storage'", + `readonly namespaceId: '${namespaceId}'`, + "readonly entityKind: 'valueObject'", + `readonly entityName: '${name}'`, + ]; + const single = `{ ${members.join('; ')} }`; + if (offset - lineStart(text, offset) + single.length + 1 <= DTS_LINE_WIDTH) return single; + const indent = indentOf(text, offset); + const lines = members.map((member) => `${indent} ${member};`); + return `{${lineEnding}${lines.join(lineEnding)}${lineEnding}${indent}}`; +} + +interface ContractPlan { + readonly path: string; + readonly snapshot: boolean; + readonly oldHash: string; + readonly newHash: string; + readonly content: string; + readonly valueObjectNamespaces: ReadonlyMap; +} + +interface MigrationOps { + readonly path: string; + /** `undefined` when the package has no `ops.json` or it is not valid JSON. */ + readonly ops: Json | undefined; + readonly unreadable: boolean; +} + +function readOps(migrationDir: string): MigrationOps { + const path = join(migrationDir, 'ops.json'); + if (!existsSync(path)) return { path, ops: undefined, unreadable: false }; + const ops = parseJson(readFileSync(path, 'utf8')); + return { path, ops, unreadable: ops === undefined }; +} + +function familyOf(contract: JsonRecord): Family | undefined { + const family = contract['targetFamily']; + return family === 'sql' || family === 'mongo' ? family : undefined; +} + +function summary( + root: string, + contractsFound: number, + files: number, + directories: number, + hashes: ReadonlyMap, +): readonly string[] { + if (contractsFound === 0) + return [`No SQL or MongoDB contract was found under ${root}; nothing changed.`]; + if (files === 0 && directories === 0) + return ['No contract keeps value objects in the domain plane; nothing changed.']; + const renamed = count(directories, 'snapshot directory', 'snapshot directories'); + const changes = `Rewrote ${count(files, 'file', 'files')} and renamed ${renamed}.`; + if (hashes.size === 0) return [changes]; + return [ + `${changes} Storage hashes changed (old -> new):`, + ...[...hashes] + .sort(([a], [b]) => compareCodeUnits(a, b)) + .map(([oldHash, newHash]) => ` ${oldHash} -> ${newHash}`), + ]; +} + +const JSON_FILES_THAT_MUST_PARSE = new Set(['contract.json', 'migration.json']); + +function main({ + root, + errors, +}: { + readonly root: string; + readonly errors: readonly string[]; +}): number { + if (errors.length > 0) { + process.stderr.write(`${errors.join('\n')}\n`); + return 1; + } + const display = (path: string): string => relative(root, path).split(sep).join('/'); + const files = listFiles(root); + if (files.unreadable.length > 0) { + process.stderr.write( + `${files.unreadable + .map( + ({ dir, code }) => + `${display(dir)}: cannot read the directory (${code}); give the script read access to it, or move it out of the project, and run the script again`, + ) + .join('\n')}\n`, + ); + return 1; + } + const plans: ContractPlan[] = []; + const argumentWrites = new Map(); + const stops: string[] = []; + let contractsFound = 0; + + for (const path of files.json) { + const text = readFileSync(path, 'utf8'); + const contract = parseJson(text); + if ( + contract === undefined && + (JSON_FILES_THAT_MUST_PARSE.has(basename(path)) || files.refs.includes(path)) + ) { + stops.push(`${display(path)}: not valid JSON`); + continue; + } + if (!isRecord(contract) || typeof contract['target'] !== 'string') continue; + const family = familyOf(contract); + if (family === undefined || !isRecord(contract['storage'])) continue; + contractsFound++; + const rewrite = rewriteContract(contract, family); + const snapshot = isSnapshotContract(path); + if (!rewrite.changed) { + const dtsPath = dtsPathOf(path, snapshot); + if (existsSync(dtsPath)) { + const dts = readFileSync(dtsPath, 'utf8'); + const next = withStorageNamespacesArgument(dts); + if (next !== dts) argumentWrites.set(dtsPath, next); + } + continue; + } + const stored = contract['storage']['storageHash']; + const oldHash = snapshot ? basename(dirname(path)) : typeof stored === 'string' ? stored : ''; + const computed = computeStorageHash(family, contract); + if (computed !== oldHash || (typeof stored === 'string' && stored !== oldHash)) { + stops.push( + `${display(path)}: the stored storage hash ${oldHash} does not match the contract's content, which hashes to ${computed}; restore the file from version control, or re-emit it, and run the script again`, + ); + continue; + } + if (rewrite.problems.length > 0) { + for (const problem of rewrite.problems) + stops.push( + `${display(path)}: ${problem}; restore the file from version control, or re-emit it, and run the script again`, + ); + continue; + } + const newHash = computeStorageHash(family, rewrite.contract); + plans.push({ + path, + snapshot, + oldHash, + newHash, + content: writtenForm(withStorageHash(rewrite.contract, newHash), contract, text), + valueObjectNamespaces: rewrite.valueObjectNamespaces, + }); + } + + const hashes = new Map(); + for (const plan of [...plans].sort((a, b) => Number(b.snapshot) - Number(a.snapshot))) + if (plan.oldHash !== plan.newHash && !hashes.has(plan.oldHash)) + hashes.set(plan.oldHash, plan.newHash); + + const newDirectories = new Map>(); + const directorySources = new Map(); + const sourceDirectories = new Set(); + const removals = new Set(); + const snapshotRewrites: [string, string][] = []; + const referenceWrites = new Map(); + const contractWrites = new Map(); + for (const plan of plans) { + const dtsPath = dtsPathOf(plan.path, plan.snapshot); + const dtsHashes = new Map([...hashes, [plan.oldHash, plan.newHash]]); + const dts = existsSync(dtsPath) + ? rewriteDts(readFileSync(dtsPath, 'utf8'), plan.valueObjectNamespaces, dtsHashes) + : undefined; + if (!plan.snapshot) { + if (dts !== undefined) referenceWrites.set(dtsPath, dts); + contractWrites.set(plan.path, plan.content); + continue; + } + const oldDir = dirname(plan.path); + const newDir = join(dirname(oldDir), plan.newHash); + sourceDirectories.add(oldDir); + if (newDir === oldDir) { + if (dts !== undefined) snapshotRewrites.push([dtsPath, dts]); + snapshotRewrites.push([plan.path, plan.content]); + continue; + } + removals.add(oldDir); + const content = readDirectory(oldDir); + content.set('contract.json', plan.content); + if (dts !== undefined) content.set('contract.d.ts', dts); + const planned = newDirectories.get(newDir); + if (planned !== undefined && !sameFiles(planned, content)) { + stops.push(`${display(newDir)}: snapshot directory already exists with different content`); + continue; + } + newDirectories.set(newDir, content); + directorySources.set(newDir, oldDir); + } + for (const [dir, content] of newDirectories) { + if (sourceDirectories.has(dir) || !existsSync(dir)) continue; + if (sameFiles(readDirectory(dir), content)) newDirectories.delete(dir); + else stops.push(`${display(dir)}: snapshot directory already exists with different content`); + } + + for (const path of files.migrationJson) { + const text = readFileSync(path, 'utf8'); + const metadata = parseJson(text); + if (!isRecord(metadata)) continue; + const from = metadata['from']; + const to = metadata['to']; + const nextFrom = typeof from === 'string' ? (hashes.get(from) ?? from) : from; + const nextTo = typeof to === 'string' ? (hashes.get(to) ?? to) : to; + if (nextFrom === from && nextTo === to) continue; + const { path: opsPath, ops, unreadable } = readOps(dirname(path)); + if (unreadable) { + stops.push(`${display(opsPath)}: not valid JSON`); + continue; + } + const oldMigrationHash = metadata['migrationHash']; + let next = replaceQuotedHashes(text, hashes); + if ( + ops !== undefined && + typeof oldMigrationHash === 'string' && + nextFrom !== undefined && + nextTo !== undefined + ) { + const migrationHash = computeMigrationHash({ ...metadata, from: nextFrom, to: nextTo }, ops); + next = next.replace(`"${oldMigrationHash}"`, `"${migrationHash}"`); + } + referenceWrites.set(path, next); + } + for (const path of files.refs) { + const text = readFileSync(path, 'utf8'); + const next = replaceQuotedHashes(text, hashes); + if (next !== text) referenceWrites.set(path, next); + } + for (const path of files.migrationTs) { + const text = readFileSync(path, 'utf8'); + const next = replaceHashTokens(text, hashes); + if (next !== text) referenceWrites.set(path, next); + } + + if (stops.length > 0) { + process.stderr.write(`${[...new Set(stops)].join('\n')}\n`); + return 1; + } + + try { + for (const path of files.leftovers) rmSync(path, { recursive: true, force: true }); + for (const [dir, content] of newDirectories) + writeDirectory(dir, content, directorySources.get(dir) ?? dir); + for (const [path, text] of snapshotRewrites) writeFile(path, text); + for (const [path, text] of referenceWrites) writeFile(path, text); + for (const [path, text] of contractWrites) writeFile(path, text); + for (const [path, text] of argumentWrites) writeFile(path, text); + for (const dir of removals) removeDirectory(dir); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write( + `${message}; the upgrade stopped partway, run the script again to finish it\n`, + ); + return 1; + } + const rewrittenFiles = + snapshotRewrites.length + referenceWrites.size + contractWrites.size + argumentWrites.size; + const lines = summary(root, contractsFound, rewrittenFiles, removals.size, hashes); + process.stdout.write(`${lines.join('\n')}\n`); + return 0; +} + +function parseRoot(args: readonly string[]): { readonly root: string; readonly errors: string[] } { + const errors = args.filter((arg) => arg.startsWith('-')).map((arg) => `${arg}: unknown option`); + const positional = args.filter((arg) => !arg.startsWith('-')); + if (positional.length > 1) errors.push(`expected one project root, got ${positional.length}`); + const root = resolve(positional[0] ?? process.cwd()); + if (errors.length === 0 && !isDirectory(root)) errors.push(`${root}: not a directory`); + if (errors.length > 0) errors.push('Usage: node value-objects-in-storage.ts [project-root]'); + return { root, errors }; +} + +process.exitCode = main(parseRoot(process.argv.slice(2))); diff --git a/upgrade-instructions/pending/value-objects-in-storage/extension/instructions.md b/upgrade-instructions/pending/value-objects-in-storage/extension/instructions.md new file mode 100644 index 000000000000..4521458af1a3 --- /dev/null +++ b/upgrade-instructions/pending/value-objects-in-storage/extension/instructions.md @@ -0,0 +1,137 @@ +--- +changes: + - id: mongo-result-shapes-read-storage + summary: | + `contractModelToMongoResultShape` and `contractFieldToMongoFieldShape` from `@prisma/orm-mongo/query-builder` take the contract's storage, `{ storage: contract.storage }` and `contract.storage`, instead of its value objects. `compileMongoQuery` from `@prisma/orm-mongo/orm` takes the contract's storage as its fifth argument. `MongoValueObjects` is removed. This replaces the rc.14 advice to pass `valueObjects`. + detection: + glob: "**/*.{ts,mts,cts,tsx}" + matches: + - '\bMongoValueObjects\b' + - '\bcontractModelToMongoResultShape\s*\([\s\S]*?\bvalueObjects\b' + - '\bcontractFieldToMongoFieldShape\s*\(' + - '\bcompileMongoQuery\s*\(' + - id: postgres-check-expression-types-move + summary: | + `PostgresCheckKind`, `PostgresCheckExpressionInput` and `PostgresCheckExpressionCandidate` are removed from `@prisma/orm-postgres/target/types`. Use `CheckExpressionInput` and `CheckExpressionCandidate` from `@prisma/orm-postgres/family-contract/check-expressions` (also exported at the same path by `@prisma/orm-mysql` and `@prisma/orm-sqlite`). A candidate's `kind` is `'membership'`, `'elementNotNull'` or the new `'shape'`, the check of a value object column's shape. + detection: + glob: "**/*.{ts,mts,cts,tsx}" + matches: + - '\bPostgresCheck(?:Kind|ExpressionInput|ExpressionCandidate)\b' + - id: field-types-read-storage-value-objects + summary: | + In `@prisma/orm-framework/contract/types`, `ValueObjectTypesFromDomain` is now `ValueObjectTypesFromStorage`, which takes the contract's storage namespaces. `FieldTypesFromDomain` takes the storage namespaces as a new, required sixth argument, `Contract['storage']['namespaces']`, since it reads both planes. An override for a value object field is keyed by the value object's storage namespace, at `[storage namespace]['valueObjects'][valueObject][field]`, while a model field's override stays keyed by its domain namespace. + detection: + glob: "**/*.{ts,mts,cts,tsx}" + matches: + - '\bValueObjectTypesFromDomain\b' + - '\bFieldTypesFromDomain\s*<' + - id: value-object-reference-type + summary: | + In `@prisma/orm-framework/contract/types`, `ValueObjectFieldType` is renamed `ValueObjectReference`: the type of a field that refers to a value object, which is now the value object's storage coordinate. `ContractValueObjectField` stays the type of a value object's own field. + detection: + glob: "**/*.{ts,mts,cts,tsx}" + matches: + - '\bValueObjectFieldType\b' + - id: postgres-codec-json-type + summary: | + A PostgreSQL codec descriptor declares `jsonType`: the JSON type the codec's value is written as inside a JSON document, one of `'string'`, `'number'`, `'boolean'`, `'object'`, `'array'` or `'any'`. `'any'` means any JSON value, including `null`, as for a JSON codec. The derived check of a value object column requires a member to be of its codec's JSON type, and a required member is never JSON `null` unless its codec's type is `'any'`. A class that extends `PostgresCodecDescriptor`, or an object that implements `AnyPostgresCodecDescriptor`, declares it as a property. `postgresCodec(template, options)` reads it from the template, so a family codec template declares it. A registry accepts a descriptor without it, but a contract build that derives the check of a value object whose member uses such a codec refuses it with `CONTRACT.CODEC_JSON_TYPE_MISSING`. + detection: + glob: "**/*.{ts,mts,cts,tsx}" + matches: + - '\bextends\s+PostgresCodecDescriptor\b' + - '\bAnyPostgresCodecDescriptor\b' + - '\bpostgresCodec\s*\(' +--- + +# Value objects are storage entities + +A contract keeps its value objects in storage, at `storage.namespaces..entries.valueObject`, and a domain field names its value object by a storage coordinate. Code that read `domain.namespaces..valueObjects` reads the storage namespaces instead. + +## `mongo-result-shapes-read-storage` + +```ts +// before +contractModelToMongoResultShape(model, { includes: { author: authorShape }, valueObjects }); +contractFieldToMongoFieldShape(field, valueObjects); +compileMongoQuery(collection, state, storageHash, model, valueObjects); + +// after +contractModelToMongoResultShape(model, { includes: { author: authorShape }, storage: contract.storage }); +contractFieldToMongoFieldShape(field, contract.storage); +compileMongoQuery(collection, state, storageHash, model, contract.storage); +``` + +A value object field's shape is a document of its fields when `storage` holds the value object the field's coordinate names. Drop any `MongoValueObjects` type annotation; each parameter takes an object with the contract's storage `namespaces`. + +## `postgres-check-expression-types-move` + +```ts +// before +import type { PostgresCheckExpressionInput } from '@prisma/orm-postgres/target/types'; + +// after +import type { CheckExpressionInput } from '@prisma/orm-postgres/family-contract/check-expressions'; +``` + +`postgresRenderCheckExpressions` stays in `@prisma/orm-postgres/target/types`. Its input also takes `valueObject`, a `ResolvedColumnValueObject` (the column's nullability, whether its JSON value is an array, and the value object), or `undefined`. + +## `field-types-read-storage-value-objects` + +```ts +// before +import type { FieldTypesFromDomain, ValueObjectTypesFromDomain } from '@prisma/orm-framework/contract/types'; +type Fields = FieldTypesFromDomain; +type ValueObjects = ValueObjectTypesFromDomain; + +// after +import type { FieldTypesFromDomain, ValueObjectTypesFromStorage } from '@prisma/orm-framework/contract/types'; +type Fields = FieldTypesFromDomain; +type ValueObjects = ValueObjectTypesFromStorage; +``` + +Pass the sixth argument for every contract, with value objects or without. `FieldTypeOverrides` has two kinds of key at its top level. Under `models`, the key is the domain namespace of the model. Under `valueObjects`, the key is the storage namespace that holds the value object. These can differ. On SQL every value object is stored in the default storage namespace, such as `public` on Postgres, whatever the namespace of the model that holds it. On MongoDB it is `__unbound__`. + +```ts +// a model `User` in the PSL namespace `billing`, with an `Address` field, on Postgres +type FieldTypeOverrides = { + readonly billing: { readonly models: { readonly User: { readonly status: { readonly output: 'active' | 'closed' } } } }; + readonly public: { readonly valueObjects: { readonly Address: { readonly kind: { readonly output: 'home' | 'work' } } } }; +}; +``` + +## `value-object-reference-type` + +```ts +// before +import type { ValueObjectFieldType } from '@prisma/orm-framework/contract/types'; +const reference: ValueObjectFieldType = { kind: 'valueObject', name: 'Address' }; + +// after +import type { ValueObjectReference } from '@prisma/orm-framework/contract/types'; +const reference: ValueObjectReference = { + kind: 'valueObject', + plane: 'storage', + namespaceId: 'public', + entityKind: 'valueObject', + entityName: 'Address', +}; +``` + +## `postgres-codec-json-type` + +```ts +// before +export class CitextDescriptor extends PostgresCodecDescriptor { + override readonly codecId = 'citext/text@1'; + // ... +} + +// after +export class CitextDescriptor extends PostgresCodecDescriptor { + override readonly jsonType = 'string' as const; + override readonly codecId = 'citext/text@1'; + // ... +} +``` + +Declare the type the codec's value is written as when the application value is turned into JSON: a vector is `'array'`, a geometry and bytes are `'object'`, and a JSON codec is `'any'`. A template passed to `postgresCodec(template, options)` declares `jsonType` itself, as the `sql/*` family codecs do.