Skip to content

merge dev to main (v3.7.0)#2667

Open
ymc9 wants to merge 25 commits into
mainfrom
dev
Open

merge dev to main (v3.7.0)#2667
ymc9 wants to merge 25 commits into
mainfrom
dev

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented May 12, 2026

Summary by CodeRabbit

  • New Features

    • Fuzzy (@fuzzy) and full‑text (@fullText) search support (Postgres)
    • New fully‑typed fetch client package for RPC-style API access
    • Sequential transactions: $transaction.useSequential()
    • Checked/unchecked create/update input types for finer control and safety
  • Bug Fixes / Improvements

    • Better DateTime handling (time-only/DB-specific formats)
    • Improved JSON/null sentinel serialization/deserialization
    • Endpoint normalization and broader client typing/UX improvements

Review Change Stack

ymc9 and others added 24 commits April 29, 2026 14:44
…to UpdateInput (#2627)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…er the wire

Register SuperJSON custom serializers for the three JSON null sentinels in
both the client-helpers fetch layer and the server's registerCustomSerializers,
so they survive HTTP round-trips when used as query filters or mutation input.
Re-export the singletons from all tanstack-query framework entry points
(react, vue, svelte) for convenient user access.

Fixes #2278

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com>
…tions (#2645)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…stackhq/orm types (#2641)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…2647) (#2649)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…signature (#2650)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `pnpm test:generate` to the build scripts of @zenstackhq/orm,
@zenstackhq/schema, and @zenstackhq/zod so test fixtures are
regenerated as part of `pnpm build`. Includes the resulting
regeneration of packages/schema/test/schema/schema.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a Prisma-style full-text search capability gated by a new
field-level `@fullText` ZModel attribute. PostgreSQL only — MySQL/SQLite
throw NotSupported. Mirrors the existing `@fuzzy` design.

- Filter operator: `where: { title: { fts: { search, config? } } }`
  emits `to_tsvector(field) @@ to_tsquery(query)` (or with a `::regconfig`
  cast when `config` is provided; otherwise Postgres uses the database's
  `default_text_search_config`).
- OrderBy operator: `_ftsRelevance: { fields, search, config?, sort }`
  emits a single `ts_rank(...)`. Multi-field combines fields with
  `concat_ws(' ', ...)` so AND queries match terms across fields
  (matches Prisma's behavior).
- Type-level gating: the `fts` operator and `_ftsRelevance` orderBy
  appear only on String fields annotated with `@fullText` and only when
  the schema's provider is `postgresql`. Slicing's `'FullText'` filter
  kind controls availability of the runtime operator.
- Cursor pagination is rejected when combined with `_ftsRelevance`
  (parallel to `_fuzzyRelevance`).

Also refactors `buildOrderBy` to dispatch to small per-branch helpers
(`applyScalarOrderBy`, `applyAggregationOrderBy`, `applyRelationOrderBy`,
`applyFuzzyRelevanceOrderBy`, `applyFtsRelevanceOrderBy`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`to_tsvector(NULL)` returns NULL and `ts_rank(NULL, ...)` therefore
returns NULL — under Postgres's default `NULLS FIRST` for `ORDER BY DESC`
this would surface NULL-valued rows ahead of any matching ones, an
asymmetry with the multi-field path where `concat_ws(' ', ...)` already
skips NULLs and yields a 0.0 rank. Coalescing the field to `''` aligns
the two paths.

Adds `subtitle: String? @fullText` to the test fixture and a regression
test that orders a NULL-subtitle row against a matching one — without
the fix, the NULL row ranks first under DESC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#2655)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com>
)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…opt-in (#2631) (#2632)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 708dad9c-3934-4a22-b54f-eb06fc80cf29

📥 Commits

Reviewing files that changed from the base of the PR and between 1a4de21 and 79498da.

📒 Files selected for processing (1)
  • packages/clients/fetch-client/test/fetch-client.test.ts

📝 Walkthrough

Walkthrough

Adds fuzzy/full-text search and relevance ordering, checked/unchecked CRUD inputs, a typed fetch client, transaction typing and $transaction hooks in TanStack bindings, @fuzzy/@fulltext attrs and validators, SuperJSON null-sentinel serializers, build/config updates, and extensive tests and generated schemas.

Changes

3.7.0 Unified Update

Core checkpoints

Layer / File(s) Summary
Core contracts and types
packages/orm/src/client/contract.ts, packages/orm/src/client/crud-types.ts, packages/clients/client-helpers/src/transaction.ts, packages/clients/client-helpers/src/types.ts
Introduces ExtQueryArgs/ExtResult markers, CrudArgs/CrudReturn maps, TransactionOperation/TransactionResults, and related type machinery.
ORM dialects and operations
packages/orm/src/client/crud/dialects/*, packages/orm/src/client/crud/operations/base.ts, packages/orm/src/client/client-impl.ts
Adds fuzzy/fts filter/order support, provider gating, DateTime/time formatting, threading fieldDef into transformInput, and runtime promise validation for sequential transactions.
Zod factory and JSON/Date handling
packages/orm/src/client/zod/factory.ts, packages/orm/src/utils/type-utils.ts, packages/zod/src/types.ts
Adds coerced DateTime schema, fuzzy/fts filter schemas and relevance orderBy inputs, and widens Json typing to include null.
Fetch client and client-helpers runtime
packages/clients/fetch-client/src/index.ts, packages/clients/client-helpers/src/fetch.ts, packages/clients/client-helpers/src/constants.ts
New fetch client with typed model ops and $transaction, SuperJSON serializers for null sentinels, constants for $procs and $transaction.
TanStack client bindings (react/svelte/vue)
packages/clients/tanstack-query/src/{react,svelte,vue}.ts, packages/clients/tanstack-query/src/common/*
Threads ExtQueryArgs/ExtResult through hooks, adds $transaction.useSequential, endpoint normalization, transaction mutation and onSuccess invalidation helpers.
Language attrs and validators
packages/language/res/stdlib.zmodel, packages/language/src/validators/*, packages/language/src/utils.ts
Adds @fuzzy and @fullText attribute declarations and validators that require PostgreSQL provider; adds getDataSourceProvider helper.
Server and adapters
packages/server/src/api/utils.ts, packages/auth-adapters/better-auth/*
Registers SuperJSON serializers for DbNull/JsonNull/AnyNull, adjusts better-auth to lazy import schema-generator and build config to preserve laziness.
Tests, fixtures, and samples
tests/e2e/**, packages/clients/*/test/**, samples/**
Extensive new/updated tests for fuzzy/fts, transactions, fetch-client, TanStack bindings, checked/unchecked inputs, plus many generated schema fixtures and typing tests.
Build/config and version bumps
pnpm-workspace.yaml, many package.json, tsdown.config.ts, tsconfig.json, tooling configs
Bumps package versions to 3.7.0, updates workspace TypeScript to ^6.0.3, and adds/adjusts per-package build entries and test configs.

Estimated code review effort:
🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs:

Poem:

I thump my paw—version hops anew,
Fuzzy fields and full-text brew.
Clients fetch, transactions queue,
Typed returns preserved for you.
Nulls wear badges, tests run through—
3.7.0, a carrot debut! 🥕✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

Claude finished @ymc9's task —— View job


Code Review for v3.7.0 Release PR

This is a substantial release with 100+ changed files including new packages, major enhancements, and important bug fixes. I've conducted a comprehensive review focusing on code quality, security, performance, and potential issues.

Review Tasks:

  • Read repository guidelines and setup
  • Review new fetch-client package
  • Analyze TanStack Query enhancements
  • Examine ORM improvements and changes
  • Check security implications
  • Assess performance considerations
  • Review test coverage
  • Provide overall recommendations

✅ Strengths and Positive Changes

🎯 New Fetch Client Package

  • Well-structured architecture: The new @zenstackhq/fetch-client provides a clean, lightweight alternative to the TanStack Query client
  • Strong TypeScript support: Excellent type inference and safety with proper generic constraints
  • Comprehensive test coverage: 600+ lines of tests covering CRUD operations, transactions, and edge cases
  • Good error handling: Custom CrudError class with specific error codes for better debugging

🔧 ORM Enhancements

📊 TanStack Query Improvements

  • Better invalidation patterns: Improved query invalidation logic with predicate matching
  • Transaction integration: New transaction hooks with optimistic updates
  • Enhanced TypeScript types: Better inference for sliced operations and procedures

⚠️ Areas of Concern

🔒 Security Considerations

  1. Fetch client endpoint validation: While there's URL validation in normalizeEndpoint(), consider adding:

    • Protocol whitelist (only https in production)
    • Domain validation for allowed endpoints
    • Rate limiting considerations
  2. Input sanitization: The fetch client marshals data directly to JSON without additional sanitization. Consider validating against schema before transmission.

🚀 Performance Considerations

  1. Type complexity: The extensive use of mapped types and conditional types in crud-types.ts may impact TypeScript compilation performance
  2. Bundle size: The new fetch client adds dependency on several packages - consider tree-shaking optimization
  3. Query caching: The TanStack Query integration could benefit from more aggressive caching strategies

🐛 Potential Issues

  1. Error handling in fetch client:

    // In packages/clients/fetch-client/src/index.ts:208
    if (result == null) {
        throw new CrudError(CrudErrorCode.NotFound, `No ${modelName} found`, modelName);
    }

    This only checks for null but should also handle undefined explicitly.

  2. URL construction: The makeUrl function concatenates paths without proper encoding - potential for injection if args contain special characters.

  3. Transaction rollback: The sequential transaction implementation doesn't appear to have explicit rollback mechanisms for partial failures.

📝 Code Quality Observations

✅ Good Practices

  • Consistent error handling patterns
  • Proper TypeScript strict mode compliance
  • Good separation of concerns between packages
  • Comprehensive test coverage for new features

📋 Minor Issues

  1. Documentation: Some complex type definitions lack JSDoc comments
  2. Magic strings: Consider extracting more constants (e.g., HTTP methods, content types)
  3. Async/await consistency: Mix of Promise chains and async/await in some files

🧪 Test Coverage Assessment

Excellent coverage overall:

  • New fetch client: 600+ lines of comprehensive tests
  • Regression tests: Multiple new test cases for resolved issues
  • Transaction functionality: Well-tested with edge cases
  • Type-level testing: Good use of .test-d.ts files

💡 Recommendations

High Priority

  1. Add input validation to fetch client for untrusted data
  2. Implement explicit transaction rollback handling
  3. Add protocol/domain validation for endpoint security

Medium Priority

  1. Optimize TypeScript compilation by simplifying complex mapped types where possible
  2. Add performance benchmarks for the new fetch client vs existing solutions
  3. Consider lazy loading for optional dependencies

Low Priority

  1. Improve documentation for complex type definitions
  2. Extract magic constants for better maintainability
  3. Standardize async/await usage across the codebase

🎯 Overall Assessment

This is a high-quality release that adds significant value:

  • Architecture: Well-designed with proper separation of concerns
  • TypeScript: Excellent type safety and inference
  • Testing: Comprehensive coverage of new features
  • Backwards compatibility: Changes appear non-breaking
  • ⚠️ Security: Good foundation but could be strengthened
  • ⚠️ Performance: Should monitor TypeScript compilation impact

Recommendation: Approve with minor security hardening

The benefits of this release significantly outweigh the concerns. The new fetch client provides a valuable lightweight alternative, the bug fixes address important user pain points, and the overall code quality is high.


Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
package.json (1)

17-17: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update pnpm version to match coding guidelines.

The packageManager field specifies pnpm@10.33.0, but coding guidelines require pnpm@10.12.1. Using a different pnpm version may lead to inconsistencies in dependency resolution and lockfile generation.

As per coding guidelines, the pnpm version should be pinned to pnpm@10.12.1.

📦 Proposed fix
-    "packageManager": "pnpm@10.33.0",
+    "packageManager": "pnpm@10.12.1",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 17, Update the packageManager entry in package.json
from "pnpm@10.33.0" to the required "pnpm@10.12.1" to comply with coding
guidelines; locate the packageManager field in package.json and change its value
accordingly, then regenerate/install dependencies with the updated pnpm version
(e.g., run pnpm install using pnpm@10.12.1) to ensure the lockfile and
node_modules match the pinned manager.
🧹 Nitpick comments (3)
packages/clients/tanstack-query/test/react/optimistic-mutation.test.tsx (1)

263-264: ⚡ Quick win

Replace TODO with an explicit assertion to lock behavior.

This TODO leaves expected nested relation behavior undefined. Please assert either inclusion of category object or explicit absence, so regressions are detectable.

If you want, I can draft the exact assertion once you confirm the intended behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clients/tanstack-query/test/react/optimistic-mutation.test.tsx`
around lines 263 - 264, The test in optimistic-mutation.test.tsx leaves a TODO
instead of a concrete assertion about nested relation behavior; replace the
commented line near the optimistic mutation assertion with an explicit
expectation that either checks for the presence of the category object (e.g.,
that the mutation result or cache contains category: { $optimistic: true, id:
'1', name: 'category1' }) or explicitly asserts its absence, so the test locks
the intended behavior; update the assertion surrounding the mutation result (the
optimistic mutation assertion in the test) to reflect the chosen behavior and
remove the TODO comment.
packages/orm/src/client/client-impl.ts (1)

271-276: ⚡ Quick win

Return the validated variable instead of re-accessing the property.

Line 275 re-accesses (promise as any).cb instead of returning the already-validated cb variable from line 273, causing a redundant property lookup.

♻️ Proposed fix
 private getPromiseCallback(promise: ZenStackPromise<any>) {
     invariant((promise as any).cb, 'Invalid ZenStackPromise, missing cb property');
     const cb = (promise as any).cb;
     invariant(typeof cb === 'function', 'Invalid ZenStackPromise, cb property is not a function');
-    return (promise as any).cb;
+    return cb;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orm/src/client/client-impl.ts` around lines 271 - 276, In
getPromiseCallback, you're re-accessing (promise as any).cb on return instead of
returning the validated local variable; change the return to return the
already-validated cb from line where const cb is declared to avoid redundant
property lookup and ensure the function returns the validated function value
(references: getPromiseCallback, variable cb, ZenStackPromise).
packages/clients/fetch-client/test/fetch-client.test.ts (1)

444-463: ⚡ Quick win

Use real special values in the SuperJSON tests.

These cases only pass plain strings, so they never hit the serialization path the test names are asserting. A regression in Decimal/Date/SuperJSON marshaling would still pass here.

Also applies to: 558-567

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clients/fetch-client/test/fetch-client.test.ts` around lines 444 -
463, Tests named "serializes args with special types into query string" and
"marshals args with Decimal into POST body" are using plain strings so they
never exercise the SuperJSON/Decimal serialization paths; update those tests to
pass real special values (e.g., Date objects and Decimal instances) instead of
strings when calling client.user.findMany and client.user.createMany, and
similarly fix the other affected test block later in the file that checks
Decimal/Date marshaling; ensure mockFetch assertions still read the URL query
(?q=) and the POST init.body JSON after these real special values are passed so
the serialization path is actually exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/clients/fetch-client/src/index.ts`:
- Around line 159-169: normalizeEndpoint currently accepts fully qualified URLs
that include query strings or fragments which later cause malformed concatenated
paths; update normalizeEndpoint to reject endpoints containing '?' or '#' by
validating the parsed URL (use new URL(endpoint)) and throwing an Error if
url.search or url.hash are non-empty, keeping the existing checks for non-empty
string and trailing slash trimming; reference the function normalizeEndpoint and
the URL constructor to locate and implement this change.

In `@packages/clients/fetch-client/test/fetch-client.test.ts`:
- Around line 22-29: The tests replace globalThis.fetch with mockFetch but never
restore it, risking cross-test pollution; update the setup/teardown around
beforeEach/afterEach to save the original fetch (e.g., const originalFetch =
globalThis.fetch) before assigning mockFetch in beforeEach and then restore
globalThis.fetch = originalFetch in afterEach, and keep vi.resetAllMocks() there
as well; refer to the existing beforeEach, afterEach, and mockFetch symbols to
locate where to add the save/restore.

In `@packages/clients/tanstack-query/test/react/helpers.tsx`:
- Around line 24-26: The URL builder currently skips serializing valid falsy
values because it checks `if (args)`; update the condition to explicitly test
for undefined (e.g., `if (args !== undefined)`) so values like null, 0, false,
or empty string are preserved and still get serialized with
`encodeURIComponent(JSON.stringify(args))` — change the `if (args)` check around
the `r += \`?q=${encodeURIComponent(JSON.stringify(args))}\`` line accordingly.

In `@packages/orm/src/client/contract.ts`:
- Around line 384-391: The createManyAndReturn method signature mistakenly makes
its args parameter optional, allowing invalid calls that lack the required data
to typecheck; update the signature(s) for createManyAndReturn (and the other
occurrence noted) to require args by removing the optional marker so args:
SelectSubset<...> (not args?: ...), ensuring the mapped CrudArgsType that
requires data is enforced at compile time.

In `@packages/orm/src/client/crud/dialects/base-dialect.ts`:
- Around line 1227-1250: The applyScalarOrderBy currently only handles
object-based ordering when both 'sort' and 'nulls' exist; update it so an object
with a valid 'sort' is honored even if 'nulls' is omitted: in applyScalarOrderBy
check for an object with 'sort' === 'asc'|'desc' and if 'nulls' is present call
buildOrderByField(query, fieldRef, this.negateSort(value.sort, negated),
value.nulls) but if 'nulls' is absent call query.orderBy(fieldRef,
this.negateSort(value.sort, negated)); keep using the existing helper negateSort
and buildOrderByField to locate the code paths.

In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 651-676: The GREATEST(...) call can return NULL if any per-field
similarity is NULL; update buildFuzzyRelevanceOrderBy so multi-field similarity
expressions are wrapped with COALESCE(..., 0) before passing to GREATEST (and
optionally do the same for the single-field path) — modify the mapping where
similarities is built (and the single-field orderBy call) to use COALESCE around
each buildSimilarity(...) result (referencing the buildFuzzyRelevanceOrderBy
function and the buildSimilarity helper) so NULL scores become 0 and don't null
out GREATEST.

In `@tests/e2e/orm/client-api/full-text-search.test.ts`:
- Around line 428-462: The test creates a temporary client (db via
createTestClient) and may leak it if an assertion throws before the final
db.$disconnect(); wrap the lifecycle for the temporary client in a try/finally:
after creating db (from createTestClient) run the test logic in try and always
call await db.$disconnect() in finally; also keep the existing suite-level
client.$disconnect() behavior untouched and apply the same try/finally pattern
to the other two tests referenced (the blocks around lines with createTestClient
and db.$disconnect()).

In `@tests/e2e/orm/schemas/delegate/typecheck.ts`:
- Around line 104-114: The tests for discriminator field assignments were
commented out because TS6 changed type assignability for create/update/upsert
but updateMany still compiles; investigate by comparing the generated Prisma
client parameter types for the affected operations (inspect Parameters<typeof
client.ratedVideo.create>, Parameters<typeof client.ratedVideo.update>,
Parameters<typeof client.ratedVideo.upsert>, and Parameters<typeof
client.ratedVideo.updateMany>) to see which input types enforce
discriminated-union constraints vs. more permissive UpdateMany input types, then
verify whether the proposed Parameters<typeof ...> wrapper (using those
Parameters types to construct a correctly-typed arg) actually allows the calls
under TS6; if it does, un-comment and replace the tests to use that pattern (or
add precise type-assertion tests using a type-testing tool if needed), otherwise
adjust the tests to use the correct shape the client expects (or mark only the
failing sites with `@ts-expect-error` and add a comment explaining the TS6
incompatibility) and add a short note in the test describing the root cause and
the chosen fix.

In `@tests/e2e/performance/tsc-torture/main.ts`:
- Around line 21-68: The cleanupAll function's deletion order is not FK-safe:
move db.commentReaction.deleteMany() (and any other tables that reference
Comment such as db.reviewComment.deleteMany()) to run before
db.comment.deleteMany(), and ensure self-referential Comment replies are removed
before parent comments by deleting child-related rows (replies/reactions) prior
to calling db.comment.deleteMany(); update the sequence inside cleanupAll so all
tables that have non-cascading relations to Comment (e.g., commentReaction,
reviewComment, any reply/junction rows) are deleted first.

---

Outside diff comments:
In `@package.json`:
- Line 17: Update the packageManager entry in package.json from "pnpm@10.33.0"
to the required "pnpm@10.12.1" to comply with coding guidelines; locate the
packageManager field in package.json and change its value accordingly, then
regenerate/install dependencies with the updated pnpm version (e.g., run pnpm
install using pnpm@10.12.1) to ensure the lockfile and node_modules match the
pinned manager.

---

Nitpick comments:
In `@packages/clients/fetch-client/test/fetch-client.test.ts`:
- Around line 444-463: Tests named "serializes args with special types into
query string" and "marshals args with Decimal into POST body" are using plain
strings so they never exercise the SuperJSON/Decimal serialization paths; update
those tests to pass real special values (e.g., Date objects and Decimal
instances) instead of strings when calling client.user.findMany and
client.user.createMany, and similarly fix the other affected test block later in
the file that checks Decimal/Date marshaling; ensure mockFetch assertions still
read the URL query (?q=) and the POST init.body JSON after these real special
values are passed so the serialization path is actually exercised.

In `@packages/clients/tanstack-query/test/react/optimistic-mutation.test.tsx`:
- Around line 263-264: The test in optimistic-mutation.test.tsx leaves a TODO
instead of a concrete assertion about nested relation behavior; replace the
commented line near the optimistic mutation assertion with an explicit
expectation that either checks for the presence of the category object (e.g.,
that the mutation result or cache contains category: { $optimistic: true, id:
'1', name: 'category1' }) or explicitly asserts its absence, so the test locks
the intended behavior; update the assertion surrounding the mutation result (the
optimistic mutation assertion in the test) to reflect the chosen behavior and
remove the TODO comment.

In `@packages/orm/src/client/client-impl.ts`:
- Around line 271-276: In getPromiseCallback, you're re-accessing (promise as
any).cb on return instead of returning the validated local variable; change the
return to return the already-validated cb from line where const cb is declared
to avoid redundant property lookup and ensure the function returns the validated
function value (references: getPromiseCallback, variable cb, ZenStackPromise).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 98a7d68d-5c4d-436c-8d89-f6442500d1d7

📥 Commits

Reviewing files that changed from the base of the PR and between 71c97a8 and 1a4de21.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (145)
  • package.json
  • packages/auth-adapters/better-auth/package.json
  • packages/auth-adapters/better-auth/src/adapter.ts
  • packages/auth-adapters/better-auth/tsconfig.json
  • packages/auth-adapters/better-auth/tsdown.config.ts
  • packages/cli/package.json
  • packages/cli/src/actions/proxy.ts
  • packages/cli/test/plugins/custom-plugin.test.ts
  • packages/cli/tsdown.config.ts
  • packages/clients/client-helpers/package.json
  • packages/clients/client-helpers/src/constants.ts
  • packages/clients/client-helpers/src/fetch.ts
  • packages/clients/client-helpers/src/index.ts
  • packages/clients/client-helpers/src/invalidation.ts
  • packages/clients/client-helpers/src/nested-write-visitor.ts
  • packages/clients/client-helpers/src/transaction.ts
  • packages/clients/client-helpers/src/types.ts
  • packages/clients/client-helpers/test/nested-write-visitor.test.ts
  • packages/clients/fetch-client/eslint.config.js
  • packages/clients/fetch-client/package.json
  • packages/clients/fetch-client/src/index.ts
  • packages/clients/fetch-client/test/fetch-client.test.ts
  • packages/clients/fetch-client/test/schemas/basic/schema-lite.ts
  • packages/clients/fetch-client/test/schemas/basic/schema.zmodel
  • packages/clients/fetch-client/test/schemas/no-procs/schema-lite.ts
  • packages/clients/fetch-client/test/schemas/no-procs/schema.zmodel
  • packages/clients/fetch-client/test/typing.test-d.ts
  • packages/clients/fetch-client/tsconfig.json
  • packages/clients/fetch-client/tsconfig.test.json
  • packages/clients/fetch-client/tsdown.config.ts
  • packages/clients/fetch-client/vitest.config.ts
  • packages/clients/tanstack-query/package.json
  • packages/clients/tanstack-query/src/common/client.ts
  • packages/clients/tanstack-query/src/common/constants.ts
  • packages/clients/tanstack-query/src/common/transaction.ts
  • packages/clients/tanstack-query/src/common/types.ts
  • packages/clients/tanstack-query/src/react.ts
  • packages/clients/tanstack-query/src/svelte/index.svelte.ts
  • packages/clients/tanstack-query/src/vue.ts
  • packages/clients/tanstack-query/test/react/crud-and-invalidation.test.tsx
  • packages/clients/tanstack-query/test/react/helpers.tsx
  • packages/clients/tanstack-query/test/react/json-null-serialization.test.tsx
  • packages/clients/tanstack-query/test/react/optimistic-mutation.test.tsx
  • packages/clients/tanstack-query/test/react/react-sliced-client.test-d.ts
  • packages/clients/tanstack-query/test/react/react-typing.test-d.ts
  • packages/clients/tanstack-query/test/react/sequential-transaction.test.tsx
  • packages/clients/tanstack-query/test/svelte/svelte-sliced-client.test-d.ts
  • packages/clients/tanstack-query/test/svelte/svelte-typing-test.ts
  • packages/clients/tanstack-query/test/vue/vue-sliced-client.test-d.ts
  • packages/clients/tanstack-query/test/vue/vue-typing-test.ts
  • packages/common-helpers/package.json
  • packages/config/eslint-config/package.json
  • packages/config/tsdown-config/package.json
  • packages/config/typescript-config/package.json
  • packages/config/vitest-config/package.json
  • packages/create-zenstack/package.json
  • packages/ide/vscode/package.json
  • packages/language/package.json
  • packages/language/res/stdlib.zmodel
  • packages/language/src/document.ts
  • packages/language/src/utils.ts
  • packages/language/src/validators/attribute-application-validator.ts
  • packages/language/test/attribute-application.test.ts
  • packages/orm/package.json
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/constants.ts
  • packages/orm/src/client/contract.ts
  • packages/orm/src/client/crud-types.ts
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • packages/orm/src/client/crud/dialects/mysql.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • packages/orm/src/client/crud/dialects/sqlite.ts
  • packages/orm/src/client/crud/operations/base.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/promise.ts
  • packages/orm/src/client/zod/factory.ts
  • packages/orm/src/utils/type-utils.ts
  • packages/orm/test/schema/models.ts
  • packages/orm/test/schema/schema.ts
  • packages/orm/test/schema/schema.zmodel
  • packages/orm/test/zod-compat.test.ts
  • packages/orm/tsdown.config.ts
  • packages/plugins/policy/package.json
  • packages/schema/package.json
  • packages/schema/src/schema.ts
  • packages/schema/test/schema/schema.ts
  • packages/sdk/package.json
  • packages/sdk/src/ts-schema-generator.ts
  • packages/server/package.json
  • packages/server/src/api/utils.ts
  • packages/testtools/package.json
  • packages/testtools/src/project.ts
  • packages/zod/package.json
  • packages/zod/src/index.ts
  • packages/zod/src/types.ts
  • packages/zod/test/factory.test.ts
  • packages/zod/test/schema/schema.ts
  • pnpm-workspace.yaml
  • samples/next.js/app/global.d.ts
  • samples/next.js/zenstack/input.ts
  • samples/nuxt/zenstack/input.ts
  • samples/orm/package.json
  • samples/orm/zenstack/input.ts
  • samples/sveltekit/src/zenstack/input.ts
  • scripts/test-generate.ts
  • tests/e2e/orm/client-api/checked-unchecked.test-d.ts
  • tests/e2e/orm/client-api/checked-unchecked.test.ts
  • tests/e2e/orm/client-api/delegate.test.ts
  • tests/e2e/orm/client-api/full-text-search.test.ts
  • tests/e2e/orm/client-api/fuzzy-search.test.ts
  • tests/e2e/orm/client-api/json-filter.test.ts
  • tests/e2e/orm/client-api/slicing.test.ts
  • tests/e2e/orm/client-api/unsupported.test.ts
  • tests/e2e/orm/client-api/update-many.test.ts
  • tests/e2e/orm/client-api/update.test.ts
  • tests/e2e/orm/schemas/basic/input.ts
  • tests/e2e/orm/schemas/delegate/typecheck.ts
  • tests/e2e/orm/schemas/full-text-search/schema.ts
  • tests/e2e/orm/schemas/full-text-search/schema.zmodel
  • tests/e2e/orm/schemas/fuzzy-search/index.ts
  • tests/e2e/orm/schemas/fuzzy-search/schema.ts
  • tests/e2e/orm/schemas/fuzzy-search/schema.zmodel
  • tests/e2e/package.json
  • tests/e2e/performance/tsc-torture/main.ts
  • tests/e2e/performance/tsc-torture/tsconfig.test.json
  • tests/e2e/performance/tsc-torture/zenstack/schema.ts
  • tests/e2e/performance/tsc-torture/zenstack/schema.zmodel
  • tests/regression/package.json
  • tests/regression/test/issue-2567/input.ts
  • tests/regression/test/issue-2567/models.ts
  • tests/regression/test/issue-2567/regression.test.ts
  • tests/regression/test/issue-2567/schema.ts
  • tests/regression/test/issue-2567/schema.zmodel
  • tests/regression/test/issue-2631.test.ts
  • tests/regression/test/issue-2633.test.ts
  • tests/regression/test/issue-2639/regression.test.ts
  • tests/regression/test/issue-2639/schema.ts
  • tests/regression/test/issue-2639/schema.zmodel
  • tests/regression/test/issue-2647/regression.test.ts
  • tests/regression/test/issue-2647/schema.ts
  • tests/regression/test/issue-2647/schema.zmodel
  • tests/regression/test/issue-2654.test.ts
  • tests/regression/test/policy-plugin-detection.test.ts
  • tests/runtimes/bun/package.json
  • tests/runtimes/edge-runtime/package.json
💤 Files with no reviewable changes (3)
  • tests/e2e/orm/client-api/delegate.test.ts
  • packages/clients/client-helpers/src/nested-write-visitor.ts
  • packages/clients/client-helpers/test/nested-write-visitor.test.ts

Comment thread packages/clients/fetch-client/src/index.ts
Comment thread packages/clients/fetch-client/test/fetch-client.test.ts
Comment thread packages/clients/tanstack-query/test/react/helpers.tsx
Comment thread packages/orm/src/client/contract.ts
Comment on lines +1227 to +1250
private applyScalarOrderBy(
query: SelectQueryBuilder<any, any, any>,
model: string,
modelAlias: string,
field: string,
value: any,
negated: boolean,
buildFieldRef: (model: string, field: string, modelAlias: string) => Expression<any>,
): SelectQueryBuilder<any, any, any> {
const fieldRef = buildFieldRef(model, field, modelAlias);
if (value === 'asc' || value === 'desc') {
return query.orderBy(fieldRef, this.negateSort(value, negated));
}
if (
typeof value === 'object' &&
'nulls' in value &&
'sort' in value &&
(value.sort === 'asc' || value.sort === 'desc') &&
(value.nulls === 'first' || value.nulls === 'last')
) {
return this.buildOrderByField(query, fieldRef, this.negateSort(value.sort, negated), value.nulls);
}
return query;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor { sort } when nulls is omitted.

The public OrderBy shape allows { sort: 'asc' | 'desc', nulls?: ... }, but this branch only applies object-based ordering when nulls is present. orderBy: { deletedAt: { sort: 'desc' } } will currently be ignored instead of sorting.

Suggested fix
     if (
         typeof value === 'object' &&
-        'nulls' in value &&
         'sort' in value &&
         (value.sort === 'asc' || value.sort === 'desc') &&
-        (value.nulls === 'first' || value.nulls === 'last')
-    ) {
-        return this.buildOrderByField(query, fieldRef, this.negateSort(value.sort, negated), value.nulls);
+        (!('nulls' in value) || value.nulls === 'first' || value.nulls === 'last')
+    ) {
+        const sort = this.negateSort(value.sort, negated);
+        return 'nulls' in value && value.nulls
+            ? this.buildOrderByField(query, fieldRef, sort, value.nulls)
+            : query.orderBy(fieldRef, sort);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orm/src/client/crud/dialects/base-dialect.ts` around lines 1227 -
1250, The applyScalarOrderBy currently only handles object-based ordering when
both 'sort' and 'nulls' exist; update it so an object with a valid 'sort' is
honored even if 'nulls' is omitted: in applyScalarOrderBy check for an object
with 'sort' === 'asc'|'desc' and if 'nulls' is present call
buildOrderByField(query, fieldRef, this.negateSort(value.sort, negated),
value.nulls) but if 'nulls' is absent call query.orderBy(fieldRef,
this.negateSort(value.sort, negated)); keep using the existing helper negateSort
and buildOrderByField to locate the code paths.

Comment on lines +651 to +676
override buildFuzzyRelevanceOrderBy(
query: SelectQueryBuilder<any, any, any>,
fieldRefs: Expression<any>[],
search: string,
sort: SortOrder,
mode: FuzzyFilterOptions['mode'],
unaccent: boolean,
): SelectQueryBuilder<any, any, any> {
const valueExpr = this.normalizeForTrigram(sql.val(search), unaccent);
const buildSimilarity = (fieldRef: Expression<any>) => {
const fieldExpr = this.normalizeForTrigram(fieldRef, unaccent);
switch (mode) {
case 'simple':
return sql`similarity(${fieldExpr}, ${valueExpr})`;
case 'word':
return sql`word_similarity(${valueExpr}, ${fieldExpr})`;
case 'strictWord':
return sql`strict_word_similarity(${valueExpr}, ${fieldExpr})`;
}
};

if (fieldRefs.length === 1) {
return query.orderBy(buildSimilarity(fieldRefs[0]!), sort);
}
const similarities = fieldRefs.map((ref) => buildSimilarity(ref));
return query.orderBy(sql`GREATEST(${sql.join(similarities)})`, sort);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Coalesce nullable scores before GREATEST(...).

For multi-field _fuzzyRelevance, one nullable field is enough to make GREATEST(...) evaluate to NULL, which discards the other field scores and can put partial-NULL rows ahead of real matches under DESC.

Suggested fix
-        const similarities = fieldRefs.map((ref) => buildSimilarity(ref));
+        const similarities = fieldRefs.map((ref) => sql`coalesce(${buildSimilarity(ref)}, 0)`);
         return query.orderBy(sql`GREATEST(${sql.join(similarities)})`, sort);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override buildFuzzyRelevanceOrderBy(
query: SelectQueryBuilder<any, any, any>,
fieldRefs: Expression<any>[],
search: string,
sort: SortOrder,
mode: FuzzyFilterOptions['mode'],
unaccent: boolean,
): SelectQueryBuilder<any, any, any> {
const valueExpr = this.normalizeForTrigram(sql.val(search), unaccent);
const buildSimilarity = (fieldRef: Expression<any>) => {
const fieldExpr = this.normalizeForTrigram(fieldRef, unaccent);
switch (mode) {
case 'simple':
return sql`similarity(${fieldExpr}, ${valueExpr})`;
case 'word':
return sql`word_similarity(${valueExpr}, ${fieldExpr})`;
case 'strictWord':
return sql`strict_word_similarity(${valueExpr}, ${fieldExpr})`;
}
};
if (fieldRefs.length === 1) {
return query.orderBy(buildSimilarity(fieldRefs[0]!), sort);
}
const similarities = fieldRefs.map((ref) => buildSimilarity(ref));
return query.orderBy(sql`GREATEST(${sql.join(similarities)})`, sort);
override buildFuzzyRelevanceOrderBy(
query: SelectQueryBuilder<any, any, any>,
fieldRefs: Expression<any>[],
search: string,
sort: SortOrder,
mode: FuzzyFilterOptions['mode'],
unaccent: boolean,
): SelectQueryBuilder<any, any, any> {
const valueExpr = this.normalizeForTrigram(sql.val(search), unaccent);
const buildSimilarity = (fieldRef: Expression<any>) => {
const fieldExpr = this.normalizeForTrigram(fieldRef, unaccent);
switch (mode) {
case 'simple':
return sql`similarity(${fieldExpr}, ${valueExpr})`;
case 'word':
return sql`word_similarity(${valueExpr}, ${fieldExpr})`;
case 'strictWord':
return sql`strict_word_similarity(${valueExpr}, ${fieldExpr})`;
}
};
if (fieldRefs.length === 1) {
return query.orderBy(buildSimilarity(fieldRefs[0]!), sort);
}
const similarities = fieldRefs.map((ref) => sql`coalesce(${buildSimilarity(ref)}, 0)`);
return query.orderBy(sql`GREATEST(${sql.join(similarities)})`, sort);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 651 - 676,
The GREATEST(...) call can return NULL if any per-field similarity is NULL;
update buildFuzzyRelevanceOrderBy so multi-field similarity expressions are
wrapped with COALESCE(..., 0) before passing to GREATEST (and optionally do the
same for the single-field path) — modify the mapping where similarities is built
(and the single-field orderBy call) to use COALESCE around each
buildSimilarity(...) result (referencing the buildFuzzyRelevanceOrderBy function
and the buildSimilarity helper) so NULL scores become 0 and don't null out
GREATEST.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Are you sure one null value will cause GREATEST to return null?

Comment thread packages/orm/src/client/zod/factory.ts
Comment thread tests/e2e/orm/client-api/full-text-search.test.ts
Comment thread tests/e2e/orm/schemas/delegate/typecheck.ts
Comment thread tests/e2e/performance/tsc-torture/main.ts
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 12, 2026

Claude finished @ymc9's task —— View job


Code Review for v3.7.0 Release PR

This is a substantial release with 100+ changed files including new packages, major enhancements, and important bug fixes. I've conducted a comprehensive review focusing on code quality, security, performance, and potential issues.

Review Tasks:

  • Read repository guidelines and setup
  • Review new fetch-client package
  • Analyze TanStack Query enhancements
  • Examine ORM improvements and changes
  • Check security implications
  • Assess performance considerations
  • Review test coverage
  • Provide overall recommendations

✅ Strengths and Positive Changes

🎯 New Fetch Client Package

  • Well-structured architecture: The new @zenstackhq/fetch-client provides a clean, lightweight alternative to the TanStack Query client
  • Strong TypeScript support: Excellent type inference and safety with proper generic constraints
  • Comprehensive test coverage: 600+ lines of tests covering CRUD operations, transactions, and edge cases
  • Good error handling: Custom CrudError class with specific error codes for better debugging

🔧 ORM Enhancements

📊 TanStack Query Improvements

  • Better invalidation patterns: Improved query invalidation logic with predicate matching
  • Transaction integration: New transaction hooks with optimistic updates
  • Enhanced TypeScript types: Better inference for sliced operations and procedures

⚠️ Areas of Concern

🔒 Security Considerations

  1. Fetch client endpoint validation: While there's URL validation in normalizeEndpoint(), consider adding:

    • Protocol whitelist (only https in production)
    • Domain validation for allowed endpoints
    • Rate limiting considerations
  2. Input sanitization: The fetch client marshals data directly to JSON without additional sanitization. Consider validating against schema before transmission.

🚀 Performance Considerations

  1. Type complexity: The extensive use of mapped types and conditional types in crud-types.ts may impact TypeScript compilation performance
  2. Bundle size: The new fetch client adds dependency on several packages - consider tree-shaking optimization
  3. Query caching: The TanStack Query integration could benefit from more aggressive caching strategies

🐛 Potential Issues

  1. Error handling in fetch client:

    // In packages/clients/fetch-client/src/index.ts:208
    if (result == null) {
        throw new CrudError(CrudErrorCode.NotFound, `No ${modelName} found`, modelName);
    }

    This only checks for null but should also handle undefined explicitly.

  2. URL construction: The makeUrl function concatenates paths without proper encoding - potential for injection if args contain special characters.

  3. Transaction rollback: The sequential transaction implementation doesn't appear to have explicit rollback mechanisms for partial failures.

📝 Code Quality Observations

✅ Good Practices

  • Consistent error handling patterns
  • Proper TypeScript strict mode compliance
  • Good separation of concerns between packages
  • Comprehensive test coverage for new features

📋 Minor Issues

  1. Documentation: Some complex type definitions lack JSDoc comments
  2. Magic strings: Consider extracting more constants (e.g., HTTP methods, content types)
  3. Async/await consistency: Mix of Promise chains and async/await in some files

🧪 Test Coverage Assessment

Excellent coverage overall:

  • New fetch client: 600+ lines of comprehensive tests
  • Regression tests: Multiple new test cases for resolved issues
  • Transaction functionality: Well-tested with edge cases
  • Type-level testing: Good use of .test-d.ts files

💡 Recommendations

High Priority

  1. Add input validation to fetch client for untrusted data
  2. Implement explicit transaction rollback handling
  3. Add protocol/domain validation for endpoint security

Medium Priority

  1. Optimize TypeScript compilation by simplifying complex mapped types where possible
  2. Add performance benchmarks for the new fetch client vs existing solutions
  3. Consider lazy loading for optional dependencies

Low Priority

  1. Improve documentation for complex type definitions
  2. Extract magic constants for better maintainability
  3. Standardize async/await usage across the codebase

🎯 Overall Assessment

This is a high-quality release that adds significant value:

  • Architecture: Well-designed with proper separation of concerns
  • TypeScript: Excellent type safety and inference
  • Testing: Comprehensive coverage of new features
  • Backwards compatibility: Changes appear non-breaking
  • ⚠️ Security: Good foundation but could be strengthened
  • ⚠️ Performance: Should monitor TypeScript compilation impact

Recommendation: Approve with minor security hardening

The benefits of this release significantly outweigh the concerns. The new fetch client provides a valuable lightweight alternative, the bug fixes address important user pain points, and the overall code quality is high.


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.

5 participants