Skip to content

feat: ORM api slicing#2383

Merged
ymc9 merged 16 commits intodevfrom
feat/api-slicing
Feb 18, 2026
Merged

feat: ORM api slicing#2383
ymc9 merged 16 commits intodevfrom
feat/api-slicing

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Feb 17, 2026

Summary by CodeRabbit

  • New Features

    • Fine-grained slicing: include/exclude models, procedures, operations, and per-field filter kinds; query/client options now propagate slicing choices.
    • New exported type utilities to configure and inspect slicing and filter-kind rules.
  • Refactor

    • Unified client/query options and normalized aggregate naming for a clearer public API.
  • Tests

    • Extensive type- and runtime test suite for slicing and filters added; test runner now enforces type-checking.
  • Other

    • Example schema: optional user age field added.

@claude
Copy link
Contributor

claude bot commented Feb 17, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Todo List:

  • Understand the API slicing feature and its purpose
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Review Summary

This PR introduces a powerful API slicing feature that allows fine-grained control over ORM client capabilities. The implementation is comprehensive and well-architected.

Strengths

1. Excellent Type Safety Implementation

The TypeScript implementation in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle model, operation, and filter slicing
  • Proper handling of inclusion/exclusion precedence (exclusion takes precedence)
  • Support for $all fallback configuration with model-specific overrides
  • Type-safe integration with existing ORM APIs

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 1,872 lines of comprehensive tests covering all slicing scenarios
  • Excellent use of @ts-expect-error to verify TypeScript prevents accessing sliced APIs
  • Tests cover model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced at runtime

3. Well-Architected Core Implementation

  • Clean separation between slicing logic and core ORM functionality
  • Proper integration with existing validation pipeline
  • Client proxy correctly hides sliced operations at runtime
  • Maintains backward compatibility (no slicing config = all APIs available)

4. Client Library Integration

TanStack Query integration properly respects slicing configuration:

  • Type-safe hook generation based on sliced models/operations
  • Proper TypeScript inference for sliced client APIs

⚠️ Areas of Concern

1. Complex Type Computation Performance

The type utilities perform complex conditional type computations that could impact TypeScript compilation:

// packages/orm/src/client/type-utils.ts:155-179
export type GetSlicedFilterKindsForField<
    Schema extends SchemaDef,
    Model extends GetModels<Schema>,
    Field extends string,
    Options extends QueryOptions<Schema>,
> = // deeply nested conditional types...

Impact: May slow TypeScript compilation for large schemas with extensive slicing.

2. Runtime Validation Overhead

The validator in packages/orm/src/client/crud/validator/index.ts now needs to check slicing configuration for every operation:

// Validation occurs on every query/mutation
private validate<T>(
    model: GetModels<Schema>,
    operation: CoreCrudOperations,
    getSchemaFunc: GetSchemaFunc<Schema>,
    args: unknown,
): T {
    // Additional slicing validation overhead
}

3. Memory Usage Considerations

Each sliced client maintains its own configuration and validation schemas, potentially increasing memory usage in applications creating many client instances.

🔍 Potential Edge Cases

1. Relation Validation Consistency

In packages/orm/src/client/crud/validator/index.ts, ensure relation validation properly handles cases where:

  • Parent model is included but related model is excluded
  • Complex nested include/select with mixed slicing configurations

2. Procedure Slicing Edge Case

The procedure slicing logic should handle the case where procedures reference excluded models in their parameters or return types.

🔒 Security Considerations

Positive Security Aspects:

  • Access Control: Slicing can enforce role-based restrictions (e.g., read-only users, restricted models)
  • Data Exposure Reduction: Helps prevent accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional layer of API surface reduction

Recommendations:

  • Consider adding documentation on using slicing for security purposes
  • Ensure slicing validation cannot be bypassed through raw queries or edge cases

📈 Performance Assessment

Compile-Time Performance:

  • Concern: Complex conditional types may slow TypeScript compilation
  • Mitigation: Consider type caching strategies for frequently used slicing configurations

Runtime Performance:

  • Overhead: Additional validation checks on every operation
  • Optimization: Validation schemas are cached, which helps mitigate the overhead

🎯 Final Recommendations

High Priority:

  1. Add Performance Benchmarks: Include compilation time and runtime performance tests
  2. Document Performance Implications: Add guidance on when slicing overhead matters
  3. Consider Type Optimization: Explore ways to simplify complex conditional types

Medium Priority:

  1. Security Documentation: Add examples of using slicing for access control
  2. Error Message Improvements: Ensure validation errors clearly indicate slicing restrictions
  3. Edge Case Testing: Add tests for complex relation scenarios with mixed slicing

Low Priority:

  1. Memory Usage Optimization: Consider sharing validation schemas between similar slicing configurations

Verdict

This is an excellent implementation of a complex feature. The code quality is high, test coverage is comprehensive, and the TypeScript integration is sophisticated. The slicing feature adds significant value for creating specialized clients and implementing access controls.

Recommendation: Approve with the suggestion to monitor TypeScript compilation performance in larger codebases and consider the performance optimizations mentioned above.


Notable files reviewed: 32 changed files including core ORM implementation, type utilities, client integrations, and comprehensive test suite.

@ymc9
Copy link
Member Author

ymc9 commented Feb 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduce comprehensive slicing (models, operations, procedures, filter kinds) with type-level utilities and runtime guards, thread Options through ORM and TanStack Query typings/hooks, rename aggregate operator constant, relax some internal handler typings, and add extensive type tests and e2e slicing tests.

Changes

