From df1ba8bcaf4c376f31f2398f4780bb8e9a6bf3d8 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 23 May 2026 18:42:18 -0700 Subject: [PATCH 1/3] doc: document @@delegateMap attribute for customizing the polymorphic discriminator value Co-Authored-By: Claude Opus 4.7 --- docs/orm/polymorphism.md | 73 ++++++++++++++++++++++++++++++ docs/reference/zmodel/attribute.md | 14 ++++++ 2 files changed, 87 insertions(+) diff --git a/docs/orm/polymorphism.md b/docs/orm/polymorphism.md index 1a7f85b2..0af379f1 100644 --- a/docs/orm/polymorphism.md +++ b/docs/orm/polymorphism.md @@ -5,6 +5,7 @@ description: Polymorphic models import ZenStackVsPrisma from '../_components/ZenStackVsPrisma'; import StackBlitzGithub from '@site/src/components/StackBlitzGithub'; +import AvailableSince from '../_components/AvailableSince'; # Polymorphic Models @@ -26,6 +27,78 @@ The ORM query API hides all the complexity of managing polymorphic models for yo - When querying a base entity, the ORM fetches the associated concrete entity and merges the results. - When deleting a base or concrete entity, the ORM automatically deletes the counterpart entity. +## Customizing the discriminator value + + + +By default, when the ORM creates a concrete entity, it stores the concrete model's **name** in the base model's discriminator field. For instance, given the schema below, creating a `Video` writes `"Video"` to `Content.type`, and creating an `Image` writes `"Image"`. + +```zmodel +model Content { + id Int @id + type String + @@delegate(type) +} + +model Video extends Content { url String } +model Image extends Content { data Bytes } +``` + +If the model names don't match the values you want to store, you can override the value with the `@@delegateMap` attribute on each concrete model. + +### String discriminator + +When the discriminator field is a `String`, pass a string literal to `@@delegateMap`: + +```zmodel +model Content { + id Int @id + type String + @@delegate(type) +} + +model Video extends Content { + url String + // highlight-next-line + @@delegateMap("video") +} + +model Image extends Content { + data Bytes + // highlight-next-line + @@delegateMap("image") +} +``` + +### Enum discriminator + +When the discriminator field is an enum, pass an enum member to `@@delegateMap`. The member must come from the same enum as the discriminator field: + +```zmodel +enum AssetKind { + ASSET_KIND_VIDEO + ASSET_KIND_IMAGE +} + +model Asset { + id Int @id + type AssetKind + @@delegate(type) +} + +model Video extends Asset { + url String + // highlight-next-line + @@delegateMap(ASSET_KIND_VIDEO) +} + +model Image extends Asset { + data Bytes + // highlight-next-line + @@delegateMap(ASSET_KIND_IMAGE) +} +``` + ## Samples The schema used in the sample involves a base model and three concrete models: diff --git a/docs/reference/zmodel/attribute.md b/docs/reference/zmodel/attribute.md index bb2f792f..a33371eb 100644 --- a/docs/reference/zmodel/attribute.md +++ b/docs/reference/zmodel/attribute.md @@ -359,6 +359,20 @@ _Params_: | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | | discriminator | A `String` or `enum` field in the same model used to store the name of the concrete model that inherit from this base model. | +### @@delegateMap + +```zmodel +attribute @@delegateMap(_ value: Any) +``` + +Maps a delegate sub-model to a specific discriminator value. Applied to a concrete model that extends a delegate base. If omitted, the sub-model's name is used as the discriminator value by default. See [Customizing the discriminator value](../../orm/polymorphism.md#customizing-the-discriminator-value). + +_Params_: + +| Name | Description | +| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| value | A string literal (when the discriminator is `String`) or an enum member (when the discriminator is an enum, and must belong to the same enum type). | + ### @@meta ```zmodel From f51ac7668ad3ee2abc60786abe011b386e342321 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 23 May 2026 18:48:17 -0700 Subject: [PATCH 2/3] doc: add TypeScript narrowing and validation rules subsections to @@delegateMap docs Co-Authored-By: Claude Opus 4.7 --- docs/orm/polymorphism.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/orm/polymorphism.md b/docs/orm/polymorphism.md index 0af379f1..496cfce6 100644 --- a/docs/orm/polymorphism.md +++ b/docs/orm/polymorphism.md @@ -99,6 +99,32 @@ model Image extends Asset { } ``` +### TypeScript narrowing + +The discriminator value flows into the result type as a string literal (or enum member), so a check on the base field narrows the result to the matching concrete model: + +```ts +const content = await client.content.findUniqueOrThrow({ where: { id: 1 } }); +if (content.type === 'video') { + // narrowed to Video — `url` is available + console.log(content.url); +} else if (content.type === 'image') { + // narrowed to Image — `data` is available + console.log(content.data); +} +``` + +For an enum discriminator, narrow against the enum member instead (e.g., `asset.type === 'ASSET_KIND_VIDEO'`). + +### Validation rules + +The ZModel compiler enforces the following at the schema boundary: + +- `@@delegateMap` is only valid on a model that extends a delegate base. +- A model may include at most one `@@delegateMap` attribute. +- The value type must match the discriminator field: a string literal for a `String` discriminator, an enum member for an enum discriminator (and the member must belong to the discriminator's enum). +- All concrete models that share the same delegate base must end up with distinct discriminator values. Without `@@delegateMap`, the model name is used — so a mapped value of `"Image"` would collide with a sibling `Image` model that has no `@@delegateMap`. + ## Samples The schema used in the sample involves a base model and three concrete models: From ff88c9612c7b9526d585c4208de8236f22674b08 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 23 May 2026 18:50:19 -0700 Subject: [PATCH 3/3] update --- docs/orm/polymorphism.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/orm/polymorphism.md b/docs/orm/polymorphism.md index 496cfce6..965bba75 100644 --- a/docs/orm/polymorphism.md +++ b/docs/orm/polymorphism.md @@ -114,17 +114,6 @@ if (content.type === 'video') { } ``` -For an enum discriminator, narrow against the enum member instead (e.g., `asset.type === 'ASSET_KIND_VIDEO'`). - -### Validation rules - -The ZModel compiler enforces the following at the schema boundary: - -- `@@delegateMap` is only valid on a model that extends a delegate base. -- A model may include at most one `@@delegateMap` attribute. -- The value type must match the discriminator field: a string literal for a `String` discriminator, an enum member for an enum discriminator (and the member must belong to the discriminator's enum). -- All concrete models that share the same delegate base must end up with distinct discriminator values. Without `@@delegateMap`, the model name is used — so a mapped value of `"Image"` would collide with a sibling `Image` model that has no `@@delegateMap`. - ## Samples The schema used in the sample involves a base model and three concrete models: