Skip to content

feat(orm): implement $is sub-model filter for delegate base models#2559

Open
motopods wants to merge 12 commits intozenstackhq:devfrom
motopods:dev
Open

feat(orm): implement $is sub-model filter for delegate base models#2559
motopods wants to merge 12 commits intozenstackhq:devfrom
motopods:dev

Conversation

@motopods
Copy link
Copy Markdown
Contributor

@motopods motopods commented Apr 6, 2026

Adds support for the $is filter operator on delegate (polymorphic) base model where clauses, enabling queries that filter by sub-model-specific fields.

What's new

When querying from a delegate base model, use $is to filter by sub-model type and/or sub-model-specific fields. Multiple sub-model entries are combined with OR semantics.

// Return only Video assets (includes RatedVideo since it extends Video)
await client.asset.findMany({
    where: { $is: { video: {} } },
});

// true value is equivalent to an empty filter — same as above
await client.asset.findMany({
    where: { $is: { video: true} },
});

// Filter on a sub-model-specific field
await client.asset.findMany({
    where: { $is: { video: { duration: { gt: 100 } } } },
});

// Combine a base field filter with a sub-model filter (AND)
await client.asset.findMany({
    where: {
        viewCount: { gt: 0 },
        $is: { video: { duration: { gt: 100 } } },
    },
});

// Multiple sub-models — OR semantics
// Returns: (Videos with duration > 100) OR (Images with format 'png')
await client.asset.findMany({
    where: {
        $is: {
            video: { duration: { gt: 100 } },
            image: { format: 'png' },
        },
    },
});

// Nested $is — for multi-level delegate hierarchies
// Asset.$is.video.$is.ratedVideo
await client.asset.findMany({
    where: {
        $is: {
            video: { $is: { ratedVideo: { rating: 5 } } },
        },
    },
});

// $is on an intermediate delegate
await client.video.findMany({
    where: { $is: { ratedVideo: { rating: 5 } } },
});
  • Correlated EXISTS subqueries — Filters are translated into EXISTS (SELECT 1 FROM <submodel> WHERE id = <base>.id AND <discriminator> AND <sub-fields>) SQL, ensuring correct row-level scoping.
  • Type-safe WhereInput$SubModelWhereInput types are generated in crud-types.ts so TypeScript enforces valid sub-model fields at compile time.
  • Runtime Zod validationzod/factory.ts validates $is payloads at runtime, giving clear error messages for unknown sub-models or invalid field shapes.

Files changed

File Change
packages/orm/src/client/crud-types.ts Added $SubModelWhereInput and wired $is into WhereInput
packages/orm/src/client/crud/dialects/base-dialect.ts Added buildIsFilter to emit correlated EXISTS subqueries
packages/orm/src/client/zod/factory.ts Added runtime Zod validation for $is payloads
tests/e2e/orm/client-api/delegate.test.ts End-to-end tests covering basic and nested $is filtering

Close #1740

Summary by CodeRabbit

  • New Features

    • Added a top-level $is filter for delegate (polymorphic) base models to filter by sub-model type, including nested $is conditions, OR-combined multi-sub-model queries, and correlated-subquery semantics for inherited/base fields. Input validation now recognizes $is in where/filter schemas.
  • Tests

    • Added e2e coverage validating $is behavior across base models, nested $is paths, nested predicates, and multi-sub-model queries.

Copilot AI and others added 8 commits April 2, 2026 05:00
…-content

feat(orm): implement discriminated union return types for delegate (polymorphic) models
…rence-for-content

Revert "feat(orm): implement discriminated union return types for delegate (polymorphic) models"
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Adds a $is polymorphic sub-model filter: type definitions, query-predicate construction, Zod where-schema generation, and end-to-end tests enabling base-model queries narrowed by concrete sub-model conditions (including OR semantics and nested $is).

Changes