Cohort / File(s) Summary
Slicing core types & utils
packages/orm/src/client/options.ts, packages/orm/src/client/type-utils.ts
Add SlicingOptions, QueryOptions, ClientOptions changes and many type utilities: GetSlicedModels, GetSlicedOperations, GetSlicedProcedures, GetSlicedFilterKindsForField, plus helpers for included/excluded resolution.
CRUD public types (large)
packages/orm/src/client/crud-types.ts
Thread Options through nearly all CRUD inputs/results/filters; add FieldFilter and AllowedKinds plumbing so filters are Options-aware across nested payloads.
Validator & schema builders
packages/orm/src/client/crud/validator/index.ts
Make validators slicing-aware: add FieldInfo, compute/trim filter kinds, propagate model/field context, and gate relation/filter schemas by slicing.
Client contract & runtime enforcement
packages/orm/src/client/contract.ts, packages/orm/src/client/client-impl.ts, packages/orm/src/client/index.ts
Client types now use sliced models/procedures; add isModelIncluded/isProcedureIncluded runtime guards; client and $procs only expose included items; re-export type-utils.
TanStack Query clients & hooks
packages/clients/tanstack-query/src/common/types.ts, .../react.ts, .../vue.ts, .../svelte/index.svelte.ts
Switch to GetSlicedModels/GetSlicedProcedures and TrimSlicedOperations; thread Options generics through ClientHooks, ProcedureHooks, ModelQueryHooks and all hook arg/result types.
CRUD implementations & dialects (typing relaxations)
packages/orm/src/client/crud/operations/*, packages/orm/src/client/crud/dialects/*
Widen several internal/private handler signatures (many methods accept an extra generic any or any args) and update dialect signatures to match widened FindArgs shape; runtime behavior unchanged.
Constants & query utils
packages/orm/src/client/constants.ts, packages/orm/src/client/query-utils.ts
Rename AGGREGATE_OPERATORSAggregateOperators; add FILTER_PROPERTY_TO_KIND and FilterPropertyToKind; update aggregate util to use renamed type/constant.
Type-level slicing helpers export
packages/orm/src/client/index.ts
Re-export slicing/type-utils so slicing types are available from client index.
Tests & test config
packages/clients/tanstack-query/test/*, packages/clients/tanstack-query/vitest.config.ts, tests/e2e/orm/client-api/slicing.test.ts
Add new sliced client type-tests (React/Svelte/Vue), remove legacy react typing test, enable Vitest typecheck (tsconfig.test.json), and add large e2e slicing test suite exercising runtime and type constraints.
Misc small edits
packages/auth-adapters/better-auth/src/adapter.ts, packages/zod/package.json, tests/e2e/orm/schemas/basic/*
Small generic tweak in adapter.ts UpdateInput generic, update packages/zod/package.json metadata fields, and add optional age field to test User schema.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I nibbled types and threaded Options fine,
Pruned models, hooks, and procedures in line,
Filters sorted by kind with a hop and a cheer,
Tests sprouted green to prove the rules here,
A tiny rabbit clap — the slices appear! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: ORM api slicing' directly and accurately reflects the main feature being implemented - API slicing for the ORM, which is confirmed by the PR description and extensive changes across ORM typing and client-level slicing support.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/api-slicing

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.

Copy link
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/orm/src/client/crud/validator/index.ts (2)

901-924: ⚠️ Potential issue | 🟠 Major

JSON filters incorrectly require Json kind even for equality.
Type-level JsonFilter allows Equality-only usage, but runtime validation rejects all JSON filters unless Json is included. This creates a type/runtime mismatch and blocks valid equals/not filters.

🛠️ Suggested fix
-        // Check if Json filter kind is allowed
-        if (allowedFilterKinds && !allowedFilterKinds.has('Json')) {
-            // Return a never schema if Json filters are not allowed
-            return z.never();
-        }
-
-        const valueSchema = this.makeJsonValueSchema(optional, true);
-        return z.strictObject({
-            path: z.string().optional(),
-            equals: valueSchema.optional(),
-            not: valueSchema.optional(),
-            string_contains: z.string().optional(),
-            string_starts_with: z.string().optional(),
-            string_ends_with: z.string().optional(),
-            mode: this.makeStringModeSchema().optional(),
-            array_contains: valueSchema.optional(),
-            array_starts_with: valueSchema.optional(),
-            array_ends_with: valueSchema.optional(),
-        });
+        const valueSchema = this.makeJsonValueSchema(optional, true);
+        const operators = {
+            path: z.string().optional(),
+            equals: valueSchema.optional(),
+            not: valueSchema.optional(),
+            string_contains: z.string().optional(),
+            string_starts_with: z.string().optional(),
+            string_ends_with: z.string().optional(),
+            mode: this.makeStringModeSchema().optional(),
+            array_contains: valueSchema.optional(),
+            array_starts_with: valueSchema.optional(),
+            array_ends_with: valueSchema.optional(),
+        };
+        const filtered = this.trimFilterOperators(operators, allowedFilterKinds);
+        return Object.keys(filtered).length === 0 ? z.never() : z.strictObject(filtered);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 901 - 924, The
runtime check in makeJsonFilterSchema currently rejects all JSON filters unless
the 'Json' kind is present; update the conditional that inspects
allowedFilterKinds (from getEffectiveFilterKinds) to permit equality-only JSON
filters by allowing the schema when allowedFilterKinds has 'Equality' even if it
lacks 'Json'. In practice, change the logic that returns z.never() so it only
returns never when allowedFilterKinds exists and contains neither 'Json' nor
'Equality'; keep the rest of makeJsonFilterSchema (valueSchema, equals/not,
string_/array_ predicates, mode) unchanged.

578-613: ⚠️ Potential issue | 🟠 Major

Relation filters can still target excluded models.
Model exclusion is enforced in select/include/create/update, but makeWhereSchema still permits relation filters to excluded models. This leaks access to excluded models via where clauses.

🛠️ Suggested fix
             if (fieldDef.relation) {
                 if (withoutRelationFields) {
                     continue;
                 }
 
-                // Check if Relation filter kind is allowed
-                const allowedFilterKinds = this.getEffectiveFilterKinds(model, field);
-                if (allowedFilterKinds && !allowedFilterKinds.has('Relation')) {
-                    // Relation filters are not allowed for this field - use z.never()
-                    fieldSchema = z.never();
-                } else {
+                // Disallow relation filters to excluded models
+                if (!this.isModelAllowed(fieldDef.type)) {
+                    fieldSchema = z.never();
+                } else {
+                    // Check if Relation filter kind is allowed
+                    const allowedFilterKinds = this.getEffectiveFilterKinds(model, field);
+                    if (allowedFilterKinds && !allowedFilterKinds.has('Relation')) {
+                        fieldSchema = z.never();
+                    } else {
                         fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional());
 
                         // optional to-one relation allows null
                         fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional);
 
                         if (fieldDef.array) {
                             // to-many relation
                             fieldSchema = z.union([
                                 fieldSchema,
                                 z.strictObject({
                                     some: fieldSchema.optional(),
                                     every: fieldSchema.optional(),
                                     none: fieldSchema.optional(),
                                 }),
                             ]);
                         } else {
                             // to-one relation
                             fieldSchema = z.union([
                                 fieldSchema,
                                 z.strictObject({
                                     is: fieldSchema.optional(),
                                     isNot: fieldSchema.optional(),
                                 }),
                             ]);
                         }
+                    }
                 }
             } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 578 - 613, When
building relation filter schemas, ensure relation targets that are excluded by
the model-exclusion rules are rejected: before calling
this.makeWhereSchema(fieldDef.type, false) check the same exclusion predicate
used elsewhere (the function used for select/include/create/update exclusion —
e.g. a method like this.isModelExcluded or the existing exclusion lookup) using
the related model identifier fieldDef.type; if the related model is excluded,
set fieldSchema = z.never() and skip the lazy schema creation, otherwise proceed
with the current lazy makeWhereSchema + nullableIf + array/non-array union logic
so excluded models cannot be referenced in where filters.
🧹 Nitpick comments (5)
packages/clients/tanstack-query/test/react-sliced-client.test-d.ts (1)

9-18: Conflicting includedOperations and excludedOperations for User — intentional?

The User model config specifies 'update' in both includedOperations and excludedOperations. While this config is only used in the "works with sliced models" test (which doesn't check operation-level typing), the overlap is confusing. Consider either:

  • Adding an explicit test that validates the precedence behavior (exclude wins over include), or
  • Removing the conflict to keep the test fixture clear.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/clients/tanstack-query/test/react-sliced-client.test-d.ts` around
lines 9 - 18, The User model in the slicing config has a conflicting operation
listed in both includedOperations and excludedOperations; remove the ambiguity
by deleting 'update' from the excludedOperations array (i.e., update the slicing
-> models -> User config to keep includedOperations:
['findUnique','findMany','update'] and either make excludedOperations empty or
remove the 'update' entry) so the test fixture is clear, or alternatively add a
focused test that asserts precedence (exclude wins) using the slicing config and
operation-level type checks for User.
packages/orm/src/client/client-impl.ts (2)

485-538: isModelIncluded and isProcedureIncluded share identical structure — consider extracting a generic helper.

Both functions follow the exact same includedX / excludedX check pattern. A single generic isIncluded(included, excluded, name) helper would reduce duplication.

♻️ Example consolidation
+function isSliceIncluded(
+    included: readonly string[] | undefined,
+    excluded: readonly string[] | undefined,
+    name: string,
+): boolean {
+    if (included !== undefined && !included.includes(name)) {
+        return false;
+    }
+    if (excluded && excluded.length > 0 && excluded.includes(name)) {
+        return false;
+    }
+    return true;
+}
+
 function isModelIncluded(options: ClientOptions<SchemaDef>, model: string): boolean {
     const slicing = options.slicing;
     if (!slicing) return true;
-    const { includedModels, excludedModels } = slicing;
-    if (includedModels !== undefined) {
-        if (!includedModels.includes(model as any)) {
-            return false;
-        }
-    }
-    if (excludedModels && excludedModels.length > 0) {
-        if (excludedModels.includes(model as any)) {
-            return false;
-        }
-    }
-    return true;
+    return isSliceIncluded(
+        slicing.includedModels as readonly string[] | undefined,
+        slicing.excludedModels as readonly string[] | undefined,
+        model,
+    );
 }
 
 function isProcedureIncluded(options: ClientOptions<SchemaDef>, procedureName: string): boolean {
     const slicing = options.slicing;
     if (!slicing) return true;
-    const { includedProcedures, excludedProcedures } = slicing;
-    if (includedProcedures !== undefined) {
-        if (!(includedProcedures as readonly string[]).includes(procedureName)) {
-            return false;
-        }
-    }
-    if (excludedProcedures && excludedProcedures.length > 0) {
-        if ((excludedProcedures as readonly string[]).includes(procedureName)) {
-            return false;
-        }
-    }
-    return true;
+    return isSliceIncluded(
+        slicing.includedProcedures as readonly string[] | undefined,
+        slicing.excludedProcedures as readonly string[] | undefined,
+        procedureName,
+    );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/client-impl.ts` around lines 485 - 538, Extract a
generic helper like isIncluded(included?: readonly string[] | undefined,
excluded?: readonly string[] | undefined, name: string): boolean that implements
the existing logic (if included is !== undefined then require
included.includes(name); then if excluded present and non-empty return false if
excluded.includes(name); otherwise true). Replace the body of isModelIncluded to
call isIncluded(includedModels, excludedModels, model) and isProcedureIncluded
to call isIncluded(includedProcedures as readonly string[] | undefined,
excludedProcedures as readonly string[] | undefined, procedureName) so behavior
(including the semantic that an empty included array means include none) remains
identical and duplication is removed.

466-475: Excluded models silently return undefined — consider a descriptive runtime error.

When a model is excluded via slicing, the proxy returns undefined (line 471). A JavaScript consumer (or one ignoring types) calling client.user.findMany(...) will get a cryptic TypeError: Cannot read properties of undefined. A small guarded throw with a clear message would improve the developer experience.

💡 Proposed improvement
                 if (model) {
                     // Check if model is allowed by slicing configuration
                     if (!isModelIncluded(client.$options, model)) {
-                        return undefined;
+                        throw new Error(
+                            `Model "${model}" is not available. It has been excluded by the client's slicing configuration.`
+                        );
                     }
                     return createModelCrudHandler(client as any, model, client.inputValidator, resultProcessor);
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/client-impl.ts` around lines 466 - 475, The proxy
currently returns undefined for excluded models which leads to cryptic runtime
errors; update the block in the string-prop handling (the branch that finds
model via Object.keys(client.$schema.models).find(...)) so that after
isModelIncluded(client.$options, model) returns false you throw a clear Error
instead of returning undefined; include the model name and a hint about
slicing/selection in the message and reference the involved symbols (prop,
model, client.$options, isModelIncluded, createModelCrudHandler) so callers know
the model is intentionally excluded.
packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts (1)

9-15: Contradictory slicing config: update is both included and excluded.

The User model specifies includedOperations: ['findUnique', 'findMany', 'update'] and excludedOperations: ['update']. Per GetSlicedOperations in type-utils.ts, exclusions win (Exclude<IO, EO>), so update is effectively removed. If this is intentional to test precedence semantics, a brief comment would clarify the intent. Otherwise, remove 'update' from one of the lists to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts` around
lines 9 - 15, The User model's slicing config is contradictory—'update' appears
in both includedOperations and excludedOperations—so adjust it: either remove
'update' from includedOperations or from excludedOperations, or add a clarifying
comment that this test intentionally verifies precedence (GetSlicedOperations
uses Exclude<IO, EO> so exclusions win). Locate the User model object
(slicing.models.User) and update the includedOperations/excludedOperations
arrays accordingly or insert the explanatory comment next to them.
packages/orm/src/client/crud/validator/index.ts (1)

2128-2149: Avoid hard-coded filter-kind list.
If new kinds are added to FILTER_PROPERTY_TO_KIND, the exclusion-only path will silently allow the wrong set. Consider deriving from the mapping instead.

♻️ Suggested refactor
-        if (excluded !== undefined) {
-            if (!result) {
-                // If no inclusion list, start with all filter kinds
-                result = new Set(['Equality', 'Range', 'Like', 'Json', 'List', 'Relation']);
-            }
+        if (excluded !== undefined) {
+            if (!result) {
+                // If no inclusion list, start with all filter kinds
+                result = new Set(Object.values(FILTER_PROPERTY_TO_KIND));
+            }
             // Remove excluded kinds
             for (const kind of excluded) {
                 result.delete(kind);
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 2128 - 2149,
computeFilterKinds currently seeds the exclusion-only path with a hard-coded
list ('Equality','Range','Like','Json','List','Relation'), which will break when
FILTER_PROPERTY_TO_KIND gains new kinds; update computeFilterKinds to derive the
initial full set from the keys/values of FILTER_PROPERTY_TO_KIND (e.g., iterate
its values or keys to build the Set) so excluded-only behavior uses the
authoritative mapping; ensure you reference FILTER_PROPERTY_TO_KIND when
constructing the default Set and keep the existing logic for applying
included/excluded arrays intact.
🤖 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/constants.ts`:
- Around line 29-31: You renamed the exported AggregateOperators constant/type
which breaks downstream code; restore a deprecated alias by re-exporting the old
names (e.g., export const AGGREGATE_OPERATORS = AggregateOperators and export
type AGGREGATE_OPERATORS = AggregateOperators) alongside the new
AggregateOperators export and add a brief deprecation comment so consumers see
it’s legacy. Ensure the alias references the existing AggregateOperators symbol
(both the const and the type) rather than duplicating the array.

In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 731-746: Nested typed-JSON filters are using the inner type's
field name for slicing instead of the outer model field's slice configuration;
update the call site in validator index.ts (inside isTypeDefType branch) so
makeTypedJsonFilterSchema receives the outer model field identifier (e.g., pass
a parentField or rootName property set to the outer field variable used for
fieldSchemas key) rather than (or in addition to) name: field, and then update
makeTypedJsonFilterSchema to use that parentField/rootName when computing slice
info; ensure the symbol names to change are the call in
this.makeTypedJsonFilterSchema(...) and the makeTypedJsonFilterSchema
signature/implementation to read the parentField/rootName and prefer it for
slicing decisions.

In `@tests/e2e/orm/client-api/slicing.test.ts`:
- Around line 1706-1708: The test currently calls db.user.findMany({ where: {
name: { gte: 'A' } } }) but claims Range filters are excluded; update the
assertion to expect the call to reject instead of succeed: wrap the
db.user.findMany invocation in an assertion that it throws/rejects (e.g., use
your test runner's expect(...).rejects/throws pattern) and ensure the failure is
specifically tied to the Range filter on name (referencing the db.user.findMany
call and the where: { name: { gte: 'A' } } clause).

---

Outside diff comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 901-924: The runtime check in makeJsonFilterSchema currently
rejects all JSON filters unless the 'Json' kind is present; update the
conditional that inspects allowedFilterKinds (from getEffectiveFilterKinds) to
permit equality-only JSON filters by allowing the schema when allowedFilterKinds
has 'Equality' even if it lacks 'Json'. In practice, change the logic that
returns z.never() so it only returns never when allowedFilterKinds exists and
contains neither 'Json' nor 'Equality'; keep the rest of makeJsonFilterSchema
(valueSchema, equals/not, string_/array_ predicates, mode) unchanged.
- Around line 578-613: When building relation filter schemas, ensure relation
targets that are excluded by the model-exclusion rules are rejected: before
calling this.makeWhereSchema(fieldDef.type, false) check the same exclusion
predicate used elsewhere (the function used for select/include/create/update
exclusion — e.g. a method like this.isModelExcluded or the existing exclusion
lookup) using the related model identifier fieldDef.type; if the related model
is excluded, set fieldSchema = z.never() and skip the lazy schema creation,
otherwise proceed with the current lazy makeWhereSchema + nullableIf +
array/non-array union logic so excluded models cannot be referenced in where
filters.

---

Duplicate comments:
In `@packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts`:
- Around line 9-15: The test config for slicing has a contradictory entry where
the User model lists 'update' in both includedOperations and excludedOperations;
update the test to remove the contradiction by either removing 'update' from
excludedOperations or from includedOperations (or add an explicit comment in the
test clarifying that this duplication is intentional to test precedence
semantics), targeting the slicing -> models -> User block and the
includedOperations/excludedOperations arrays to make the intent consistent with
the other framework tests.

---

Nitpick comments:
In `@packages/clients/tanstack-query/test/react-sliced-client.test-d.ts`:
- Around line 9-18: The User model in the slicing config has a conflicting
operation listed in both includedOperations and excludedOperations; remove the
ambiguity by deleting 'update' from the excludedOperations array (i.e., update
the slicing -> models -> User config to keep includedOperations:
['findUnique','findMany','update'] and either make excludedOperations empty or
remove the 'update' entry) so the test fixture is clear, or alternatively add a
focused test that asserts precedence (exclude wins) using the slicing config and
operation-level type checks for User.

In `@packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts`:
- Around line 9-15: The User model's slicing config is contradictory—'update'
appears in both includedOperations and excludedOperations—so adjust it: either
remove 'update' from includedOperations or from excludedOperations, or add a
clarifying comment that this test intentionally verifies precedence
(GetSlicedOperations uses Exclude<IO, EO> so exclusions win). Locate the User
model object (slicing.models.User) and update the
includedOperations/excludedOperations arrays accordingly or insert the
explanatory comment next to them.

In `@packages/orm/src/client/client-impl.ts`:
- Around line 485-538: Extract a generic helper like isIncluded(included?:
readonly string[] | undefined, excluded?: readonly string[] | undefined, name:
string): boolean that implements the existing logic (if included is !==
undefined then require included.includes(name); then if excluded present and
non-empty return false if excluded.includes(name); otherwise true). Replace the
body of isModelIncluded to call isIncluded(includedModels, excludedModels,
model) and isProcedureIncluded to call isIncluded(includedProcedures as readonly
string[] | undefined, excludedProcedures as readonly string[] | undefined,
procedureName) so behavior (including the semantic that an empty included array
means include none) remains identical and duplication is removed.
- Around line 466-475: The proxy currently returns undefined for excluded models
which leads to cryptic runtime errors; update the block in the string-prop
handling (the branch that finds model via
Object.keys(client.$schema.models).find(...)) so that after
isModelIncluded(client.$options, model) returns false you throw a clear Error
instead of returning undefined; include the model name and a hint about
slicing/selection in the message and reference the involved symbols (prop,
model, client.$options, isModelIncluded, createModelCrudHandler) so callers know
the model is intentionally excluded.

In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 2128-2149: computeFilterKinds currently seeds the exclusion-only
path with a hard-coded list
('Equality','Range','Like','Json','List','Relation'), which will break when
FILTER_PROPERTY_TO_KIND gains new kinds; update computeFilterKinds to derive the
initial full set from the keys/values of FILTER_PROPERTY_TO_KIND (e.g., iterate
its values or keys to build the Set) so excluded-only behavior uses the
authoritative mapping; ensure you reference FILTER_PROPERTY_TO_KIND when
constructing the default Set and keep the existing logic for applying
included/excluded arrays intact.

@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing Implementation

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Review Tasks:

  • Understand the API slicing feature and its architecture
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Review Summary

This PR introduces a comprehensive and well-architected API slicing feature that provides fine-grained control over ORM client capabilities. The implementation demonstrates high-quality TypeScript engineering with extensive test coverage.

Strengths

1. Excellent TypeScript Implementation

The type system in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle nested slicing logic with proper precedence
  • Support for $all fallback configuration with model/field-specific overrides
  • Type-safe integration prevents accessing sliced APIs at compile-time
  • Handles edge cases like empty arrays and exclusion precedence correctly

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 1,872 lines of comprehensive tests covering all slicing scenarios
  • Excellent use of @ts-expect-error to verify TypeScript prevents accessing sliced APIs
  • Tests cover model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced properly

3. Clean Architecture & Implementation

  • Runtime enforcement: Client proxy correctly hides sliced operations and models
  • Validation integration: Proper integration with existing validation pipeline in validator/index.ts
  • Client library integration: TanStack Query clients properly respect slicing configuration
  • Backward compatibility: No breaking changes - no slicing config means all APIs available

4. Powerful Feature Set

  • Model slicing: Include/exclude entire models
  • Operation slicing: Control CRUD operations per model with $all fallbacks
  • Procedure slicing: Include/exclude custom procedures
  • Filter kind slicing: Control filter operators per field with granular configuration

🐛 Critical Bugs Found

1. JSON Filter Validation Bug (validator/index.ts:906-908)

Issue: JSON filters incorrectly require Json kind even for equality operations.

// Current code blocks ALL JSON filters if 'Json' kind not allowed
if (allowedFilterKinds && !allowedFilterKinds.has('Json')) {
    return z.never(); // ❌ This blocks `equals` and `not` which are 'Equality' kind
}

Impact: JSON fields can't use equals/not filters when Json is excluded but Equality is allowed.
Fix: Check for both Json OR Equality kinds, then use trimFilterOperators() to selectively allow operators.

2. Test Logic Error (slicing.test.ts:1706-1708)

Issue: Test claims Range filters are excluded for name field but allows gte operation to succeed.

// Configuration excludes Range for $all AND Like for name field
$all: { excludedFilterKinds: ['Range'] },
name: { excludedFilterKinds: ['Like'] },

// This should FAIL but test expects it to succeed:
await db.user.findMany({ where: { name: { gte: 'A' } } }); // ❌

Impact: Test doesn't validate the filter slicing correctly, masking potential runtime bugs.

3. Relation Filter Bypass (validator/index.ts:578-613)

Issue: Relation filters can still target excluded models in where clauses.

// Current code only checks filter kinds but not model exclusion
if (!allowedFilterKinds.has('Relation')) {
    fieldSchema = z.never();
} else {
    // ❌ This allows relations to excluded models
    fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false));
}

Impact: Could leak access to excluded models through relation filtering.

⚠️ Potential Concerns

1. TypeScript Compilation Performance

The complex conditional types may slow compilation for large schemas:

export type GetSlicedFilterKindsForField</* deeply nested conditional types */> = // ...

