fix(orm): type inference for polymorphic models#2543
Conversation
…hic) models Agent-Logs-Url: https://github.com/motopods/zenstack/sessions/678a87a1-1e67-44f9-95ee-e77f3d5e81cc Co-authored-by: motopods <58200641+motopods@users.noreply.github.com>
Agent-Logs-Url: https://github.com/motopods/zenstack/sessions/678a87a1-1e67-44f9-95ee-e77f3d5e81cc Co-authored-by: motopods <58200641+motopods@users.noreply.github.com>
…-content feat(orm): implement discriminated union return types for delegate (polymorphic) models
…egate (polymorphic) models"
…rence-for-content Revert "feat(orm): implement discriminated union return types for delegate (polymorphic) models"
… for delegate (polymorphic) models""
📝 WalkthroughWalkthroughAdds discriminant-aware type logic to ORM client types to produce discriminated unions for delegated models, including a recursion-depth guard and a generic-model switch; activates discriminant-based narrowing examples in an end-to-end typecheck test. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/orm/src/client/crud-types.ts (1)
81-88: Consider reusingFlatModelResultto reduce duplication.Lines 84 and 88 contain the same mapped type expression that's also defined as
FlatModelResultat lines 140-144. UsingFlatModelResult<Schema, Model, Omit, Options>here would improve maintainability.♻️ Suggested refactor
> = WrapType< _IsGenericModel extends true - ? // generic model — return flat type immediately to avoid expensive recursion - { [Key in NonRelationFields<Schema, Model> as ShouldOmitField<Schema, Model, Options, Key, Omit> extends true ? never : Key]: MapModelFieldType<Schema, Model, Key> } + ? // generic model — return flat type immediately + FlatModelResult<Schema, Model, Omit, Options> : IsDelegateModel<Schema, Model> extends true ? // delegate model's selection result is a union of all sub-models DelegateUnionResult<Schema, Model, Options, GetSubModels<Schema, Model>, Omit> - : { [Key in NonRelationFields<Schema, Model> as ShouldOmitField<Schema, Model, Options, Key, Omit> extends true ? never : Key]: MapModelFieldType<Schema, Model, Key> }, + : FlatModelResult<Schema, Model, Omit, Options>, Optional, Array >;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/orm/src/client/crud-types.ts` around lines 81 - 88, The mapped type used in the non-delegate and generic branches is duplicated; replace that inline mapped type with the existing FlatModelResult generic to remove duplication. In the WrapType conditional where _IsGenericModel and the final else currently expand to "{ [Key in NonRelationFields<Schema, Model> as ShouldOmitField<Schema, Model, Options, Key, Omit> extends true ? never : Key]: MapModelFieldType<Schema, Model, Key> }", swap those expansions to FlatModelResult<Schema, Model, Omit, Options> (keeping the DelegateUnionResult branch unchanged), ensuring you reference the same type parameters (Schema, Model, Omit, Options) and preserve behavior for IsDelegateModel and DelegateUnionResult/GetSubModels.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/orm/src/client/crud-types.ts`:
- Around line 81-88: The mapped type used in the non-delegate and generic
branches is duplicated; replace that inline mapped type with the existing
FlatModelResult generic to remove duplication. In the WrapType conditional where
_IsGenericModel and the final else currently expand to "{ [Key in
NonRelationFields<Schema, Model> as ShouldOmitField<Schema, Model, Options, Key,
Omit> extends true ? never : Key]: MapModelFieldType<Schema, Model, Key> }",
swap those expansions to FlatModelResult<Schema, Model, Omit, Options> (keeping
the DelegateUnionResult branch unchanged), ensuring you reference the same type
parameters (Schema, Model, Omit, Options) and preserve behavior for
IsDelegateModel and DelegateUnionResult/GetSubModels.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c33bbb8e-9b19-46ca-9eb2-1525981d53b2
📒 Files selected for processing (2)
packages/orm/src/client/crud-types.tstests/e2e/orm/schemas/delegate/typecheck.ts
…ric to remove duplication
|
@copilot review |
There was a problem hiding this comment.
Pull request overview
Improves ORM TypeScript result typing for delegated (polymorphic) models so delegate queries return a discriminated union of all concrete leaf models (including multi-level delegation), enabling proper narrowing via discriminator fields.
Changes:
- Update
DefaultModelResultto expand delegate models into a discriminated union (with a generic-model guard to limit instantiation cost). - Introduce
FlatModelResultandDelegateUnionResultto build recursive leaf unions with fixed discriminator literals. - Re-enable/extend e2e typecheck assertions verifying discriminant-based narrowing for direct and relation-based queries.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/orm/src/client/crud-types.ts | Adds delegate-union expansion logic for query result types, including multi-level delegation support and a guard for generic schema evaluation. |
| tests/e2e/orm/schemas/delegate/typecheck.ts | Activates discriminated-union narrowing checks for delegate queries and relation includes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/orm/src/client/crud-types.ts (1)
87-87: Consider defensive distributivePartialwrapper for union discriminator preservation, but this is only necessary if authType is assigned a delegate model.AuthModelType wraps
Partial<DefaultModelResult<...>>. When DefaultModelResult returns a discriminated union (DelegateUnionResult), TypeScript distributesPartialover the union, making each discriminator field optional and weakening narrowing. However, in practice,authTypeinpackages/orm/src/client/contract.ts:973-980is typically a scalar or simple model—rarely a delegate model—so this scenario is uncommon.If delegate models become valid for
authType, apply the distributive pattern at lines 965-971 inpackages/orm/src/client/contract.ts:type AuthModelType<Schema extends SchemaDef, Model extends GetModels<Schema>> = Model extends unknown ? Partial<DefaultModelResult<Schema, Model>> & { ... } : never;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/orm/src/client/crud-types.ts` at line 87, AuthModelType currently wraps Partial<DefaultModelResult<...>> which lets TypeScript distribute Partial over a discriminated union (DelegateUnionResult) and weaken discriminators; to prevent this when DefaultModelResult may be a delegate-model union, change the AuthModelType definition (the type that composes Partial<DefaultModelResult<...>> for authType) into a distributive conditional form so Partial is applied per-variant rather than distributed—i.e., make AuthModelType conditional on Model (Model extends unknown ? ... : never) and place the Partial<DefaultModelResult<...>> & { ... } inside the true branch so discriminators are preserved if DefaultModelResult returns DelegateUnionResult/union types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/orm/src/client/crud-types.ts`:
- Line 87: AuthModelType currently wraps Partial<DefaultModelResult<...>> which
lets TypeScript distribute Partial over a discriminated union
(DelegateUnionResult) and weaken discriminators; to prevent this when
DefaultModelResult may be a delegate-model union, change the AuthModelType
definition (the type that composes Partial<DefaultModelResult<...>> for
authType) into a distributive conditional form so Partial is applied per-variant
rather than distributed—i.e., make AuthModelType conditional on Model (Model
extends unknown ? ... : never) and place the Partial<DefaultModelResult<...>> &
{ ... } inside the true branch so discriminators are preserved if
DefaultModelResult returns DelegateUnionResult/union types.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3f827469-e652-4d9c-93c5-2ee2997953b8
📒 Files selected for processing (1)
packages/orm/src/client/crud-types.ts
Summary
Querying a delegate (polymorphic base) model such as
db.asset.findMany()now returns a proper TypeScript discriminated union instead of the flat base type.Before:
findFirstOrThrow()returnedAsset— accessingr.durationwas always a type error, and narrowing byr.assetTypehad no effect on the type.After:
findFirstOrThrow()returns:TypeScript can now narrow the result via the discriminator field:
Multi-level delegation (Asset → Video → RatedVideo) is fully expanded so every concrete leaf model appears in the union. The
selectpath continues to return a flat result as before (explicitly selected fields remove polymorphic information).Changes
packages/orm/src/client/crud-types.tsIsDelegateModel,GetModelDiscriminator,GetSubModelsto schema imports.FlatModelResulthelper type — the original scalar-field object expression extracted into a named alias for the leaf case.DelegateUnionResult<Schema, Model, Options, SubModel, Omit, _Depth>:DefaultModelResultto useDelegateUnionResultfor delegate models._IsGenericModeldefault type parameter (checks[string] extends [Model]) that short-circuits the entire delegate expansion when TypeScript is evaluatingClientContract<SchemaDef>(the generic base interface). This keeps the type instantiation count bounded (~1.9M vs 1.6M baseline, well below TypeScript's recursion limit).tests/e2e/orm/schemas/delegate/typecheck.tsfindFirstOrThrowand theinclude: { assets: true }relation path).Fix #2352
Summary by CodeRabbit