Skip to content

merge dev to main (v3.5.2)#2527

Merged
ymc9 merged 4 commits intomainfrom
dev
Mar 29, 2026
Merged

merge dev to main (v3.5.2)#2527
ymc9 merged 4 commits intomainfrom
dev

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Mar 29, 2026

Summary by CodeRabbit

  • New Features

    • Added nested routes support for REST API endpoints with create, read, update, and delete operations on related resources
    • Enabled order-by capability for array aggregation in relation fields
    • Added optional partial model return when document loading encounters errors
  • Tests

    • Added comprehensive test coverage for nested routes OpenAPI spec generation and order-by behavior with nested relations
  • Chores

    • Released version 3.5.2 across all packages

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 29, 2026

Claude finished @ymc9's task —— View job


PR Review: merge dev to main (v3.5.2)

I've completed a comprehensive review of this release PR. Here's my analysis:

Todo List:

  • Analyze environment and key code changes
  • Review ORM lateral join dialect improvements
  • Examine OpenAPI nestedRoutes implementation
  • Check language document partial model loading changes
  • Assess test coverage for new features
  • Identify potential bugs and security concerns
  • Provide comprehensive feedback and recommendations

Code Quality Assessment

🟢 Excellent Aspects

1. ORM Lateral Join Improvements (packages/orm/src/client/crud/dialects/)

  • Well-structured implementation with proper separation of concerns
  • Robust orderBy handling for nested relations using buildRelationOrderByExpressions method in lateral-join-dialect-base.ts:197-231
  • Comprehensive test coverage with large datasets (2000 records) to validate ordering stability
  • Proper null handling and edge cases considered
  • Good abstraction with base class for PostgreSQL and MySQL dialects

2. OpenAPI nestedRoutes Feature (packages/server/src/api/rest/openapi.ts)

  • Clean implementation of nested CRUD operations
  • Proper HTTP method mapping (GET/PATCH/DELETE for nested single resources)
  • Comprehensive test coverage including negative test cases
  • Well-structured schema generation with proper JSON:API compliance
  • Good separation between collection and single resource handling

3. Language Document Loading (packages/language/src/document.ts)

  • Simple but effective addition of returnPartialModelForError parameter
  • Maintains backward compatibility
  • Proper error handling preservation

🟡 Minor Concerns

1. Performance Considerations

  • lateral-join-dialect-base.ts:100: Force adding Number.MAX_SAFE_INTEGER limit could impact query performance on large datasets
  • The orderBy logic with negative take values involves sorting negation which adds complexity

2. Code Complexity

  • openapi.ts:196-231: The buildRelationOrderByExpressions method is quite complex with nested conditionals
  • Access policy checking in openapi.ts:1213-1252 has intricate logic that could benefit from helper methods

🟢 Security Assessment

No security concerns identified:

  • No introduction of user input without validation
  • No exposure of sensitive information
  • Proper parameter sanitization in OpenAPI spec generation
  • Access policy respect in mayDenyAccess method

🟢 Test Coverage

Comprehensive test coverage across all features:

  • ORM: Extensive tests with 2000 records testing both ascending/descending order stability
  • OpenAPI: Complete test suite covering nested routes, parameter validation, and schema generation
  • Language: Implicit coverage through existing document loading tests

🟢 Best Practices

Code follows excellent practices:

  • Proper TypeScript typing throughout
  • Consistent error handling patterns
  • Good abstraction and inheritance hierarchy
  • Clear separation of concerns
  • Comprehensive JSDoc documentation where needed

Specific Technical Feedback

ORM Lateral Join Implementation (lateral-join-dialect-base.ts)

Strengths:

  • Line 197-231: Smart orderBy extraction for array aggregation maintains stable sorting
  • Line 217-220: Proper handling of negative take values with sort negation
  • Good use of expression builders for type safety

Minor optimization opportunity:

  • Lines 97-101: The Number.MAX_SAFE_INTEGER limit fallback might be worth making configurable

OpenAPI nestedRoutes Feature (openapi.ts)

Strengths:

  • Lines 140-151: Proper conditional generation of nested single paths only for collections
  • Lines 361-425: Well-structured nested operation generation
  • Lines 1213-1252: Thorough access policy analysis

Suggestion:

  • Consider extracting the access policy checking logic into a separate utility class for better testability

Language Document Enhancement (document.ts)