Recommendation: Monitor compilation times and consider type caching strategies.

2. Runtime Validation Overhead

Every operation now includes slicing validation checks in the hot path.
Mitigation: Validation schemas are cached, which helps offset the overhead.

3. Memory Usage

Each sliced client maintains its own configuration and validation schemas.
Impact: Moderate - mainly affects applications creating many client instances.

🔒 Security Assessment

Positive Security Aspects:

  • Access Control: Excellent for implementing role-based restrictions
  • Attack Surface Reduction: Prevents accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional API surface security layer

Security Recommendations:

  • Document security use cases (e.g., read-only users, tenant isolation)
  • Ensure slicing validation cannot be bypassed through edge cases
  • Consider adding audit logging for slicing violations

🎯 Recommendations

High Priority (Critical Bugs):

  1. Fix JSON filter validation to allow Equality operations on JSON fields
  2. Fix test assertion for Range filter exclusion validation
  3. Add model exclusion check for relation filters in where clauses

Medium Priority (Improvements):

  1. Add performance benchmarks for TypeScript compilation and runtime overhead
  2. Improve error messages for sliced model access (currently returns undefined)
  3. Extract common slicing logic to reduce duplication in isModelIncluded/isProcedureIncluded

Low Priority (Maintenance):

  1. Add deprecation aliases for renamed AGGREGATE_OPERATORS constant
  2. Fix hard-coded filter kinds list in computeFilterKinds() to derive from FILTER_PROPERTY_TO_KIND
  3. Resolve TanStack Query test conflicts where operations appear in both include and exclude lists