Cohort / File(s) Summary
Type System
packages/orm/src/client/crud-types.ts
Added SubModelWhereInput and extended WhereInput for delegate models to optionally include $is?: SubModelWhereInput<...>, where each sub-model key maps to true or a nested WhereInput.
Query Builder / Dialect
packages/orm/src/client/crud/dialects/base-dialect.ts
Added buildIsFilter handling in buildFilter: validates discriminator, maps camelCase keys to discriminator values, enforces discriminator equality, and, for nested filters, adds correlated EXISTS subqueries joined on id fields. Multiple sub-model entries are OR-combined.
Schema Validation (Zod)
packages/orm/src/client/zod/factory.ts
makeWhereSchema now conditionally inserts an optional top-level $is strict object for delegate models; keys are lowercased sub-model names and values are true or a lazily-evaluated nested where schema.
Tests
tests/e2e/orm/client-api/delegate.test.ts
Added e2e tests for $is on base models: true matches, nested sub-model predicates, combinations with base-model filters, OR across sub-models, and nested $is paths; additional seeded data included.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I'm a rabbit in the model wood,
I sniff the $is where sub-models stood,
I hop through predicates, small and grand,
Combining checks with a correlated strand,
Polymorphic hops — query fields in hand! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR fully addresses the primary objectives of issue #1740: enabling queries on delegate base models using sub-model-specific properties with type safety and developer-friendly API.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the $is sub-model filter feature; no unrelated modifications are present in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title 'feat(orm): implement $is sub-model filter for delegate base models' is directly related to the main change in the changeset, which introduces a new $is filter operator for delegate (polymorphic) base models across type definitions, dialect logic, validation, and tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@motopods
Copy link
Copy Markdown
Contributor Author

motopods commented Apr 6, 2026

@ymc9 Hi, what about you think of this?

@sanny-io
Copy link
Copy Markdown
Contributor

sanny-io commented Apr 6, 2026

This is very interesting. It may be better to use the lowercase-first name of the model (Video -> video).

Also, null matching the first query seems odd to me. Might it be better to use booleans, instead?

await client.asset.findMany({
    where: { $is: { video: true } },
});

@motopods
Copy link
Copy Markdown
Contributor Author

motopods commented Apr 6, 2026

@sanny-io You make a lot of sense.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/orm/src/client/crud/dialects/base-dialect.ts`:
- Around line 367-382: The EXISTS subquery is built manually (using tmpAlias,
selectFrom and this.eb.exists) which bypasses delegate-aware helpers and causes
inherited-delegate filters from this.buildFilter to reference outer tables
incorrectly; replace the manual existsSubquery construction with the
dialect-aware helper (use the existing buildExistsExpression /
buildDelegateQueryHelpers pathway) so the subquery is created via the same
delegate-aware query builder, passing subModelName, subAlias, joinConditions and
subWhere (or subWhereFilter) into that helper instead of calling
this.eb.selectFrom directly; ensure the final condition still uses
discriminatorCheck and the helper-produced exists expression (instead of
this.eb.exists(existsSubquery)) so dialect-specific EXISTS workarounds and
proper alias joins are preserved.

In `@packages/orm/src/client/zod/factory.ts`:
- Around line 490-503: The $is filter currently uses z.object(...) which strips
unknown sub-model keys and hides typos; update the construction of
subModelSchema to use z.strictObject(...) instead (i.e. replace z.object(...)
with z.strictObject(...)) so unknown sub-model names will be rejected at
validation time; keep the same Object.fromEntries mapping of subModel names and
the existing union that references this.makeWhereSchema, and preserve the
.optional() on subModelSchema before assigning it to fields['$is'].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7ffa087-26a0-464d-9e91-d1bac09d8a0b

📥 Commits

Reviewing files that changed from the base of the PR and between 39a0a28 and d62333d.

📒 Files selected for processing (4)
  • packages/orm/src/client/crud-types.ts
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/zod/factory.ts
  • tests/e2e/orm/client-api/delegate.test.ts

lucas added 2 commits April 7, 2026 23:15
inherited from a delegate base model (e.g. `viewCount` on `Asset`
accessed via `$is: { video: { viewCount: ... } }`), including:
- single inherited-field filter
- mixed inherited + own-field filter
- inherited-field filter returning no rows
- nested delegate level (Video → RatedVideo) with inherited field
@motopods motopods changed the title implement $is sub-model filter for delegate base models fit(orm): implement $is sub-model filter for delegate base models Apr 9, 2026
@motopods motopods changed the title fit(orm): implement $is sub-model filter for delegate base models feat(orm): implement $is sub-model filter for delegate base models Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Polymorphic Models: Query base model with concrete model's props

3 participants