Strengths:

  • Lines 36, 120, 144: Clean parameter addition with proper default value
  • Maintains full backward compatibility
  • Simple and focused change

Overall Assessment

This is a high-quality release PR with:

Excellent code quality - Well-structured, maintainable code following best practices
Comprehensive features - Significant improvements to ORM ordering and OpenAPI spec generation
Strong test coverage - Thorough testing including edge cases and large datasets
No security issues - Safe implementation with proper validation
Good documentation - Clear code comments and type definitions
Backward compatibility - No breaking changes introduced

Recommendation

✅ APPROVE - This PR is ready for merge. The implementation quality is high, test coverage is comprehensive, and no significant issues were identified. The new features enhance the framework's capabilities while maintaining stability and security.


@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Patch version bump from 3.5.1 to 3.5.2 across all packages, with functional enhancements for relation ordering in ORM dialects, optional partial model returns in language document loading, and nested REST API routes in OpenAPI spec generation.

Changes

Cohort / File(s) Summary
Package version bumps
package.json, packages/auth-adapters/better-auth/package.json, packages/cli/package.json, packages/clients/client-helpers/package.json, packages/clients/tanstack-query/package.json, packages/common-helpers/package.json, packages/config/eslint-config/package.json, packages/config/typescript-config/package.json, packages/config/vitest-config/package.json, packages/create-zenstack/package.json, packages/language/package.json, packages/orm/package.json, packages/plugins/policy/package.json, packages/schema/package.json, packages/sdk/package.json, packages/server/package.json, packages/testtools/package.json, packages/zod/package.json, samples/orm/package.json, tests/e2e/package.json, tests/regression/package.json, tests/runtimes/bun/package.json, tests/runtimes/edge-runtime/package.json
Updated all package versions from 3.5.1 to 3.5.2.
Language document API
packages/language/src/document.ts
Added optional returnPartialModelForError parameter to loadDocument() function. When enabled and parsing fails, the return object now includes an optional model field containing the partial parse result.
ORM relation orderBy support
packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts, packages/orm/src/client/crud/dialects/mysql.ts, packages/orm/src/client/crud/dialects/postgresql.ts
Extended buildArrayAgg() abstract method to accept optional orderBy parameter. Added buildRelationOrderByExpressions() helper in base class. MySQL preserves prior behavior with documentation; PostgreSQL generates ORDER BY clauses in jsonb_agg() when ordering is provided.
Server REST OpenAPI nestedRoutes
packages/server/src/api/rest/openapi.ts
Added nestedRoutes capability to RestApiSpecGenerator. Introduces nested single-resource paths (/{model}/{id}/{field}/{childId}) and augments fetch-related paths with POST (array relations) and PATCH (to-one relations) operations when enabled. Added buildNestedSinglePath() builder with GET, PATCH, DELETE support.
OpenAPI nestedRoutes tests
packages/server/test/openapi/rest-openapi.test.ts
Added comprehensive test suite validating nestedRoutes behavior: verifies nested single paths generation for collection relations, method presence/absence for to-one relations, fetch-related operations augmentation, and interaction with query slicing options. Includes OpenAPI 3.1 validation and operationId uniqueness checks.
E2E relation orderBy test
tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts
Added e2e Vitest test validating relation orderBy semantics with nested include/select structures. Tests ascending/descending order consistency with 2000 seeded posts and comments across multiple nested levels.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Version 3.5.2 hops into place,
With orderBy dancing through dialects with grace,
Nested routes bloom in REST's design,
Partial models return when parsing's not fine,
The warren grows stronger, line by line! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 PR title accurately describes the main purpose: merging the dev branch to main with a version bump to v3.5.2, which aligns with the 20+ package.json version updates and functional changes throughout the changeset.

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

✨ 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

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

🧹 Nitpick comments (4)
packages/language/src/document.ts (1)

120-120: Optional cleanup: deduplicate partial-model construction.

The same ternary/cast is duplicated in both error branches. Consider computing it once and reusing it for cleaner maintenance.

♻️ Proposed refactor
+    const partialModel = returnPartialModelForError ? (document.parseResult.value as Model) : undefined;
+
     if (errors.length > 0) {
         return {
             success: false,
-            model: returnPartialModelForError ? (document.parseResult.value as Model) : undefined,
+            model: partialModel,
             errors,
             warnings,
         };
     }