Final Verdict

This is an excellent implementation of a complex and valuable feature. The code quality is high, the architecture is sound, and the test coverage is comprehensive. The TypeScript integration is sophisticated and provides excellent developer experience.

Recommendation: Approve with required bug fixes. The critical bugs should be addressed before merge, but they don't impact the overall architecture quality.

The API slicing feature will be highly valuable for:

  • Creating specialized client variants
  • Implementing role-based access controls
  • Reducing API surface area for security
  • Building tenant-specific ORM clients

Notable Achievement: This PR successfully implements complex TypeScript conditional types that work correctly with the existing ORM type system - a significant technical accomplishment.


Review completed: 32 files analyzed including core ORM implementation, type utilities, client integrations, and comprehensive test suite.

- type simplification
- addressing PR comments
@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing Implementation

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Review Tasks:

  • Understand the API slicing feature and its architecture
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Review Summary

This PR introduces a comprehensive and well-architected API slicing feature that provides fine-grained control over ORM client capabilities. The implementation demonstrates high-quality TypeScript engineering with extensive test coverage.

Strengths

1. Excellent TypeScript Implementation

The type system in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle nested slicing logic with proper precedence
  • Support for $all fallback configuration with model/field-specific overrides
  • Type-safe integration prevents accessing sliced APIs at compile-time
  • Handles edge cases like empty arrays and exclusion precedence correctly

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 1,872 lines of comprehensive tests covering all slicing scenarios
  • Excellent use of @ts-expect-error to verify TypeScript prevents accessing sliced APIs
  • Tests cover model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced at runtime

3. Well-Architected Core Implementation

  • Clean separation between slicing logic and core ORM functionality
  • Proper integration with existing validation pipeline
  • Client proxy correctly hides sliced operations at runtime
  • Maintains backward compatibility (no slicing config = all APIs available)

4. Client Library Integration

TanStack Query integration properly respects slicing configuration:

  • Type-safe hook generation based on sliced models/operations
  • Proper TypeScript inference for sliced client APIs

⚠️ Areas of Concern

1. Complex Type Computation Performance

The type utilities perform complex conditional type computations that could impact TypeScript compilation:

// packages/orm/src/client/type-utils.ts:155-179
export type GetSlicedFilterKindsForField<
    Schema extends SchemaDef,
    Model extends GetModels<Schema>,
    Field extends string,
    Options extends QueryOptions<Schema>,
> = // deeply nested conditional types...

Impact: May slow TypeScript compilation for large schemas with extensive slicing.

2. Runtime Validation Overhead

The validator in packages/orm/src/client/crud/validator/index.ts now needs to check slicing configuration for every operation:

// Validation occurs on every query/mutation
private validate<T>(
    model: GetModels<Schema>,
    operation: CoreCrudOperations,
    getSchemaFunc: GetSchemaFunc<Schema>,
    args: unknown,
): T {
    // Additional slicing validation overhead
}

3. Memory Usage Considerations

Each sliced client maintains its own configuration and validation schemas, potentially increasing memory usage in applications creating many client instances.

🔍 Potential Edge Cases

1. Relation Validation Consistency

In packages/orm/src/client/crud/validator/index.ts, ensure relation validation properly handles cases where:

  • Parent model is included but related model is excluded
  • Complex nested include/select with mixed slicing configurations

2. Procedure Slicing Edge Case

The procedure slicing logic should handle the case where procedures reference excluded models in their parameters or return types.

🔒 Security Considerations

Positive Security Aspects:

  • Access Control: Slicing can enforce role-based restrictions (e.g., read-only users, restricted models)
  • Data Exposure Reduction: Helps prevent accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional layer of API surface reduction

Recommendations:

  • Consider adding documentation on using slicing for security purposes
  • Ensure slicing validation cannot be bypassed through raw queries or edge cases

📈 Performance Assessment

Compile-Time Performance:

  • Concern: Complex conditional types may slow TypeScript compilation
  • Mitigation: Consider type caching strategies for frequently used slicing configurations

Runtime Performance:

  • Overhead: Additional validation checks on every operation
  • Optimization: Validation schemas are cached, which helps mitigate the overhead

🎯 Final Recommendations

High Priority:

  1. Add Performance Benchmarks: Include compilation time and runtime performance tests
  2. Document Performance Implications: Add guidance on when slicing overhead matters
  3. Consider Type Optimization: Explore ways to simplify complex conditional types

Medium Priority:

  1. Security Documentation: Add examples of using slicing for access control
  2. Error Message Improvements: Ensure validation errors clearly indicate slicing restrictions
  3. Edge Case Testing: Add tests for complex relation scenarios with mixed slicing

Low Priority:

  1. Memory Usage Optimization: Consider sharing validation schemas between similar slicing configurations

Verdict

This is an excellent implementation of a complex feature. The code quality is high, test coverage is comprehensive, and the TypeScript integration is sophisticated. The slicing feature adds significant value for creating specialized clients and implementing access controls.

Recommendation: Approve with the suggestion to monitor TypeScript compilation performance in larger codebases and consider the performance optimizations mentioned above.


Notable files reviewed: 32 changed files including core ORM implementation, type utilities, client integrations, and comprehensive test suite.

Notable Achievement: This PR successfully implements complex TypeScript conditional types that work correctly with the existing ORM type system - a significant technical accomplishment.


Copy link
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/orm/src/client/crud-types.ts (1)

299-839: ⚠️ Potential issue | 🟠 Major

Add type-level guard to prevent empty filter objects when AllowedKinds is never.

When includedFilterKinds: [] results in AllowedKinds = never, the filter types currently accept {} because all filter conditionals collapse to empty objects and the not property is optional. This creates a type-safety gap where invalid filters compile successfully (caught only at runtime). Add the suggested guard at the FieldFilter entry point to ensure the type itself resolves to never when no filter kinds are allowed, preventing even empty objects from being assignable.

Suggested fix
 type FieldFilter<
     Schema extends SchemaDef,
     Model extends GetModels<Schema>,
     Field extends GetModelFields<Schema, Model>,
     Options extends QueryOptions<Schema>,
     WithAggregations extends boolean,
     AllowedKinds extends FilterKind = GetSlicedFilterKindsForField<Schema, Model, Field, Options>,
 > =
+    [AllowedKinds] extends [never]
+        ? never
+        : Field extends RelationFields<Schema, Model>
         ? // relation
           RelationFilter<Schema, Model, Field, Options, AllowedKinds>
         : FieldIsArray<Schema, Model, Field> extends true
