diff --git a/packages/plugins/openapi/src/rpc-generator.ts b/packages/plugins/openapi/src/rpc-generator.ts index 5b47b810f..1a8f3630e 100644 --- a/packages/plugins/openapi/src/rpc-generator.ts +++ b/packages/plugins/openapi/src/rpc-generator.ts @@ -154,6 +154,13 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { // analyze access policies to determine default security const { create, read, update, delete: del } = analyzePolicies(zmodel); + // OrderByWithRelationInput's name is different when "fullTextSearch" is enabled + const orderByWithRelationInput = this.inputObjectTypes + .map((o) => upperCaseFirst(o.name)) + .includes(`${modelName}OrderByWithRelationInput`) + ? `${modelName}OrderByWithRelationInput` + : `${modelName}OrderByWithRelationAndSearchRelevanceInput`; + if (ops['createOne']) { definitions.push({ method: 'post', @@ -269,6 +276,13 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { select: this.omittableRef(`${modelName}Select`), include: hasRelation ? this.omittableRef(`${modelName}Include`) : undefined, where: this.omittableRef(`${modelName}WhereInput`), + orderBy: this.oneOf( + this.omittableRef(orderByWithRelationInput), + this.array(this.omittableRef(orderByWithRelationInput)) + ), + cursor: this.omittableRef(`${modelName}WhereUniqueInput`), + take: { type: 'integer' }, + skip: { type: 'integer' }, meta: this.ref('_Meta'), }, }, @@ -422,13 +436,6 @@ export class RPCOpenAPIGenerator extends OpenAPIGeneratorBase { security: read === true ? [] : undefined, }); - // OrderByWithRelationInput's name is different when "fullTextSearch" is enabled - const orderByWithRelationInput = this.inputObjectTypes - .map((o) => upperCaseFirst(o.name)) - .includes(`${modelName}OrderByWithRelationInput`) - ? `${modelName}OrderByWithRelationInput` - : `${modelName}OrderByWithRelationAndSearchRelevanceInput`; - if (ops['aggregate']) { definitions.push({ method: 'get', diff --git a/packages/plugins/openapi/tests/baseline/rpc-3.0.0-omit.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-3.0.0-omit.baseline.yaml index dd683e9e3..0a48952db 100644 --- a/packages/plugins/openapi/tests/baseline/rpc-3.0.0-omit.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc-3.0.0-omit.baseline.yaml @@ -572,6 +572,18 @@ components: $ref: '#/components/schemas/_AnyObject' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/_AnyObject' + - type: array + items: + $ref: '#/components/schemas/_AnyObject' + cursor: + $ref: '#/components/schemas/_AnyObject' + take: + type: integer + skip: + type: integer UserUpdateArgs: type: object required: @@ -760,6 +772,18 @@ components: $ref: '#/components/schemas/_AnyObject' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/_AnyObject' + - type: array + items: + $ref: '#/components/schemas/_AnyObject' + cursor: + $ref: '#/components/schemas/_AnyObject' + take: + type: integer + skip: + type: integer ProfileUpdateArgs: type: object required: diff --git a/packages/plugins/openapi/tests/baseline/rpc-3.0.0.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-3.0.0.baseline.yaml index 94b98f80e..b3c4deaa8 100644 --- a/packages/plugins/openapi/tests/baseline/rpc-3.0.0.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc-3.0.0.baseline.yaml @@ -3318,6 +3318,18 @@ components: $ref: '#/components/schemas/UserWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/UserOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/UserOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/UserWhereUniqueInput' + take: + type: integer + skip: + type: integer UserUpdateArgs: type: object required: @@ -3506,6 +3518,18 @@ components: $ref: '#/components/schemas/ProfileWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/ProfileOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/ProfileOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/ProfileWhereUniqueInput' + take: + type: integer + skip: + type: integer ProfileUpdateArgs: type: object required: @@ -3694,6 +3718,18 @@ components: $ref: '#/components/schemas/Post_ItemWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/Post_ItemOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/Post_ItemOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/Post_ItemWhereUniqueInput' + take: + type: integer + skip: + type: integer Post_ItemUpdateArgs: type: object required: diff --git a/packages/plugins/openapi/tests/baseline/rpc-3.1.0-omit.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-3.1.0-omit.baseline.yaml index fae21b204..11369e7d0 100644 --- a/packages/plugins/openapi/tests/baseline/rpc-3.1.0-omit.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc-3.1.0-omit.baseline.yaml @@ -610,6 +610,18 @@ components: $ref: '#/components/schemas/_AnyObject' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/_AnyObject' + - type: array + items: + $ref: '#/components/schemas/_AnyObject' + cursor: + $ref: '#/components/schemas/_AnyObject' + take: + type: integer + skip: + type: integer UserUpdateArgs: type: object required: @@ -798,6 +810,18 @@ components: $ref: '#/components/schemas/_AnyObject' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/_AnyObject' + - type: array + items: + $ref: '#/components/schemas/_AnyObject' + cursor: + $ref: '#/components/schemas/_AnyObject' + take: + type: integer + skip: + type: integer ProfileUpdateArgs: type: object required: diff --git a/packages/plugins/openapi/tests/baseline/rpc-3.1.0.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-3.1.0.baseline.yaml index b6f2c5095..5c5103e09 100644 --- a/packages/plugins/openapi/tests/baseline/rpc-3.1.0.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc-3.1.0.baseline.yaml @@ -3382,6 +3382,18 @@ components: $ref: '#/components/schemas/UserWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/UserOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/UserOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/UserWhereUniqueInput' + take: + type: integer + skip: + type: integer UserUpdateArgs: type: object required: @@ -3570,6 +3582,18 @@ components: $ref: '#/components/schemas/ProfileWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/ProfileOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/ProfileOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/ProfileWhereUniqueInput' + take: + type: integer + skip: + type: integer ProfileUpdateArgs: type: object required: @@ -3758,6 +3782,18 @@ components: $ref: '#/components/schemas/Post_ItemWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/Post_ItemOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/Post_ItemOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/Post_ItemWhereUniqueInput' + take: + type: integer + skip: + type: integer Post_ItemUpdateArgs: type: object required: diff --git a/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.0.0.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.0.0.baseline.yaml index 01c9a2d0f..d4aa6f8cf 100644 --- a/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.0.0.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.0.0.baseline.yaml @@ -1959,6 +1959,18 @@ components: $ref: '#/components/schemas/FooWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/FooOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/FooOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/FooWhereUniqueInput' + take: + type: integer + skip: + type: integer FooUpdateArgs: type: object required: diff --git a/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.1.0.baseline.yaml b/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.1.0.baseline.yaml index 3ada4651d..f2d3b4c0e 100644 --- a/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.1.0.baseline.yaml +++ b/packages/plugins/openapi/tests/baseline/rpc-type-coverage-3.1.0.baseline.yaml @@ -2001,6 +2001,18 @@ components: $ref: '#/components/schemas/FooWhereInput' meta: $ref: '#/components/schemas/_Meta' + orderBy: + oneOf: + - $ref: '#/components/schemas/FooOrderByWithRelationInput' + - type: array + items: + $ref: '#/components/schemas/FooOrderByWithRelationInput' + cursor: + $ref: '#/components/schemas/FooWhereUniqueInput' + take: + type: integer + skip: + type: integer FooUpdateArgs: type: object required: diff --git a/packages/server/tests/api/rpc.test.ts b/packages/server/tests/api/rpc.test.ts index ead12c033..416ba4a6c 100644 --- a/packages/server/tests/api/rpc.test.ts +++ b/packages/server/tests/api/rpc.test.ts @@ -131,6 +131,123 @@ describe('RPC API Handler Tests', () => { expect(r.data.count).toBe(1); }); + it('pagination and ordering', async () => { + const handleRequest = makeHandler(); + + // Clean up any existing data first + await prisma.post.deleteMany(); + await prisma.user.deleteMany(); + + // Create test data + await prisma.user.create({ + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { id: '1', title: 'A Post', published: true, viewCount: 5 }, + { id: '2', title: 'B Post', published: true, viewCount: 3 }, + { id: '3', title: 'C Post', published: true, viewCount: 7 }, + { id: '4', title: 'D Post', published: true, viewCount: 1 }, + { id: '5', title: 'E Post', published: true, viewCount: 9 }, + ], + }, + }, + }); + + // Test orderBy with title ascending + let r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ orderBy: { title: 'asc' } }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data).toHaveLength(5); + expect(r.data[0].title).toBe('A Post'); + expect(r.data[4].title).toBe('E Post'); + + // Test orderBy with viewCount descending + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ orderBy: { viewCount: 'desc' } }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data[0].viewCount).toBe(9); + expect(r.data[4].viewCount).toBe(1); + + // Test multiple orderBy + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ orderBy: [{ published: 'desc' }, { title: 'asc' }] }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data[0].title).toBe('A Post'); + + // Test take (limit) + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ take: 3 }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data).toHaveLength(3); + + // Test skip (offset) + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ skip: 2, take: 2 }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data).toHaveLength(2); + + // Test skip and take with orderBy + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ orderBy: { title: 'asc' }, skip: 1, take: 3 }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data).toHaveLength(3); + expect(r.data[0].title).toBe('B Post'); + expect(r.data[2].title).toBe('D Post'); + + // Test cursor-based pagination + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ orderBy: { id: 'asc' }, take: 2 }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data).toHaveLength(2); + const lastId = r.data[1].id; + + // Get next page using cursor + r = await handleRequest({ + method: 'get', + path: '/post/findMany', + query: { q: JSON.stringify({ orderBy: { id: 'asc' }, take: 2, skip: 1, cursor: { id: lastId } }) }, + prisma, + }); + expect(r.status).toBe(200); + expect(r.data).toHaveLength(2); + expect(r.data[0].id).toBe('3'); + expect(r.data[1].id).toBe('4'); + + // Clean up + await prisma.post.deleteMany(); + await prisma.user.deleteMany(); + }); + it('check', async () => { const handleRequest = makeHandler(); @@ -163,6 +280,10 @@ describe('RPC API Handler Tests', () => { }); it('policy violation', async () => { + // Clean up any existing data first + await prisma.post.deleteMany(); + await prisma.user.deleteMany(); + await prisma.user.create({ data: { id: '1',