@@
     if (additionalErrors.length > 0) {
         return {
             success: false,
-            model: returnPartialModelForError ? (document.parseResult.value as Model) : undefined,
+            model: partialModel,
             errors: additionalErrors,
             warnings,
         };
     }

Also applies to: 144-144

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

In `@packages/language/src/document.ts` at line 120, The duplicated ternary/cast
using returnPartialModelForError ? (document.parseResult.value as Model) :
undefined should be computed once and reused; introduce a local const (e.g.
partialModel) assigned to returnPartialModelForError ?
(document.parseResult.value as Model) : undefined before the error branches,
then replace both occurrences at the lines referencing model with that
partialModel to avoid duplication (also apply the same change for the other
occurrence around line 144).
packages/server/src/api/rest/openapi.ts (1)

435-435: Consider extracting childIdParam to shared parameters.

For consistency with how id is defined in generateSharedParams, consider adding childId there as well. This would centralize parameter definitions and make future maintenance easier.

♻️ Suggested refactor

In generateSharedParams():

 private generateSharedParams(): Record<string, ParameterObject> {
     return {
         id: {
             name: 'id',
             in: 'path',
             required: true,
             schema: { type: 'string' },
             description: 'Resource ID',
         },
+        childId: {
+            name: 'childId',
+            in: 'path',
+            required: true,
+            schema: { type: 'string' },
+            description: 'Nested resource ID',
+        },
         include: {

Then in buildNestedSinglePath:

-const childIdParam = { name: 'childId', in: 'path', required: true, schema: { type: 'string' } };
+const childIdParam = { $ref: '#/components/parameters/childId' };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rest/openapi.ts` at line 435, Extract the inline
childIdParam into the shared params generator by adding a new entry for
"childId" in generateSharedParams() (mirroring the existing "id" definition) and
then replace the inline const childIdParam usage in buildNestedSinglePath with a
reference to the shared param (e.g., sharedParams.childId or the appropriate
lookup). Update any imports/returns of generateSharedParams so callers can
access the new childId definition and ensure the schema matches { type:
'string', in: 'path', required: true } to preserve behavior.
packages/server/test/openapi/rest-openapi.test.ts (1)

622-632: Consider renaming local schema variables to avoid shadowing.

The local variable schema on lines 624 and 630 shadows the outer schema constant (line 19). While functionally correct, using descriptive names like responseSchema or requestBodySchema would improve readability.

♻️ Suggested variable naming
     it('nested single path GET returns single resource response', () => {
         const getOp = spec.paths['/user/{id}/posts/{childId}'].get;
-        const schema = getOp.responses['200'].content['application/vnd.api+json'].schema;
-        expect(schema.$ref).toBe('#/components/schemas/PostResponse');
+        const responseSchema = getOp.responses['200'].content['application/vnd.api+json'].schema;
+        expect(responseSchema.$ref).toBe('#/components/schemas/PostResponse');
     });

     it('nested single path PATCH uses UpdateRequest body', () => {
         const patchOp = spec.paths['/user/{id}/posts/{childId}'].patch;
-        const schema = patchOp.requestBody.content['application/vnd.api+json'].schema;
-        expect(schema.$ref).toBe('#/components/schemas/PostUpdateRequest');
+        const requestBodySchema = patchOp.requestBody.content['application/vnd.api+json'].schema;
+        expect(requestBodySchema.$ref).toBe('#/components/schemas/PostUpdateRequest');
     });

Apply similar changes to lines 651, 657, and 672.

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

In `@packages/server/test/openapi/rest-openapi.test.ts` around lines 622 - 632,
The tests declare a top-level const named schema which is being shadowed by
local variables named schema in tests like the GET check (use
getOp.responses['200'].content['application/vnd.api+json'].schema) and the PATCH
check (use patchOp.requestBody.content['application/vnd.api+json'].schema);
rename the local variables to descriptive names (e.g., responseSchema for the
GET test and requestBodySchema for the PATCH test) to avoid shadowing and
improve readability, and apply the same renaming pattern to the other
occurrences mentioned (the tests around the tokens you noted at lines 651, 657,
and 672).
packages/orm/src/client/crud/dialects/postgresql.ts (1)

285-287: Avoid sql.raw() for the enum keywords.

These values already come from a closed set, so prefer static sql fragments here instead of uppercasing runtime strings into raw SQL.

♻️ Suggested refactor
-                const dir = sql.raw(sort.toUpperCase());
-                const nullsSql = nulls ? sql` NULLS ${sql.raw(nulls.toUpperCase())}` : sql``;
+                const dir = sort === 'asc' ? sql`ASC` : sql`DESC`;
+                const nullsSql =
+                    nulls === 'first' ? sql` NULLS FIRST` : nulls === 'last' ? sql` NULLS LAST` : sql``;
                 return sql`${expr} ${dir}${nullsSql}`;

As per coding guidelines "Use Kysely query builder as escape hatch instead of raw SQL in ORM operations".

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

In `@packages/orm/src/client/crud/dialects/postgresql.ts` around lines 285 - 287,
The code uses sql.raw(sort.toUpperCase()) and sql.raw(nulls.toUpperCase()) which
embeds runtime strings; instead map the closed sets to static sql fragments to
avoid raw SQL: replace sql.raw(sort.toUpperCase()) with a conditional that
returns sql`ASC` or sql`DESC` (based on the sort value) and replace
sql.raw(nulls.toUpperCase()) with sql`FIRST` or sql`LAST` (based on nulls), then
build dir and nullsSql using those static sql fragments when creating the final
sql`${expr} ${dir}${nullsSql}`; update the code paths that set dir and nullsSql
(the variables named dir and nullsSql) to use explicit mappings rather than
sql.raw.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts`:
- Around line 216-225: When handling negative payload.take in
lateral-join-dialect-base.ts, you negate the sort via this.negateSort(sort,
true) but you do not negate the nulls ordering, which breaks backward pagination
for nullable keys; update the block that prepares items to compute a separate
nulls variable (e.g., let nulls = value === 'string' ? undefined : value.nulls)
and when payload.take < 0 apply the same inversion to nulls (call an existing
helper like this.negateNulls(nulls) or implement equivalent logic) so you push
items with { expr, sort, nulls } where both sort and nulls are inverted together
for negative takes.

---

Nitpick comments:
In `@packages/language/src/document.ts`:
- Line 120: The duplicated ternary/cast using returnPartialModelForError ?
(document.parseResult.value as Model) : undefined should be computed once and
reused; introduce a local const (e.g. partialModel) assigned to
returnPartialModelForError ? (document.parseResult.value as Model) : undefined
before the error branches, then replace both occurrences at the lines
referencing model with that partialModel to avoid duplication (also apply the
same change for the other occurrence around line 144).

In `@packages/orm/src/client/crud/dialects/postgresql.ts`:
- Around line 285-287: The code uses sql.raw(sort.toUpperCase()) and
sql.raw(nulls.toUpperCase()) which embeds runtime strings; instead map the
closed sets to static sql fragments to avoid raw SQL: replace
sql.raw(sort.toUpperCase()) with a conditional that returns sql`ASC` or
sql`DESC` (based on the sort value) and replace sql.raw(nulls.toUpperCase())
with sql`FIRST` or sql`LAST` (based on nulls), then build dir and nullsSql using
those static sql fragments when creating the final sql`${expr}
${dir}${nullsSql}`; update the code paths that set dir and nullsSql (the
variables named dir and nullsSql) to use explicit mappings rather than sql.raw.

In `@packages/server/src/api/rest/openapi.ts`:
- Line 435: Extract the inline childIdParam into the shared params generator by
adding a new entry for "childId" in generateSharedParams() (mirroring the
existing "id" definition) and then replace the inline const childIdParam usage
in buildNestedSinglePath with a reference to the shared param (e.g.,
sharedParams.childId or the appropriate lookup). Update any imports/returns of
generateSharedParams so callers can access the new childId definition and ensure
the schema matches { type: 'string', in: 'path', required: true } to preserve
behavior.

In `@packages/server/test/openapi/rest-openapi.test.ts`:
- Around line 622-632: The tests declare a top-level const named schema which is
being shadowed by local variables named schema in tests like the GET check (use
getOp.responses['200'].content['application/vnd.api+json'].schema) and the PATCH
check (use patchOp.requestBody.content['application/vnd.api+json'].schema);
rename the local variables to descriptive names (e.g., responseSchema for the
GET test and requestBodySchema for the PATCH test) to avoid shadowing and
improve readability, and apply the same renaming pattern to the other
occurrences mentioned (the tests around the tokens you noted at lines 651, 657,
and 672).
🪄 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: 1075e42b-fa5e-4f29-b6d8-8bb557b2b2b8

📥 Commits

Reviewing files that changed from the base of the PR and between 5cdbb58 and 9346fe9.

📒 Files selected for processing (30)
  • package.json
  • packages/auth-adapters/better-auth/package.json
  • packages/cli/package.json
  • packages/clients/client-helpers/package.json
  • packages/clients/tanstack-query/package.json
  • packages/common-helpers/package.json
  • packages/config/eslint-config/package.json
  • packages/config/typescript-config/package.json
  • packages/config/vitest-config/package.json
  • packages/create-zenstack/package.json
  • packages/language/package.json
  • packages/language/src/document.ts
  • packages/orm/package.json
  • packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts
  • packages/orm/src/client/crud/dialects/mysql.ts
  • packages/orm/src/client/crud/dialects/postgresql.ts
  • packages/plugins/policy/package.json
  • packages/schema/package.json
  • packages/sdk/package.json
  • packages/server/package.json
  • packages/server/src/api/rest/openapi.ts
  • packages/server/test/openapi/rest-openapi.test.ts
  • packages/testtools/package.json
  • packages/zod/package.json
  • samples/orm/package.json
  • tests/e2e/orm/client-api/relation/order-by-nested-includes.test.ts
  • tests/e2e/package.json
  • tests/regression/package.json
  • tests/runtimes/bun/package.json
  • tests/runtimes/edge-runtime/package.json

Comment on lines +216 to +225
let sort = typeof value === 'string' ? value : value.sort;
if (payload.take !== undefined && payload.take < 0) {
// negative `take` requires negated sorting, and the result order
// will be corrected during post-read processing
sort = this.negateSort(sort, true);
}
if (typeof value === 'string') {
items.push({ expr, sort });
} else {
items.push({ expr, sort, nulls: value.nulls });
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

Reverse nulls together with sort for negative take.

Backward pagination only works if the full ordering is inverted. Keeping nulls unchanged fetches the wrong window for nullable sort keys.

🐛 Proposed fix
-                let sort = typeof value === 'string' ? value : value.sort;
+                let sort = typeof value === 'string' ? value : value.sort;
+                let nulls = typeof value === 'string' ? undefined : value.nulls;
                 if (payload.take !== undefined && payload.take < 0) {
                     // negative `take` requires negated sorting, and the result order
                     // will be corrected during post-read processing
                     sort = this.negateSort(sort, true);
+                    if (nulls) {
+                        nulls = nulls === 'first' ? 'last' : 'first';
+                    }
                 }
                 if (typeof value === 'string') {
                     items.push({ expr, sort });
                 } else {
-                    items.push({ expr, sort, nulls: value.nulls });
+                    items.push({ expr, sort, nulls });
                 }
📝 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
let sort = typeof value === 'string' ? value : value.sort;
if (payload.take !== undefined && payload.take < 0) {
// negative `take` requires negated sorting, and the result order
// will be corrected during post-read processing
sort = this.negateSort(sort, true);
}
if (typeof value === 'string') {
items.push({ expr, sort });
} else {
items.push({ expr, sort, nulls: value.nulls });
let sort = typeof value === 'string' ? value : value.sort;
let nulls = typeof value === 'string' ? undefined : value.nulls;
if (payload.take !== undefined && payload.take < 0) {
// negative `take` requires negated sorting, and the result order
// will be corrected during post-read processing
sort = this.negateSort(sort, true);
if (nulls) {
nulls = nulls === 'first' ? 'last' : 'first';
}
}
if (typeof value === 'string') {
items.push({ expr, sort });
} else {
items.push({ expr, sort, nulls });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts` around
lines 216 - 225, When handling negative payload.take in
lateral-join-dialect-base.ts, you negate the sort via this.negateSort(sort,
true) but you do not negate the nulls ordering, which breaks backward pagination
for nullable keys; update the block that prepares items to compute a separate
nulls variable (e.g., let nulls = value === 'string' ? undefined : value.nulls)
and when payload.take < 0 apply the same inversion to nulls (call an existing
helper like this.negateNulls(nulls) or implement equivalent logic) so you push
items with { expr, sort, nulls } where both sort and nulls are inverted together
for negative takes.

@ymc9 ymc9 merged commit 554f82f into main Mar 29, 2026
13 checks passed
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.

4 participants