🤖 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 299 - 839, The
FieldFilter type allows an empty object when AllowedKinds is never; update the
FieldFilter declaration to short-circuit to never when AllowedKinds is never
(i.e., add a top-level conditional like "AllowedKinds extends never ? never :
..." around the current union/conditional logic) so that FieldFilter<...,
AllowedKinds = never> resolves to never and prevents empty-filter assignment;
apply this change to the existing FieldFilter type (referencing FieldFilter and
AllowedKinds) so all downstream filters inherit the guard.
🧹 Nitpick comments (4)
packages/orm/src/client/crud/validator/index.ts (4)

831-845: Minor: internalMakeArrayFilterSchema returns z.strictObject({}) when all operators are trimmed, accepting only {}.

When both List and Equality filter kinds are excluded, all operators are stripped and the schema becomes z.strictObject({}). This accepts only an empty object {}, which is technically valid but semantically useless. Other filter methods use z.never() for the fully-excluded case (e.g., makeJsonFilterSchema at line 911). For consistency, you could short-circuit to z.never() when filteredOperators is empty.

♻️ Optional consistency fix
     // Filter operators based on allowed filter kinds
     const filteredOperators = this.trimFilterOperators(operators, allowedFilterKinds);

+    if (Object.keys(filteredOperators).length === 0) {
+        return z.never();
+    }
+
     return z.strictObject(filteredOperators);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 831 - 845,
internalMakeArrayFilterSchema currently builds operators then trims them via
trimFilterOperators and returns z.strictObject(filteredOperators); if all
operators are removed this yields z.strictObject({}) which accepts only {} —
change the function (internalMakeArrayFilterSchema) to detect when
filteredOperators is empty and return z.never() instead (matching
makeJsonFilterSchema behavior); use the existing trimFilterOperators result to
decide the short-circuit so callers get a never schema for the fully-excluded
case.

2086-2132: getEffectiveFilterKinds: Fallback chain looks correct but $all model doesn't check field-specific overrides.

The lookup precedence is:

  1. slicing.models[model].fields[field]
  2. slicing.models[model].fields.$all
  3. slicing.models.$all.fields.$all

Step 3 doesn't check slicing.models.$all.fields[field]. This is likely intentional (field names across models vary, so a global field-specific override is unusual), but worth confirming.

Also, extensive as any casts on slicing.models suggest the type definitions for the slicing config might benefit from stronger typing so these lookups are type-safe.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 2086 - 2132,
The fallback chain in getEffectiveFilterKinds misses checking for a global
model-level field override: before using allModelsConfig.fields.$all, add a
branch that checks allModelsConfig.fields[field] and, if present, return
computeFilterKinds(allModelsConfig.fields[field].includedFilterKinds,
allModelsConfig.fields[field].excludedFilterKinds); keep the existing $all
fallback after that; also remove or narrow the (slicing.models as any) casts by
using the typed slicing.models where available to make these lookups type-safe
(adjust types for models and fields if necessary).

2148-2157: Hardcoded set of all filter kinds may drift out of sync with FILTER_PROPERTY_TO_KIND.

Line 2151 hardcodes ['Equality', 'Range', 'Like', 'Json', 'List', 'Relation']. If a new filter kind is added to FILTER_PROPERTY_TO_KIND in constants.ts, this list must be updated manually.

Consider deriving it from the constants:

♻️ Derive from FILTER_PROPERTY_TO_KIND
-                result = new Set(['Equality', 'Range', 'Like', 'Json', 'List', 'Relation']);
+                result = new Set(Object.values(FILTER_PROPERTY_TO_KIND));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 2148 - 2157,
The code hardcodes the full list of filter kinds when building the initial
`result` Set (inside the block that checks `if (excluded !== undefined)`), which
can drift from `FILTER_PROPERTY_TO_KIND`; change this to derive the full set
from the constant `FILTER_PROPERTY_TO_KIND` (imported from constants.ts) and use
its values to populate `result` (e.g., create a Set from the values of
`FILTER_PROPERTY_TO_KIND`) instead of the literal array so future additions
remain in sync with `FILTER_PROPERTY_TO_KIND`.

793-820: Cache inefficiency: @cache() methods receive full FieldDef objects but only use 4 properties.

The @cache() decorator on makeEnumFilterSchema, makeArrayFilterSchema, makePrimitiveFilterSchema, and makeTypedJsonFilterSchema uses json-stable-stringify to generate cache keys from all method arguments. These methods have signatures expecting fieldInfo: FieldInfo (with only name, type, optional, array) but actual callers throughout makeWhereSchema (lines 622, 729, 760) pass complete FieldDef objects containing 14 additional properties: id, unique, updatedAt, attributes, default, omit, relation, foreignKeyFor, computed, originModel, isDiscriminator, etc.

When the cache decorator serializes these full objects, two FieldDef instances with identical filter-relevant properties but different attributes, default, or relation values generate different cache keys, causing unnecessary cache misses for semantically identical schemas.

Extract the minimal FieldInfo subset before calling these methods to keep cache keys minimal and improve hit rates:

Example extraction at call site
 // In makeWhereSchema, before calling filter schema methods:
+const fi: FieldInfo = { name: fieldDef.name, type: fieldDef.type, optional: fieldDef.optional, array: fieldDef.array };
 if (enumDef) {
     if (Object.keys(enumDef.values).length > 0) {
-        fieldSchema = this.makeEnumFilterSchema(model, fieldDef, withAggregations, ignoreSlicing);
+        fieldSchema = this.makeEnumFilterSchema(model, fi, withAggregations, ignoreSlicing);
     }
 } else if (fieldDef.array) {
-    fieldSchema = this.makeArrayFilterSchema(model, fieldDef);
+    fieldSchema = this.makeArrayFilterSchema(model, fi);
 } else if (this.isTypeDefType(fieldDef.type)) {
-    fieldSchema = this.makeTypedJsonFilterSchema(model, fieldDef);
+    fieldSchema = this.makeTypedJsonFilterSchema(model, fi);
 } else {
-    fieldSchema = this.makePrimitiveFilterSchema(model, fieldDef, withAggregations, ignoreSlicing);
+    fieldSchema = this.makePrimitiveFilterSchema(model, fi, withAggregations, ignoreSlicing);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 793 - 820, Call
sites should pass only the minimal FieldInfo ({name, type, optional, array})
instead of full FieldDef so the `@cache`() keys are stable; update calls that
currently forward a FieldDef into makeEnumFilterSchema, makeArrayFilterSchema,
makePrimitiveFilterSchema, and makeTypedJsonFilterSchema to first construct a
small object const fieldInfo = { name: field.name, type: field.type, optional:
!!field.optional, array: !!field.array } and pass that. Ensure all references
use the new FieldInfo variable (not the original FieldDef) so
json-stable-stringify used by the `@cache`() decorator serializes only those four
properties and avoids spurious cache misses.
🤖 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/validator/index.ts`:
- Around line 930-978: The cache collision stems from passing a Set to
`@cache`()-decorated methods (makeBooleanFilterSchema, makeBytesFilterSchema,
makeDateTimeFilterSchema, makeStringFilterSchema, makeNumberFilterSchema)
because stableStringify serializes Set as {} — convert the Set to a
deterministic array before it's passed into these methods. Update all call sites
that pass allowedFilterKinds to use allowedFilterKinds ?
[...allowedFilterKinds].sort() : undefined (or otherwise compute a sorted
string[] inside the caller) and, if desired, adjust the parameter types on the
above methods from Set<string>|undefined to string[]|undefined to reflect the
new input shape so the cache keys are stable and distinct per set contents.

---

Outside diff comments:
In `@packages/orm/src/client/crud-types.ts`:
- Around line 299-839: The FieldFilter type allows an empty object when
AllowedKinds is never; update the FieldFilter declaration to short-circuit to
never when AllowedKinds is never (i.e., add a top-level conditional like
"AllowedKinds extends never ? never : ..." around the current union/conditional
logic) so that FieldFilter<..., AllowedKinds = never> resolves to never and
prevents empty-filter assignment; apply this change to the existing FieldFilter
type (referencing FieldFilter and AllowedKinds) so all downstream filters
inherit the guard.

---

Duplicate comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 731-748: The nested typed-JSON branch is using the inner fieldDef
(which carries the nested field name) for filter-kind lookups and schema
creation, causing slicing rules tied to the outer field to be ignored; update
the typed-JSON handling so that calls which compute filter kinds or slicing
(e.g., getEffectiveFilterKinds) and schema factories (e.g.,
makeTypedJsonFilterSchema) use the outer field identifier (the local variable
fieldName / the outer fieldDef) instead of fieldDef.name — either pass fieldName
down to makeTypedJsonFilterSchema or have makeTypedJsonFilterSchema accept an
explicit outerFieldName parameter and use that for
getEffectiveFilterKinds(contextModel, ...); ensure all lookups that determine
slice-specific behavior reference the outer field name so nested filters honor
the parent field's slicing configuration.

---

Nitpick comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 831-845: internalMakeArrayFilterSchema currently builds operators
then trims them via trimFilterOperators and returns
z.strictObject(filteredOperators); if all operators are removed this yields
z.strictObject({}) which accepts only {} — change the function
(internalMakeArrayFilterSchema) to detect when filteredOperators is empty and
return z.never() instead (matching makeJsonFilterSchema behavior); use the
existing trimFilterOperators result to decide the short-circuit so callers get a
never schema for the fully-excluded case.
- Around line 2086-2132: The fallback chain in getEffectiveFilterKinds misses
checking for a global model-level field override: before using
allModelsConfig.fields.$all, add a branch that checks
allModelsConfig.fields[field] and, if present, return
computeFilterKinds(allModelsConfig.fields[field].includedFilterKinds,
allModelsConfig.fields[field].excludedFilterKinds); keep the existing $all
fallback after that; also remove or narrow the (slicing.models as any) casts by
using the typed slicing.models where available to make these lookups type-safe
(adjust types for models and fields if necessary).
- Around line 2148-2157: The code hardcodes the full list of filter kinds when
building the initial `result` Set (inside the block that checks `if (excluded
!== undefined)`), which can drift from `FILTER_PROPERTY_TO_KIND`; change this to
derive the full set from the constant `FILTER_PROPERTY_TO_KIND` (imported from
constants.ts) and use its values to populate `result` (e.g., create a Set from
the values of `FILTER_PROPERTY_TO_KIND`) instead of the literal array so future
additions remain in sync with `FILTER_PROPERTY_TO_KIND`.
- Around line 793-820: Call sites should pass only the minimal FieldInfo ({name,
type, optional, array}) instead of full FieldDef so the `@cache`() keys are
stable; update calls that currently forward a FieldDef into
makeEnumFilterSchema, makeArrayFilterSchema, makePrimitiveFilterSchema, and
makeTypedJsonFilterSchema to first construct a small object const fieldInfo = {
name: field.name, type: field.type, optional: !!field.optional, array:
!!field.array } and pass that. Ensure all references use the new FieldInfo
variable (not the original FieldDef) so json-stable-stringify used by the
`@cache`() decorator serializes only those four properties and avoids spurious
cache misses.

@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing Implementation

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Review Tasks:

  • Understand the API slicing feature and its architecture
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Review Summary

This PR introduces a comprehensive and well-architected API slicing feature that provides fine-grained control over ORM client capabilities. The implementation demonstrates high-quality TypeScript engineering with extensive test coverage.

Strengths

1. Excellent TypeScript Implementation

The type system in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle nested slicing logic with proper precedence
  • Support for $all fallback configuration with model/field-specific overrides
  • Type-safe integration prevents accessing sliced APIs at compile-time
  • Handles edge cases like empty arrays and exclusion precedence correctly

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 1,872 lines of comprehensive tests covering all slicing scenarios
  • Excellent use of @ts-expect-error to verify TypeScript prevents accessing sliced APIs
  • Tests cover model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced at runtime

3. Well-Architected Core Implementation

  • Clean separation between slicing logic and core ORM functionality
  • Proper integration with existing validation pipeline
  • Client proxy correctly hides sliced operations at runtime
  • Maintains backward compatibility (no slicing config = all APIs available)

4. Client Library Integration

TanStack Query integration properly respects slicing configuration:

  • Type-safe hook generation based on sliced models/operations
  • Proper TypeScript inference for sliced client APIs

🐛 Critical Bugs Found

1. JSON Filter Validation Bug (validator/index.ts:906-908)

Issue: JSON filters incorrectly require Json kind even for equality operations.

// Current code blocks ALL JSON filters if 'Json' kind not allowed
if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) {
    return z.never(); // ❌ This blocks `equals` and `not` which are 'Equality' kind
}

Impact: JSON fields can't use equals/not filters when Json is excluded but Equality is allowed.
Fix: Check for both Json OR Equality kinds, then use trimFilterOperators() to selectively allow operators.

2. Test Logic Error (slicing.test.ts:1706-1708)

Issue: Test claims Range filters are excluded for name field but allows gte operation to succeed.

// Configuration excludes Range for $all AND Like for name field
$all: { excludedFilterKinds: ['Range'] },
name: { excludedFilterKinds: ['Like'] },

// This should FAIL but test expects it to succeed:
await db.user.findMany({ where: { name: { gte: 'A' } } }); // ❌

Impact: Test doesn't validate the filter slicing correctly, masking potential runtime bugs.

3. Relation Filter Bypass (validator/index.ts:578-613)

Issue: Relation filters can still target excluded models in where clauses.

// Current code only checks filter kinds but not model exclusion
if (!allowedFilterKinds.includes('Relation')) {
    fieldSchema = z.never();
} else {
    // ❌ This allows relations to excluded models
    fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false));
}

Impact: Could leak access to excluded models through relation filtering.

⚠️ Potential Concerns

1. TypeScript Compilation Performance

The complex conditional types may slow compilation for large schemas:

export type GetSlicedFilterKindsForField</* deeply nested conditional types */> = // ...

Recommendation: Monitor compilation times and consider type caching strategies.

2. Runtime Validation Overhead

Every operation now includes slicing validation checks in the hot path.
Mitigation: Validation schemas are cached, which helps offset the overhead.

3. Memory Usage

Each sliced client maintains its own configuration and validation schemas.
Impact: Moderate - mainly affects applications creating many client instances.

🔒 Security Assessment

Positive Security Aspects:

  • Access Control: Excellent for implementing role-based restrictions
  • Attack Surface Reduction: Prevents accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional API surface security layer

Security Recommendations:

  • Document security use cases (e.g., read-only users, tenant isolation)
  • Ensure slicing validation cannot be bypassed through edge cases
  • Consider adding audit logging for slicing violations

🎯 Recommendations

High Priority (Critical Bugs):

  1. Fix JSON filter validation to allow Equality operations on JSON fields
  2. Fix test assertion for Range filter exclusion validation
  3. Add model exclusion check for relation filters in where clauses

Medium Priority (Improvements):

  1. Add performance benchmarks for TypeScript compilation and runtime overhead
  2. Improve error messages for sliced model access (currently returns undefined)
  3. Extract common slicing logic to reduce duplication in isModelIncluded/isProcedureIncluded

Low Priority (Maintenance):

  1. Add deprecation aliases for renamed AGGREGATE_OPERATORS constant
  2. Fix hard-coded filter kinds list in computeFilterKinds() to derive from FILTER_PROPERTY_TO_KIND
  3. Resolve TanStack Query test conflicts where operations appear in both include and exclude lists

Final Verdict

This is an excellent implementation of a complex and valuable feature. The code quality is high, the architecture is sound, and the test coverage is comprehensive. The TypeScript integration is sophisticated and provides excellent developer experience.

Recommendation: Approve with required bug fixes. The critical bugs should be addressed before merge, but they don't impact the overall architecture quality.

The API slicing feature will be highly valuable for:

  • Creating specialized client variants
  • Implementing role-based access controls
  • Reducing API surface area for security
  • Building tenant-specific ORM clients

Notable Achievement: This PR successfully implements complex TypeScript conditional types that work correctly with the existing ORM type system - a significant technical accomplishment.


Review completed: 32 files analyzed including core ORM implementation, type utilities, client integrations, and comprehensive test suite.


Copy link
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/orm/src/client/crud/validator/index.ts (1)

579-614: ⚠️ Potential issue | 🟠 Major

makeWhereSchema skips the isModelAllowed check for relation field targets — slicing bypass

Every other schema builder gates relation fields with isModelAllowed(fieldDef.type):

Method Guard
makeSelectSchema if (this.isModelAllowed(fieldDef.type))
makeIncludeSchema if (this.isModelAllowed(fieldDef.type))
makeCreateDataSchema if (!this.isModelAllowed(fieldDef.type)) return
makeUpdateDataSchema if (!this.isModelAllowed(fieldDef.type)) return

makeWhereSchema only checks the 'Relation' filter kind (line 586) but never calls isModelAllowed. If model B is excluded via excludedModels or absent from includedModels, a user can still write:

await client.modelA.findMany({ where: { relationToB: { id: 1 } } })

This forces the database to join an excluded model's table, defeating model slicing entirely. The impact extends to every internal makeWhereSchema call site (cursor, connect, update filter, delete filter, etc.).

🛠️ Proposed fix
             if (fieldDef.relation) {
                 if (withoutRelationFields) {
                     continue;
                 }
 
-                // Check if Relation filter kind is allowed
-                const allowedFilterKinds = this.getEffectiveFilterKinds(model, field);
-                if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) {
-                    // Relation filters are not allowed for this field - use z.never()
+                // Check if the target model is allowed by slicing configuration
+                if (!this.isModelAllowed(fieldDef.type)) {
                     fieldSchema = z.never();
                 } else {
-                    fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional());
-
-                    // optional to-one relation allows null
-                    fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional);
-
-                    if (fieldDef.array) {
-                        // to-many relation
-                        fieldSchema = z.union([
-                            fieldSchema,
-                            z.strictObject({
-                                some: fieldSchema.optional(),
-                                every: fieldSchema.optional(),
-                                none: fieldSchema.optional(),
-                            }),
-                        ]);
-                    } else {
-                        // to-one relation
-                        fieldSchema = z.union([
-                            fieldSchema,
-                            z.strictObject({
-                                is: fieldSchema.optional(),
-                                isNot: fieldSchema.optional(),
-                            }),
-                        ]);
-                    }
+                    // Check if Relation filter kind is allowed
+                    const allowedFilterKinds = this.getEffectiveFilterKinds(model, field);
+                    if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) {
+                        fieldSchema = z.never();
+                    } else {
+                        fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional());
+                        fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional);
+
+                        if (fieldDef.array) {
+                            fieldSchema = z.union([
+                                fieldSchema,
+                                z.strictObject({
+                                    some: fieldSchema.optional(),
+                                    every: fieldSchema.optional(),
+                                    none: fieldSchema.optional(),
+                                }),
+                            ]);
+                        } else {
+                            fieldSchema = z.union([
+                                fieldSchema,
+                                z.strictObject({
+                                    is: fieldSchema.optional(),
+                                    isNot: fieldSchema.optional(),
+                                }),
+                            ]);
+                        }
+                    }
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 579 - 614, The
relation branch in makeWhereSchema does not check isModelAllowed(fieldDef.type),
allowing filters into excluded models; add a guard like the other builders: if
(!this.isModelAllowed(fieldDef.type)) { fieldSchema = z.never(); } (placed
before computing allowedFilterKinds/creating the lazy schema and respecting
withoutRelationFields) so any relation targeting an excluded model yields
z.never() and prevents joins into sliced models.
🧹 Nitpick comments (1)
packages/orm/src/client/crud/validator/index.ts (1)

905-928: makeJsonFilterSchema uses all-or-nothing 'Json'-kind gating; Equality-kind operators (equals, not) are also rejected

When allowedFilterKinds excludes 'Json', the method returns z.never() (line 912) — even though equals and not are mapped to 'Equality' in FILTER_PROPERTY_TO_KIND. Every other primitive filter type (string, number, boolean, etc.) routes through trimFilterOperators/createUnionFilterSchema for per-operator control. A user who configures allowedFilterKinds = ['Equality'] on a Json field currently gets z.never() for the entire filter instead of just the Json-specific operators being stripped.

Consider applying trimFilterOperators to the JSON filter operators and falling back to z.never() only when all operators are removed:

💡 Suggested approach
 `@cache`()
 private makeJsonFilterSchema(contextModel: string | undefined, field: string, optional: boolean) {
     const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field);
 
-    // Check if Json filter kind is allowed
-    if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) {
-        return z.never();
-    }
-
     const valueSchema = this.makeJsonValueSchema(optional, true);
-    return z.strictObject({
+    const allOperators = {
         path: z.string().optional(),
         equals: valueSchema.optional(),
         not: valueSchema.optional(),
         string_contains: z.string().optional(),
         string_starts_with: z.string().optional(),
         string_ends_with: z.string().optional(),
         mode: this.makeStringModeSchema().optional(),
         array_contains: valueSchema.optional(),
         array_starts_with: valueSchema.optional(),
         array_ends_with: valueSchema.optional(),
-    });
+    };
+    const filtered = this.trimFilterOperators(allOperators, allowedFilterKinds);
+    if (Object.keys(filtered).length === 0) {
+        return z.never();
+    }
+    return z.strictObject(filtered);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 905 - 928, The
current makeJsonFilterSchema returns z.never() whenever getEffectiveFilterKinds
says 'Json' is disallowed, which blocks Equality-kind operators (equals/not)
that map to FILTER_PROPERTY_TO_KIND; instead build the JSON filter schema as you
already do (using makeJsonValueSchema/valueSchema and the operator fields), then
call trimFilterOperators (the same helper used for other primitives) with
FILTER_PROPERTY_TO_KIND and the allowed kinds to strip disallowed JSON-specific
operators, and only return z.never() if trimFilterOperators removes all operator
fields; update makeJsonFilterSchema to perform this per-operator trimming rather
than an all-or-nothing check.
🤖 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/validator/index.ts`:
- Around line 2087-2133: In getEffectiveFilterKinds, add the missing precedence
check for slicing.models.$all.fields[field] between the model-specific field
fallback and the final $all.$all fallback so that
slicing.models.$all.fields[specificField] is evaluated and passed to
computeFilterKinds before falling back to allModelsConfig.fields.$all; also
remove or replace the unsafe "as any" casts on slicing.models accesses by using
proper typing or a runtime type guard for slicing.models to preserve type safety
when reading modelConfig and allModelsConfig.

---

Outside diff comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 579-614: The relation branch in makeWhereSchema does not check
isModelAllowed(fieldDef.type), allowing filters into excluded models; add a
guard like the other builders: if (!this.isModelAllowed(fieldDef.type)) {
fieldSchema = z.never(); } (placed before computing allowedFilterKinds/creating
the lazy schema and respecting withoutRelationFields) so any relation targeting
an excluded model yields z.never() and prevents joins into sliced models.

---

Duplicate comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 731-749: The loop over typeDef.fields is passing the inner
typedef's fieldDef (whose name is the inner field, e.g., "street") into
makeEnumFilterSchema, makeArrayFilterSchema, and makePrimitiveFilterSchema,
causing getEffectiveFilterKinds(contextModel, fieldDef.name) to resolve the
wrong field; fix by creating a shallow copy of the fieldDef with the outer loop
key as the name (e.g., const outerFieldDef = { ...fieldDef, name: fieldName })
and pass outerFieldDef into makeEnumFilterSchema, makeArrayFilterSchema, and
makePrimitiveFilterSchema so slicing/filter-kind resolution uses the actual
owning model field; leave makeTypedJsonFilterSchema recursion as-is.

---

Nitpick comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 905-928: The current makeJsonFilterSchema returns z.never()
whenever getEffectiveFilterKinds says 'Json' is disallowed, which blocks
Equality-kind operators (equals/not) that map to FILTER_PROPERTY_TO_KIND;
instead build the JSON filter schema as you already do (using
makeJsonValueSchema/valueSchema and the operator fields), then call
trimFilterOperators (the same helper used for other primitives) with
FILTER_PROPERTY_TO_KIND and the allowed kinds to strip disallowed JSON-specific
operators, and only return z.never() if trimFilterOperators removes all operator
fields; update makeJsonFilterSchema to perform this per-operator trimming rather
than an all-or-nothing check.

@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing Implementation

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Review Tasks:

  • Understand the API slicing feature and its architecture
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Review Summary

This PR introduces a comprehensive and well-architected API slicing feature that provides fine-grained control over ORM client capabilities. The implementation demonstrates high-quality TypeScript engineering with extensive test coverage.

Strengths

1. Excellent TypeScript Implementation

The type system in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle nested slicing logic with proper precedence
  • Support for $all fallback configuration with model/field-specific overrides
  • Type-safe integration prevents accessing sliced APIs at compile-time
  • Handles edge cases like empty arrays and exclusion precedence correctly

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 2,001 lines of comprehensive tests covering all slicing scenarios
  • Excellent use of @ts-expect-error (99 instances) to verify TypeScript prevents accessing sliced APIs
  • Tests cover model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced at runtime

3. Well-Architected Core Implementation

  • Clean separation between slicing logic and core ORM functionality
  • Proper integration with existing validation pipeline
  • Client proxy correctly hides sliced operations at runtime
  • Maintains backward compatibility (no slicing config = all APIs available)

4. Client Library Integration

TanStack Query integration properly respects slicing configuration:

  • Type-safe hook generation based on sliced models/operations
  • Proper TypeScript inference for sliced client APIs

🐛 Critical Bugs Found

1. JSON Filter Validation Bug (validator/index.ts:910-912)

Issue: JSON filters incorrectly require Json kind even for equality operations.

// Current code blocks ALL JSON filters if 'Json' kind not allowed
if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) {
    return z.never(); // ❌ This blocks `equals` and `not` which are 'Equality' kind
}

Impact: JSON fields can't use equals/not filters when Json is excluded but Equality is allowed.
Fix: Check for both Json OR Equality kinds, then use trimFilterOperators() to selectively allow operators.

2. Test Logic Error (slicing.test.ts:1706-1708)

Issue: Test claims Range filters are excluded for name field but allows gte operation to succeed.

// Configuration excludes Range for $all AND Like for name field
$all: { excludedFilterKinds: ['Range'] },
name: { excludedFilterKinds: ['Like'] },

// This should FAIL but test expects it to succeed:
await db.user.findMany({ where: { name: { gte: 'A' } } }); // ❌

Impact: Test doesn't validate the filter slicing correctly, masking potential runtime bugs.

3. Relation Filter Bypass (validator/index.ts:585-590)

Issue: Relation filters can still target excluded models in where clauses.

// Current code only checks filter kinds but not model exclusion
if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) {
    fieldSchema = z.never();
} else {
    // ❌ This allows relations to excluded models
    fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false));
}

Impact: Could leak access to excluded models through relation filtering.

⚠️ Potential Concerns

1. TypeScript Compilation Performance

The complex conditional types may slow compilation for large schemas:

export type GetSlicedFilterKindsForField</* deeply nested conditional types */> = // ...

Recommendation: Monitor compilation times and consider type caching strategies.

2. Runtime Validation Overhead

Every operation now includes slicing validation checks in the hot path.
Mitigation: Validation schemas are cached, which helps offset the overhead.

3. Memory Usage

Each sliced client maintains its own configuration and validation schemas.
Impact: Moderate - mainly affects applications creating many client instances.

🔒 Security Assessment

Positive Security Aspects:

  • Access Control: Excellent for implementing role-based restrictions
  • Attack Surface Reduction: Prevents accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional API surface security layer

Security Recommendations:

  • Document security use cases (e.g., read-only users, tenant isolation)
  • Ensure slicing validation cannot be bypassed through edge cases
  • Consider adding audit logging for slicing violations

🎯 Recommendations

High Priority (Critical Bugs):

  1. Fix JSON filter validation to allow Equality operations on JSON fields
  2. Fix test assertion for Range filter exclusion validation
  3. Add model exclusion check for relation filters in where clauses

Medium Priority (Improvements):

  1. Add performance benchmarks for TypeScript compilation and runtime overhead
  2. Improve error messages for sliced model access (currently returns undefined)
  3. Extract common slicing logic to reduce duplication in isModelIncluded/isProcedureIncluded

Low Priority (Maintenance):

  1. Add deprecation aliases for renamed AGGREGATE_OPERATORS constant
  2. Fix hard-coded filter kinds list in computeFilterKinds() to derive from FILTER_PROPERTY_TO_KIND
  3. Resolve TanStack Query test conflicts where operations appear in both include and exclude lists

Final Verdict

This is an excellent implementation of a complex and valuable feature. The code quality is high, the architecture is sound, and the test coverage is comprehensive. The TypeScript integration is sophisticated and provides excellent developer experience.

Recommendation: Approve with required bug fixes. The critical bugs should be addressed before merge, but they don't impact the overall architecture quality.

The API slicing feature will be highly valuable for:

  • Creating specialized client variants
  • Implementing role-based access controls
  • Reducing API surface area for security
  • Building tenant-specific ORM clients

Notable Achievement: This PR successfully implements complex TypeScript conditional types that work correctly with the existing ORM type system - a significant technical accomplishment.


Review completed: 32 files analyzed including core ORM implementation, type utilities, client integrations, and comprehensive test suite.


View job

Copy link
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: 4

🧹 Nitpick comments (1)
packages/orm/src/client/crud/validator/index.ts (1)

2156-2176: Minor inefficiency in computeFilterKinds exclusion loop.

Each excluded kind triggers a full filter() over the result array, creating a new array each time: O(n·m). Since these sets are very small (typically ≤6 kinds), this is negligible. A single pass would be cleaner though:

♻️ Optional single-pass alternative
         if (excluded !== undefined) {
             if (!result) {
                 // If no inclusion list, start with all filter kinds
                 result = [...this.allFilterKinds];
             }
             // Remove excluded kinds
-            for (const kind of excluded) {
-                result = result.filter((k) => k !== kind);
-            }
+            const excludedSet = new Set(excluded);
+            result = result.filter((k) => !excludedSet.has(k));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/index.ts` around lines 2156 - 2176,
The exclusion loop creates a new array per excluded kind (O(n·m)); modify
computeFilterKinds to do exclusion in a single pass by building an exclusion set
from the excluded parameter and then applying a single filter over result (or
this.allFilterKinds when included is undefined) to remove any kind present in
that set; update references in computeFilterKinds to use the temporary Set and a
single result.filter((k) => !excludeSet.has(k)) so the behavior and return value
remain identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/orm/client-api/slicing.test.ts`:
- Line 1015: The validation expectation string in the test call to
toBeRejectedByValidation is missing the closing double-quote; update the
argument passed to toBeRejectedByValidation (the array element currently '"gte')
to include the closing quote so it matches the other occurrences (i.e., '"gte"')
to keep consistency and correct string syntax in slicing.test.ts.
- Around line 1862-1864: The test comment and variable are misleading: update
the comment that currently reads "Level 2 – User.email: Equality + Range" to
reference User.age instead, and rename the variable emailRange to ageRange where
the query const emailRange = await db.user.findMany({ where: { age: { gte: 20 }
} }); is used (and update any corresponding expect(...) assertions) so the
variable name and comment correctly reflect that db.user.findMany is filtering
on age.
- Around line 970-974: The "Range filters" block is misleading: db.user.findMany
is called with an empty where (where: {}) so no range is tested; update the test
by either changing the comment to reflect it's a generic findMany, or add an
actual range filter (e.g., replace where: {} with where: { age: { gte: 20 } } or
another appropriate field) so rangeResult actually exercises range filtering and
the expect remains valid; locate the db.user.findMany call and the rangeResult
variable to apply the fix.
- Around line 1371-1376: The test calls db.post.findMany with a non-existent
relation filter "user"; change the where clause to use the actual relation name
"author" (e.g., where: { author: { is: { email: 'test' } } }) and update the
expected validation error from '"user"' to '"author"' in the
toBeRejectedByValidation assertion so the test references db.post.findMany and
the Post->User relation correctly.

---

Duplicate comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 731-748: The nested typed-JSON branch is passing the inner
sub-field's FieldInfo (fieldDef) to filter-schema builders, so
getEffectiveFilterKinds(model, fieldDef.name) looks up the wrong slice key;
instead, when iterating Object.entries(typeDef.fields) create an adjusted
FieldInfo that preserves the original fieldDef properties but sets name =
fieldName (the outer model field) and pass that adjusted FieldInfo into
makeTypedJsonFilterSchema, makeEnumFilterSchema, makeArrayFilterSchema, and
makePrimitiveFilterSchema (still using contextModel and the same optional()
wrapping) so slicing uses the outer field name for lookups.

In `@tests/e2e/orm/client-api/slicing.test.ts`:
- Around line 1693-1708: The comment above the tests is incorrect and the Range
assertion is missing: update the comment to reflect that the name field allows
Range filters because name: { excludedFilterKinds: ['Like'] } overrides $all
excluding Range, and replace the discarded db.user.findMany({ where: { name: {
gte: 'A' } } }) call with an explicit assertion (e.g., assign result from
db.user.findMany or use expect(...) to check length/contents) so the test
verifies that gte succeeds; references: db.user.findMany, name field,
excludedFilterKinds, contains, gte.

---

Nitpick comments:
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 2156-2176: The exclusion loop creates a new array per excluded
kind (O(n·m)); modify computeFilterKinds to do exclusion in a single pass by
building an exclusion set from the excluded parameter and then applying a single
filter over result (or this.allFilterKinds when included is undefined) to remove
any kind present in that set; update references in computeFilterKinds to use the
temporary Set and a single result.filter((k) => !excludeSet.has(k)) so the
behavior and return value remain identical.

@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing Implementation

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Review Tasks:

  • Read repository guidelines and understand the codebase
  • Understand the API slicing feature and its architecture
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Review Summary

This PR introduces a comprehensive and well-architected API slicing feature that provides fine-grained control over ORM client capabilities. The implementation demonstrates high-quality TypeScript engineering with extensive test coverage.

Strengths

1. Excellent TypeScript Implementation

The type system in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle nested slicing logic with proper precedence
  • Support for $all fallback configuration with model/field-specific overrides
  • Type-safe integration prevents accessing sliced APIs at compile-time
  • Handles edge cases like empty arrays and exclusion precedence correctly

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 2,001 lines of comprehensive tests covering all slicing scenarios
  • 99 instances of @ts-expect-error to verify TypeScript prevents accessing sliced APIs
  • 64 test cases covering model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced at runtime

3. Well-Architected Core Implementation

  • Clean separation between slicing logic and core ORM functionality
  • Proper integration with existing validation pipeline
  • Client proxy correctly hides sliced operations at runtime
  • Maintains backward compatibility (no slicing config = all APIs available)

4. Client Library Integration

TanStack Query integration properly respects slicing configuration:

  • Type-safe hook generation based on sliced models/operations
  • Proper TypeScript inference for sliced client APIs

🐛 Critical Bugs Found

1. JSON Filter Validation Bug (validator/index.ts:910-912)

Issue: JSON filters incorrectly require Json kind even for equality operations.

// Current code blocks ALL JSON filters if 'Json' kind not allowed
if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) {
    return z.never(); // ❌ This blocks `equals` and `not` which are 'Equality' kind
}

Impact: JSON fields can't use equals/not filters when Json is excluded but Equality is allowed.
Fix: Check for both Json OR Equality kinds, then use trimFilterOperators() to selectively allow operators.

2. Test Logic Error (slicing.test.ts:1706-1708)

Issue: Test claims Range filters are excluded for name field but allows gte operation to succeed.

// Configuration excludes Range for $all AND Like for name field
$all: { excludedFilterKinds: ['Range'] },
name: { excludedFilterKinds: ['Like'] },

// This should FAIL but test expects it to succeed:
await db.user.findMany({ where: { name: { gte: 'A' } } }); // ❌

Impact: Test doesn't validate the filter slicing correctly, masking potential runtime bugs.

3. Relation Filter Bypass (validator/index.ts:585-590)

Issue: Relation filters can still target excluded models in where clauses.

// Current code only checks filter kinds but not model exclusion
if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) {
    fieldSchema = z.never();
} else {
    // ❌ This allows relations to excluded models
    fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false));
}

Impact: Could leak access to excluded models through relation filtering.

⚠️ Potential Concerns

1. TypeScript Compilation Performance

The complex conditional types may slow compilation for large schemas:

export type GetSlicedFilterKindsForField</* deeply nested conditional types */> = // ...

Recommendation: Monitor compilation times and consider type caching strategies.

2. Runtime Validation Overhead

Every operation now includes slicing validation checks in the hot path.
Mitigation: Validation schemas are cached, which helps offset the overhead.

3. Memory Usage

Each sliced client maintains its own configuration and validation schemas.
Impact: Moderate - mainly affects applications creating many client instances.

🔒 Security Assessment

Positive Security Aspects:

  • Access Control: Excellent for implementing role-based restrictions
  • Attack Surface Reduction: Prevents accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional API surface security layer

Security Concerns Found:

  • Unique Field Bypass: Unique fields completely bypass filter slicing (medium severity)
  • Incomplete Nested Validation: Complex nested structures may not be fully validated (low-medium severity)

Security Recommendations:

  • Document security use cases (e.g., read-only users, tenant isolation)
  • Ensure slicing validation cannot be bypassed through edge cases
  • Consider adding audit logging for slicing violations

🎯 Recommendations

High Priority (Critical Bugs):

  1. Fix JSON filter validation to allow Equality operations on JSON fields
  2. Fix test assertion for Range filter exclusion validation
  3. Add model exclusion check for relation filters in where clauses

Medium Priority (Improvements):

  1. Add performance benchmarks for TypeScript compilation and runtime overhead
  2. Improve error messages for sliced model access (currently returns undefined)
  3. Extract common slicing logic to reduce duplication in isModelIncluded/isProcedureIncluded

Low Priority (Maintenance):

  1. Add deprecation aliases for renamed AGGREGATE_OPERATORS constant
  2. Fix hard-coded filter kinds list in computeFilterKinds() to derive from FILTER_PROPERTY_TO_KIND
  3. Resolve TanStack Query test conflicts where operations appear in both include and exclude lists

Final Verdict

This is an excellent implementation of a complex and valuable feature. The code quality is high, the architecture is sound, and the test coverage is comprehensive. The TypeScript integration is sophisticated and provides excellent developer experience.

Recommendation: Approve with required bug fixes. The critical bugs should be addressed before merge, but they don't impact the overall architecture quality.

The API slicing feature will be highly valuable for:

  • Creating specialized client variants
  • Implementing role-based access controls
  • Reducing API surface area for security
  • Building tenant-specific ORM clients

Notable Achievement: This PR successfully implements complex TypeScript conditional types that work correctly with the existing ORM type system - a significant technical accomplishment.


Review completed: 32 files analyzed including core ORM implementation, type utilities, client integrations, and comprehensive test suite.


@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


PR Review: ORM API Slicing Implementation

I'll provide a comprehensive review of this PR that introduces ORM API slicing functionality.

Review Tasks:

  • Read repository guidelines and understand the codebase
  • Understand the API slicing feature and its architecture
  • Review core ORM implementation changes
  • Analyze type utilities and TypeScript implementation
  • Review TanStack Query client integrations
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Assess performance implications
  • Review security considerations
  • Provide final feedback and recommendations

Compiling comprehensive review findings...


Review Summary

This PR introduces a comprehensive and well-architected API slicing feature that provides fine-grained control over ORM client capabilities. The implementation demonstrates high-quality TypeScript engineering with extensive test coverage.

Strengths

1. Excellent TypeScript Implementation

The type system in packages/orm/src/client/type-utils.ts is exceptionally well-designed:

  • Complex conditional types correctly handle nested slicing logic with proper precedence
  • Support for $all fallback configuration with model/field-specific overrides
  • Type-safe integration prevents accessing sliced APIs at compile-time
  • Handles edge cases like empty arrays and exclusion precedence correctly

2. Comprehensive Test Coverage

The test suite in tests/e2e/orm/client-api/slicing.test.ts is thorough:

  • 2,001 lines of comprehensive tests covering all slicing scenarios
  • Excellent use of @ts-expect-error (99 instances) to verify TypeScript prevents accessing sliced APIs
  • Tests cover model inclusion/exclusion, operation slicing, procedure slicing, and filter kind slicing
  • Runtime validation tests ensure sliced configurations are enforced at runtime

3. Well-Architected Core Implementation

  • Clean separation between slicing logic and core ORM functionality
  • Proper integration with existing validation pipeline
  • Client proxy correctly hides sliced operations at runtime
  • Maintains backward compatibility (no slicing config = all APIs available)

4. Client Library Integration

TanStack Query integration properly respects slicing configuration:

  • Type-safe hook generation based on sliced models/operations
  • Proper TypeScript inference for sliced client APIs

🐛 Critical Bugs Found

1. JSON Filter Validation Bug (validator/index.ts:910-912)

Issue: JSON filters incorrectly require Json kind even for equality operations.

// Current code blocks ALL JSON filters if 'Json' kind not allowed
if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) {
    return z.never(); // ❌ This blocks `equals` and `not` which are 'Equality' kind
}

Impact: JSON fields can't use equals/not filters when Json is excluded but Equality is allowed.
Fix: Check for both Json OR Equality kinds, then use trimFilterOperators() to selectively allow operators.

2. Test Logic Error (slicing.test.ts:1706-1708)

Issue: Test claims Range filters are excluded for name field but allows gte operation to succeed.

// Configuration excludes Range for $all AND Like for name field
$all: { excludedFilterKinds: ['Range'] },
name: { excludedFilterKinds: ['Like'] },

// This should FAIL but test expects it to succeed:
await db.user.findMany({ where: { name: { gte: 'A' } } }); // ❌

Impact: Test doesn't validate the filter slicing correctly, masking potential runtime bugs.

3. Relation Filter Bypass (validator/index.ts:585-590)

Issue: Relation filters can still target excluded models in where clauses.

// Current code only checks filter kinds but not model exclusion
if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) {
    fieldSchema = z.never();
} else {
    // ❌ This allows relations to excluded models
    fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false));
}

Impact: Could leak access to excluded models through relation filtering.

⚠️ Potential Concerns

1. TypeScript Compilation Performance

The complex conditional types may slow compilation for large schemas:

export type GetSlicedFilterKindsForField</* deeply nested conditional types */> = // ...

Recommendation: Monitor compilation times and consider type caching strategies.

2. Runtime Validation Overhead

Every operation now includes slicing validation checks in the hot path.
Mitigation: Validation schemas are cached, which helps offset the overhead.

3. Memory Usage

Each sliced client maintains its own configuration and validation schemas.
Impact: Moderate - mainly affects applications creating many client instances.

🔒 Security Assessment

Positive Security Aspects:

  • Access Control: Excellent for implementing role-based restrictions
  • Attack Surface Reduction: Prevents accidental exposure of sensitive models/operations
  • Defense in Depth: Adds an additional API surface security layer

Security Recommendations:

  • Document security use cases (e.g., read-only users, tenant isolation)
  • Ensure slicing validation cannot be bypassed through edge cases
  • Consider adding audit logging for slicing violations

🎯 Recommendations

High Priority (Critical Bugs):

  1. Fix JSON filter validation to allow Equality operations on JSON fields
  2. Fix test assertion for Range filter exclusion validation
  3. Add model exclusion check for relation filters in where clauses

Medium Priority (Improvements):

  1. Add performance benchmarks for TypeScript compilation and runtime overhead
  2. Improve error messages for sliced model access (currently returns undefined)
  3. Extract common slicing logic to reduce duplication in isModelIncluded/isProcedureIncluded

Low Priority (Maintenance):

  1. Add deprecation aliases for renamed AGGREGATE_OPERATORS constant
  2. Fix hard-coded filter kinds list in computeFilterKinds() to derive from FILTER_PROPERTY_TO_KIND
  3. Resolve TanStack Query test conflicts where operations appear in both include and exclude lists

Final Verdict

This is an excellent implementation of a complex and valuable feature. The code quality is high, the architecture is sound, and the test coverage is comprehensive. The TypeScript integration is sophisticated and provides excellent developer experience.

Recommendation: Approve with required bug fixes. The critical bugs should be addressed before merge, but they don't impact the overall architecture quality.

The API slicing feature will be highly valuable for:

  • Creating specialized client variants
  • Implementing role-based access controls
  • Reducing API surface area for security
  • Building tenant-specific ORM clients

Notable Achievement: This PR successfully implements complex TypeScript conditional types that work correctly with the existing ORM type system - a significant technical accomplishment.


Review completed: 32 files analyzed including core ORM implementation, type utilities, client integrations, and comprehensive test suite.


@claude
Copy link
Contributor

claude bot commented Feb 18, 2026

Claude finished @ymc9's task —— View job


I'll analyze this and get back to you.

@ymc9 ymc9 merged commit da3b0af into dev Feb 18, 2026
7 checks passed
@ymc9 ymc9 deleted the feat/api-slicing branch February 18, 2026 15:45
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.

1 participant

Comments