From 50c9aabdd42674d9bb23b187fea949d10403ac27 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 13 Mar 2023 17:30:34 +0800 Subject: [PATCH 1/4] feat: zod generation plugin and validation for openapi requests --- package.json | 2 +- packages/language/package.json | 2 +- packages/next/package.json | 2 +- packages/plugins/openapi/openapi.yaml | 3009 ----------------- packages/plugins/openapi/package.json | 2 +- packages/plugins/react/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- packages/plugins/trpc/src/zod/generator.ts | 31 +- .../trpc/src/zod/helpers/aggregate-helpers.ts | 78 - .../trpc/src/zod/helpers/comments-helpers.ts | 112 - .../plugins/trpc/src/zod/helpers/helpers.ts | 49 - .../trpc/src/zod/helpers/include-helpers.ts | 88 - .../plugins/trpc/src/zod/helpers/index.ts | 4 - .../trpc/src/zod/helpers/model-helpers.ts | 36 - .../trpc/src/zod/helpers/modelArgs-helpers.ts | 73 - .../trpc/src/zod/helpers/mongodb-helpers.ts | 73 - .../trpc/src/zod/helpers/select-helpers.ts | 155 - packages/plugins/trpc/src/zod/transformer.ts | 56 +- packages/runtime/package.json | 2 +- packages/runtime/src/enhancements/preset.ts | 4 +- packages/runtime/src/zod.ts | 19 + packages/schema/package.json | 2 +- packages/schema/src/cli/plugin-runner.ts | 10 +- packages/schema/src/plugins/zod/generator.ts | 111 + packages/schema/src/plugins/zod/index.ts | 10 + .../schema/src/plugins/zod/transformer.ts | 616 ++++ packages/schema/src/plugins/zod/types.ts | 24 + .../schema/src/plugins/zod/utils/removeDir.ts | 15 + packages/schema/src/res/stdlib.zmodel | 10 + .../tests/generator/prisma-generator.test.ts | 10 +- packages/schema/tests/schema/cal-com.zmodel | 4 +- packages/sdk/package.json | 6 +- packages/sdk/src/code-gen.ts | 38 + packages/sdk/src/dmmf-helpers/index.ts | 1 + .../src/dmmf-helpers/missing-types-helper.ts | 16 + packages/sdk/src/index.ts | 1 + packages/server/package.json | 10 +- packages/server/src/fastify/plugin.ts | 7 + packages/server/src/openapi/index.ts | 45 + packages/server/tests/fastify-plugin.test.ts | 8 +- packages/testtools/package.json | 2 +- packages/testtools/src/schema.ts | 11 +- pnpm-lock.yaml | 8 + tests/integration/test-run/package-lock.json | 4 +- tests/integration/tests/schema/cal-com.zmodel | 4 +- tests/integration/tests/schema/todo.zmodel | 9 +- 46 files changed, 998 insertions(+), 3785 deletions(-) delete mode 100644 packages/plugins/openapi/openapi.yaml delete mode 100644 packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/comments-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/include-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/index.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/model-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/select-helpers.ts create mode 100644 packages/runtime/src/zod.ts create mode 100644 packages/schema/src/plugins/zod/generator.ts create mode 100644 packages/schema/src/plugins/zod/index.ts create mode 100644 packages/schema/src/plugins/zod/transformer.ts create mode 100644 packages/schema/src/plugins/zod/types.ts create mode 100644 packages/schema/src/plugins/zod/utils/removeDir.ts create mode 100644 packages/sdk/src/code-gen.ts create mode 100644 packages/sdk/src/dmmf-helpers/missing-types-helper.ts diff --git a/package.json b/package.json index e49210d56..d8f040dcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/language/package.json b/packages/language/package.json index c592bcda9..d0c886a33 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/next/package.json b/packages/next/package.json index 58119e37a..8d3fe8281 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/next", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "displayName": "ZenStack Next.js integration", "description": "ZenStack Next.js integration", "homepage": "https://zenstack.dev", diff --git a/packages/plugins/openapi/openapi.yaml b/packages/plugins/openapi/openapi.yaml deleted file mode 100644 index 3cfe0662c..000000000 --- a/packages/plugins/openapi/openapi.yaml +++ /dev/null @@ -1,3009 +0,0 @@ -openapi: 3.0.0 -info: - title: ZenStack Generated API - version: 1.0.0 -components: - schemas: - Role: - type: string - enum: - - USER - - ADMIN - PostScalarFieldEnum: - type: string - enum: - - id - - createdAt - - updatedAt - - title - - authorId - - published - - viewCount - QueryMode: - type: string - enum: - - default - - insensitive - SortOrder: - type: string - enum: - - asc - - desc - UserScalarFieldEnum: - type: string - enum: - - id - - createdAt - - updatedAt - - email - - role - User: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - posts: - type: array - items: - $ref: "#/components/schemas/Post" - required: - - id - - createdAt - - updatedAt - - email - - role - Post: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - author: - $ref: "#/components/schemas/User" - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - createdAt - - updatedAt - - title - - published - - viewCount - UserWhereInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/UserWhereInput" - - type: array - items: - $ref: "#/components/schemas/UserWhereInput" - OR: - type: array - items: - $ref: "#/components/schemas/UserWhereInput" - NOT: - oneOf: - - $ref: "#/components/schemas/UserWhereInput" - - type: array - items: - $ref: "#/components/schemas/UserWhereInput" - id: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - email: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - role: - oneOf: - - $ref: "#/components/schemas/EnumRoleFilter" - - $ref: "#/components/schemas/Role" - posts: - $ref: "#/components/schemas/PostListRelationFilter" - UserOrderByWithRelationInput: - type: object - properties: - id: - $ref: "#/components/schemas/SortOrder" - createdAt: - $ref: "#/components/schemas/SortOrder" - updatedAt: - $ref: "#/components/schemas/SortOrder" - email: - $ref: "#/components/schemas/SortOrder" - role: - $ref: "#/components/schemas/SortOrder" - posts: - $ref: "#/components/schemas/PostOrderByRelationAggregateInput" - UserWhereUniqueInput: - type: object - properties: - id: - type: string - email: - type: string - UserScalarWhereWithAggregatesInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - OR: - type: array - items: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - NOT: - oneOf: - - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - id: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - email: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - role: - oneOf: - - $ref: "#/components/schemas/EnumRoleWithAggregatesFilter" - - $ref: "#/components/schemas/Role" - PostWhereInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/PostWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereInput" - OR: - type: array - items: - $ref: "#/components/schemas/PostWhereInput" - NOT: - oneOf: - - $ref: "#/components/schemas/PostWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereInput" - id: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - title: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - author: - oneOf: - - $ref: "#/components/schemas/UserRelationFilter" - - $ref: "#/components/schemas/UserWhereInput" - authorId: - oneOf: - - $ref: "#/components/schemas/StringNullableFilter" - - type: string - published: - oneOf: - - $ref: "#/components/schemas/BoolFilter" - - type: boolean - viewCount: - oneOf: - - $ref: "#/components/schemas/IntFilter" - - type: integer - PostOrderByWithRelationInput: - type: object - properties: - id: - $ref: "#/components/schemas/SortOrder" - createdAt: - $ref: "#/components/schemas/SortOrder" - updatedAt: - $ref: "#/components/schemas/SortOrder" - title: - $ref: "#/components/schemas/SortOrder" - author: - $ref: "#/components/schemas/UserOrderByWithRelationInput" - authorId: - $ref: "#/components/schemas/SortOrder" - published: - $ref: "#/components/schemas/SortOrder" - viewCount: - $ref: "#/components/schemas/SortOrder" - PostWhereUniqueInput: - type: object - properties: - id: - type: string - PostScalarWhereWithAggregatesInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - OR: - type: array - items: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - NOT: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - id: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - title: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - authorId: - oneOf: - - $ref: "#/components/schemas/StringNullableWithAggregatesFilter" - - type: string - published: - oneOf: - - $ref: "#/components/schemas/BoolWithAggregatesFilter" - - type: boolean - viewCount: - oneOf: - - $ref: "#/components/schemas/IntWithAggregatesFilter" - - type: integer - UserCreateInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - posts: - $ref: "#/components/schemas/PostCreateNestedManyWithoutAuthorInput" - required: - - id - - email - UserUpdateInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - posts: - $ref: "#/components/schemas/PostUpdateManyWithoutAuthorNestedInput" - UserCreateManyInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - required: - - id - - email - UserUpdateManyMutationInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - PostCreateInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - author: - $ref: "#/components/schemas/UserCreateNestedOneWithoutPostsInput" - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUpdateInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - author: - $ref: "#/components/schemas/UserUpdateOneWithoutPostsNestedInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - PostCreateManyInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUpdateManyMutationInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - StringFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringFilter" - DateTimeFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeFilter" - EnumRoleFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleFilter" - PostListRelationFilter: - type: object - properties: - every: - $ref: "#/components/schemas/PostWhereInput" - some: - $ref: "#/components/schemas/PostWhereInput" - none: - $ref: "#/components/schemas/PostWhereInput" - BoolFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolFilter" - StringNullableFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableFilter" - PostOrderByRelationAggregateInput: - type: object - properties: - _count: - $ref: "#/components/schemas/SortOrder" - StringWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedStringFilter" - _max: - $ref: "#/components/schemas/NestedStringFilter" - DateTimeWithAggregatesFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedDateTimeFilter" - _max: - $ref: "#/components/schemas/NestedDateTimeFilter" - EnumRoleWithAggregatesFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedEnumRoleFilter" - _max: - $ref: "#/components/schemas/NestedEnumRoleFilter" - BoolWithAggregatesFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedBoolFilter" - _max: - $ref: "#/components/schemas/NestedBoolFilter" - StringNullableWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntNullableFilter" - _min: - $ref: "#/components/schemas/NestedStringNullableFilter" - _max: - $ref: "#/components/schemas/NestedStringNullableFilter" - UserRelationFilter: - type: object - properties: - is: - $ref: "#/components/schemas/UserWhereInput" - isNot: - $ref: "#/components/schemas/UserWhereInput" - IntFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntFilter" - IntWithAggregatesFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _avg: - $ref: "#/components/schemas/NestedFloatFilter" - _sum: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedIntFilter" - _max: - $ref: "#/components/schemas/NestedIntFilter" - PostCreateNestedManyWithoutAuthorInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - connectOrCreate: - oneOf: - - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - createMany: - $ref: "#/components/schemas/PostCreateManyAuthorInputEnvelope" - connect: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - StringFieldUpdateOperationsInput: - type: object - properties: - set: - type: string - DateTimeFieldUpdateOperationsInput: - type: object - properties: - set: - type: string - format: date-time - EnumRoleFieldUpdateOperationsInput: - type: object - properties: - set: - $ref: "#/components/schemas/Role" - PostUpdateManyWithoutAuthorNestedInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - connectOrCreate: - oneOf: - - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - upsert: - oneOf: - - $ref: "#/components/schemas/PostUpsertWithWhereUniqueWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUpsertWithWhereUniqueWithoutAuthorInput" - createMany: - $ref: "#/components/schemas/PostCreateManyAuthorInputEnvelope" - set: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - disconnect: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - delete: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - connect: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - update: - oneOf: - - $ref: "#/components/schemas/PostUpdateWithWhereUniqueWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUpdateWithWhereUniqueWithoutAuthorInput" - updateMany: - oneOf: - - $ref: "#/components/schemas/PostUpdateManyWithWhereWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUpdateManyWithWhereWithoutAuthorInput" - deleteMany: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - BoolFieldUpdateOperationsInput: - type: object - properties: - set: - type: boolean - UserCreateNestedOneWithoutPostsInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - connectOrCreate: - $ref: "#/components/schemas/UserCreateOrConnectWithoutPostsInput" - connect: - $ref: "#/components/schemas/UserWhereUniqueInput" - UserUpdateOneWithoutPostsNestedInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - connectOrCreate: - $ref: "#/components/schemas/UserCreateOrConnectWithoutPostsInput" - upsert: - $ref: "#/components/schemas/UserUpsertWithoutPostsInput" - disconnect: - type: boolean - delete: - type: boolean - connect: - $ref: "#/components/schemas/UserWhereUniqueInput" - update: - oneOf: - - $ref: "#/components/schemas/UserUpdateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedUpdateWithoutPostsInput" - IntFieldUpdateOperationsInput: - type: object - properties: - set: - type: integer - increment: - type: integer - decrement: - type: integer - multiply: - type: integer - divide: - type: integer - NestedStringFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringFilter" - NestedDateTimeFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeFilter" - NestedEnumRoleFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleFilter" - NestedBoolFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolFilter" - NestedStringNullableFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableFilter" - NestedStringWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedStringFilter" - _max: - $ref: "#/components/schemas/NestedStringFilter" - NestedIntFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntFilter" - NestedDateTimeWithAggregatesFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedDateTimeFilter" - _max: - $ref: "#/components/schemas/NestedDateTimeFilter" - NestedEnumRoleWithAggregatesFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedEnumRoleFilter" - _max: - $ref: "#/components/schemas/NestedEnumRoleFilter" - NestedBoolWithAggregatesFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedBoolFilter" - _max: - $ref: "#/components/schemas/NestedBoolFilter" - NestedStringNullableWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntNullableFilter" - _min: - $ref: "#/components/schemas/NestedStringNullableFilter" - _max: - $ref: "#/components/schemas/NestedStringNullableFilter" - NestedIntNullableFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntNullableFilter" - NestedIntWithAggregatesFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _avg: - $ref: "#/components/schemas/NestedFloatFilter" - _sum: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedIntFilter" - _max: - $ref: "#/components/schemas/NestedIntFilter" - NestedFloatFilter: - type: object - properties: - equals: - type: number - in: - type: array - items: - type: number - notIn: - type: array - items: - type: number - lt: - type: number - lte: - type: number - gt: - type: number - gte: - type: number - not: - oneOf: - - type: number - - $ref: "#/components/schemas/NestedFloatFilter" - PostCreateWithoutAuthorInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUncheckedCreateWithoutAuthorInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostCreateOrConnectWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - required: - - where - - create - PostCreateManyAuthorInputEnvelope: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/PostCreateManyAuthorInput" - skipDuplicates: - type: boolean - required: - - data - PostUpsertWithWhereUniqueWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - update: - oneOf: - - $ref: "#/components/schemas/PostUpdateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedUpdateWithoutAuthorInput" - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - required: - - where - - update - - create - PostUpdateWithWhereUniqueWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - data: - oneOf: - - $ref: "#/components/schemas/PostUpdateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedUpdateWithoutAuthorInput" - required: - - where - - data - PostUpdateManyWithWhereWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostScalarWhereInput" - data: - oneOf: - - $ref: "#/components/schemas/PostUpdateManyMutationInput" - - $ref: "#/components/schemas/PostUncheckedUpdateManyWithoutPostsInput" - required: - - where - - data - PostScalarWhereInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - OR: - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - NOT: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - id: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - title: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - authorId: - oneOf: - - $ref: "#/components/schemas/StringNullableFilter" - - type: string - published: - oneOf: - - $ref: "#/components/schemas/BoolFilter" - - type: boolean - viewCount: - oneOf: - - $ref: "#/components/schemas/IntFilter" - - type: integer - UserCreateWithoutPostsInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - required: - - id - - email - UserUncheckedCreateWithoutPostsInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - required: - - id - - email - UserCreateOrConnectWithoutPostsInput: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - required: - - where - - create - UserUpsertWithoutPostsInput: - type: object - properties: - update: - oneOf: - - $ref: "#/components/schemas/UserUpdateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedUpdateWithoutPostsInput" - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - required: - - update - - create - UserUpdateWithoutPostsInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - UserUncheckedUpdateWithoutPostsInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - PostCreateManyAuthorInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUpdateWithoutAuthorInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - PostUncheckedUpdateWithoutAuthorInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - PostUncheckedUpdateManyWithoutPostsInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - UserArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - UserInclude: - type: object - properties: - posts: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostFindManyArgs" - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountOutputTypeArgs" - PostInclude: - type: object - properties: - author: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserArgs" - UserCountOutputTypeSelect: - type: object - properties: - posts: - type: boolean - UserCountOutputTypeArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserCountOutputTypeSelect" - UserSelect: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - posts: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostFindManyArgs" - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountOutputTypeArgs" - PostSelect: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - author: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserArgs" - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - UserCountAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - _all: - type: boolean - UserMinAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - UserMaxAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - PostCountAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - _all: - type: boolean - PostAvgAggregateInput: - type: object - properties: - viewCount: - type: boolean - PostSumAggregateInput: - type: object - properties: - viewCount: - type: boolean - PostMinAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - PostMaxAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - AggregateUser: - type: object - properties: - _count: - $ref: "#/components/schemas/UserCountAggregateOutputType" - _min: - $ref: "#/components/schemas/UserMinAggregateOutputType" - _max: - $ref: "#/components/schemas/UserMaxAggregateOutputType" - UserGroupByOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - _count: - $ref: "#/components/schemas/UserCountAggregateOutputType" - _min: - $ref: "#/components/schemas/UserMinAggregateOutputType" - _max: - $ref: "#/components/schemas/UserMaxAggregateOutputType" - required: - - id - - createdAt - - updatedAt - - email - - role - AggregatePost: - type: object - properties: - _count: - $ref: "#/components/schemas/PostCountAggregateOutputType" - _avg: - $ref: "#/components/schemas/PostAvgAggregateOutputType" - _sum: - $ref: "#/components/schemas/PostSumAggregateOutputType" - _min: - $ref: "#/components/schemas/PostMinAggregateOutputType" - _max: - $ref: "#/components/schemas/PostMaxAggregateOutputType" - PostGroupByOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - _count: - $ref: "#/components/schemas/PostCountAggregateOutputType" - _avg: - $ref: "#/components/schemas/PostAvgAggregateOutputType" - _sum: - $ref: "#/components/schemas/PostSumAggregateOutputType" - _min: - $ref: "#/components/schemas/PostMinAggregateOutputType" - _max: - $ref: "#/components/schemas/PostMaxAggregateOutputType" - required: - - id - - createdAt - - updatedAt - - title - - published - - viewCount - UserCountAggregateOutputType: - type: object - properties: - id: - type: integer - createdAt: - type: integer - updatedAt: - type: integer - email: - type: integer - role: - type: integer - _all: - type: integer - required: - - id - - createdAt - - updatedAt - - email - - role - - _all - UserMinAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - UserMaxAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - PostCountAggregateOutputType: - type: object - properties: - id: - type: integer - createdAt: - type: integer - updatedAt: - type: integer - title: - type: integer - authorId: - type: integer - published: - type: integer - viewCount: - type: integer - _all: - type: integer - required: - - id - - createdAt - - updatedAt - - title - - authorId - - published - - viewCount - - _all - PostAvgAggregateOutputType: - type: object - properties: - viewCount: - type: number - PostSumAggregateOutputType: - type: object - properties: - viewCount: - type: integer - PostMinAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - PostMaxAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - BatchPayload: - type: object - properties: - count: - type: integer - UserCreateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - data: - $ref: "#/components/schemas/UserCreateInput" - UserCreateManyArgs: - type: object - properties: - data: - $ref: "#/components/schemas/UserCreateManyInput" - UserFindUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - UserFindFirstArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereInput" - UserFindManyArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereInput" - UserUpdateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - data: - $ref: "#/components/schemas/UserUpdateInput" - UserUpdateManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - data: - $ref: "#/components/schemas/UserUpdateManyMutationInput" - UserUpsertArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - create: - $ref: "#/components/schemas/UserCreateInput" - update: - $ref: "#/components/schemas/UserUpdateInput" - UserDeleteUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - UserDeleteManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - UserCountArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - where: - $ref: "#/components/schemas/UserWhereInput" - UserAggregateArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - orderBy: - $ref: "#/components/schemas/UserOrderByWithRelationInput" - cursor: - $ref: "#/components/schemas/UserWhereUniqueInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountAggregateInput" - _min: - $ref: "#/components/schemas/UserMinAggregateInput" - _max: - $ref: "#/components/schemas/UserMaxAggregateInput" - UserGroupByArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - orderBy: - $ref: "#/components/schemas/UserOrderByWithRelationInput" - by: - $ref: "#/components/schemas/UserScalarFieldEnum" - having: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountAggregateInput" - _min: - $ref: "#/components/schemas/UserMinAggregateInput" - _max: - $ref: "#/components/schemas/UserMaxAggregateInput" - PostCreateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - data: - $ref: "#/components/schemas/PostCreateInput" - PostCreateManyArgs: - type: object - properties: - data: - $ref: "#/components/schemas/PostCreateManyInput" - PostFindUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - PostFindFirstArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereInput" - PostFindManyArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereInput" - PostUpdateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - data: - $ref: "#/components/schemas/PostUpdateInput" - PostUpdateManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - data: - $ref: "#/components/schemas/PostUpdateManyMutationInput" - PostUpsertArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - create: - $ref: "#/components/schemas/PostCreateInput" - update: - $ref: "#/components/schemas/PostUpdateInput" - PostDeleteUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - PostDeleteManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - PostCountArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - where: - $ref: "#/components/schemas/PostWhereInput" - PostAggregateArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - orderBy: - $ref: "#/components/schemas/PostOrderByWithRelationInput" - cursor: - $ref: "#/components/schemas/PostWhereUniqueInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostCountAggregateInput" - _min: - $ref: "#/components/schemas/PostMinAggregateInput" - _max: - $ref: "#/components/schemas/PostMaxAggregateInput" - _sum: - $ref: "#/components/schemas/PostSumAggregateInput" - _avg: - $ref: "#/components/schemas/PostAvgAggregateInput" - PostGroupByArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - orderBy: - $ref: "#/components/schemas/PostOrderByWithRelationInput" - by: - $ref: "#/components/schemas/PostScalarFieldEnum" - having: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostCountAggregateInput" - _min: - $ref: "#/components/schemas/PostMinAggregateInput" - _max: - $ref: "#/components/schemas/PostMaxAggregateInput" - _sum: - $ref: "#/components/schemas/PostSumAggregateInput" - _avg: - $ref: "#/components/schemas/PostAvgAggregateInput" -paths: - /user/create: - post: - operationId: createUser - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create a new User - content: - application/json: - schema: - $ref: "#/components/schemas/UserCreateArgs" - /user/createMany: - post: - operationId: createManyUser - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create several User - content: - application/json: - schema: - $ref: "#/components/schemas/UserCreateManyArgs" - /user/findUnique: - get: - operationId: findUniqueUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserFindUniqueArgs" - /user/findFirst: - get: - operationId: findFirstUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserFindFirstArgs" - /user/findMany: - get: - operationId: findManyUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserFindManyArgs" - /user/update: - patch: - operationId: updateUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update a User - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpdateArgs" - /user/updateMany: - patch: - operationId: updateManyUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update Users matching the given condition - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpdateManyArgs" - /user/upsert: - post: - operationId: upsertUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Upsert a User - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpsertArgs" - /user/delete: - delete: - operationId: deleteUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserDeleteUniqueArgs" - /user/deleteMany: - delete: - operationId: deleteManyUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserDeleteManyArgs" - /user/count: - get: - operationId: countUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - oneOf: - - type: integer - - $ref: "#/components/schemas/UserCountAggregateOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserCountArgs" - /user/aggregate: - get: - operationId: aggregateUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/AggregateUser" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserAggregateArgs" - /user/groupBy: - get: - operationId: groupByUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/UserGroupByOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserGroupByArgs" - /post/create: - post: - operationId: createPost - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create a new Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostCreateArgs" - /post/createMany: - post: - operationId: createManyPost - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create several Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostCreateManyArgs" - /post/findUnique: - get: - operationId: findUniquePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostFindUniqueArgs" - /post/findFirst: - get: - operationId: findFirstPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostFindFirstArgs" - /post/findMany: - get: - operationId: findManyPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostFindManyArgs" - /post/update: - patch: - operationId: updatePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update a Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostUpdateArgs" - /post/updateMany: - patch: - operationId: updateManyPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update Posts matching the given condition - content: - application/json: - schema: - $ref: "#/components/schemas/PostUpdateManyArgs" - /post/upsert: - post: - operationId: upsertPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Upsert a Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostUpsertArgs" - /post/delete: - delete: - operationId: deletePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostDeleteUniqueArgs" - /post/deleteMany: - delete: - operationId: deleteManyPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostDeleteManyArgs" - /post/count: - get: - operationId: countPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - oneOf: - - type: integer - - $ref: "#/components/schemas/PostCountAggregateOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostCountArgs" - /post/aggregate: - get: - operationId: aggregatePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/AggregatePost" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostAggregateArgs" - /post/groupBy: - get: - operationId: groupByPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/PostGroupByOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostGroupByArgs" diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index c7e608295..3d502a4bf 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json index fda63ff18..74af05145 100644 --- a/packages/plugins/react/package.json +++ b/packages/plugins/react/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/react", "displayName": "ZenStack plugin and runtime for ReactJS", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin and runtime for ReactJS", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index f8d7c1601..ffa1ca224 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/src/zod/generator.ts b/packages/plugins/trpc/src/zod/generator.ts index 16c8d3fdc..a1cd1018b 100644 --- a/packages/plugins/trpc/src/zod/generator.ts +++ b/packages/plugins/trpc/src/zod/generator.ts @@ -1,17 +1,14 @@ import { ConnectorType, DMMF } from '@prisma/generator-helper'; import { Dictionary } from '@prisma/internals'; -import { PluginOptions, getLiteral } from '@zenstackhq/sdk'; -import { DataSource, Model, isDataSource } from '@zenstackhq/sdk/ast'; -import { promises as fs } from 'fs'; +import { getLiteral, PluginOptions } from '@zenstackhq/sdk'; +import { DataSource, isDataSource, Model } from '@zenstackhq/sdk/ast'; import { addMissingInputObjectTypes, - hideInputObjectTypesAndRelatedFields, - resolveAddMissingInputObjectTypeOptions, - resolveModelsComments, -} from './helpers'; -import { resolveAggregateOperationSupport } from './helpers/aggregate-helpers'; + AggregateOperationSupport, + resolveAggregateOperationSupport, +} from '@zenstackhq/sdk/dmmf-helpers'; +import { promises as fs } from 'fs'; import Transformer from './transformer'; -import { AggregateOperationSupport } from './types'; import removeDir from './utils/removeDir'; export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { @@ -22,11 +19,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const modelOperations = prismaClientDmmf.mappings.modelOperations; const inputObjectTypes = prismaClientDmmf.schema.inputObjectTypes.prisma; const outputObjectTypes = prismaClientDmmf.schema.outputObjectTypes.prisma; - const enumTypes = prismaClientDmmf.schema.enumTypes; const models: DMMF.Model[] = prismaClientDmmf.datamodel.models; - const hiddenModels: string[] = []; - const hiddenFields: string[] = []; - resolveModelsComments(models, modelOperations, enumTypes, hiddenModels, hiddenFields); await generateEnumSchemas(prismaClientDmmf.schema.enumTypes.prisma, prismaClientDmmf.schema.enumTypes.model ?? []); @@ -41,20 +34,10 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const generatorConfigOptions: Dictionary = {}; Object.entries(options).forEach(([k, v]) => (generatorConfigOptions[k] = v as string)); - const addMissingInputObjectTypeOptions = resolveAddMissingInputObjectTypeOptions(generatorConfigOptions); - addMissingInputObjectTypes( - inputObjectTypes, - outputObjectTypes, - models, - modelOperations, - dataSourceProvider, - addMissingInputObjectTypeOptions - ); + addMissingInputObjectTypes(inputObjectTypes, outputObjectTypes, models); const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes); - hideInputObjectTypesAndRelatedFields(inputObjectTypes, hiddenModels, hiddenFields); - await generateObjectSchemas(inputObjectTypes); await generateModelSchemas(models, modelOperations, aggregateOperationSupport); } diff --git a/packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts b/packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts deleted file mode 100644 index 23ebd8ddf..000000000 --- a/packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { AggregateOperationSupport } from '../types'; - -const isAggregateOutputType = (name: string) => /(?:Count|Avg|Sum|Min|Max)AggregateOutputType$/.test(name); - -export const isAggregateInputType = (name: string) => - name.endsWith('CountAggregateInput') || - name.endsWith('SumAggregateInput') || - name.endsWith('AvgAggregateInput') || - name.endsWith('MinAggregateInput') || - name.endsWith('MaxAggregateInput'); - -export function addMissingInputObjectTypesForAggregate( - inputObjectTypes: DMMF.InputType[], - outputObjectTypes: DMMF.OutputType[] -) { - const aggregateOutputTypes = outputObjectTypes.filter(({ name }) => isAggregateOutputType(name)); - for (const aggregateOutputType of aggregateOutputTypes) { - const name = aggregateOutputType.name.replace(/(?:OutputType|Output)$/, ''); - inputObjectTypes.push({ - constraints: { maxNumFields: null, minNumFields: null }, - name: `${name}Input`, - fields: aggregateOutputType.fields.map((field) => ({ - name: field.name, - isNullable: false, - isRequired: false, - inputTypes: [ - { - isList: false, - type: 'True', - location: 'scalar', - }, - ], - })), - }); - } -} - -export function resolveAggregateOperationSupport(inputObjectTypes: DMMF.InputType[]) { - const aggregateOperationSupport: AggregateOperationSupport = {}; - for (const inputType of inputObjectTypes) { - if (isAggregateInputType(inputType.name)) { - const name = inputType.name.replace('AggregateInput', ''); - if (name.endsWith('Count')) { - const model = name.replace('Count', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - count: true, - }; - } else if (name.endsWith('Min')) { - const model = name.replace('Min', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - min: true, - }; - } else if (name.endsWith('Max')) { - const model = name.replace('Max', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - max: true, - }; - } else if (name.endsWith('Sum')) { - const model = name.replace('Sum', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - sum: true, - }; - } else if (name.endsWith('Avg')) { - const model = name.replace('Avg', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - avg: true, - }; - } - } - } - return aggregateOperationSupport; -} diff --git a/packages/plugins/trpc/src/zod/helpers/comments-helpers.ts b/packages/plugins/trpc/src/zod/helpers/comments-helpers.ts deleted file mode 100644 index 13526f53b..000000000 --- a/packages/plugins/trpc/src/zod/helpers/comments-helpers.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; - -const modelAttributeRegex = /(@@Gen\.)+([A-z])+(\()+(.+)+(\))+/; -const attributeNameRegex = /(?:\.)+([A-Za-z])+(?:\()+/; -const attributeArgsRegex = /(?:\()+([A-Za-z])+:+(.+)+(?:\))+/; - -export function resolveModelsComments( - models: DMMF.Model[], - modelOperations: DMMF.ModelMapping[], - enumTypes: { model?: DMMF.SchemaEnum[]; prisma: DMMF.SchemaEnum[] }, - hiddenModels: string[], - hiddenFields: string[] -) { - models = collectHiddenModels(models, hiddenModels); - collectHiddenFields(models, hiddenModels, hiddenFields); - hideModelOperations(models, modelOperations); - hideEnums(enumTypes, hiddenModels); -} - -function collectHiddenModels(models: DMMF.Model[], hiddenModels: string[]) { - return models - .map((model) => { - if (model.documentation) { - const attribute = model.documentation?.match(modelAttributeRegex)?.[0]; - const attributeName = attribute?.match(attributeNameRegex)?.[0]?.slice(1, -1); - if (attributeName !== 'model') model; - const rawAttributeArgs = attribute?.match(attributeArgsRegex)?.[0]?.slice(1, -1); - - const parsedAttributeArgs: Record = {}; - if (rawAttributeArgs) { - const rawAttributeArgsParts = rawAttributeArgs - .split(':') - .map((it) => it.trim()) - .map((part) => (part.startsWith('[') ? part : part.split(','))) - .flat() - .map((it) => it.trim()); - - for (let i = 0; i < rawAttributeArgsParts.length; i += 2) { - const key = rawAttributeArgsParts[i]; - const value = rawAttributeArgsParts[i + 1]; - parsedAttributeArgs[key] = JSON.parse(value); - } - } - if (parsedAttributeArgs.hide) { - hiddenModels.push(model.name); - return null as unknown as DMMF.Model; - } - } - return model; - }) - .filter(Boolean); -} - -function collectHiddenFields(models: DMMF.Model[], hiddenModels: string[], hiddenFields: string[]) { - models.forEach((model) => { - model.fields.forEach((field) => { - if (hiddenModels.includes(field.type)) { - hiddenFields.push(field.name); - if (field.relationFromFields) { - field.relationFromFields.forEach((item) => hiddenFields.push(item)); - } - } - }); - }); -} -function hideEnums(enumTypes: { model?: DMMF.SchemaEnum[]; prisma: DMMF.SchemaEnum[] }, hiddenModels: string[]) { - enumTypes.prisma = enumTypes.prisma.filter((item) => !hiddenModels.find((model) => item.name.startsWith(model))); -} - -function hideModelOperations(models: DMMF.Model[], modelOperations: DMMF.ModelMapping[]) { - let i = modelOperations.length; - while (i >= 0) { - --i; - const modelOperation = modelOperations[i]; - if ( - modelOperation && - !models.find((model) => { - return model.name === modelOperation.model; - }) - ) { - modelOperations.splice(i, 1); - } - } -} - -export function hideInputObjectTypesAndRelatedFields( - inputObjectTypes: DMMF.InputType[], - hiddenModels: string[], - hiddenFields: string[] -) { - let j = inputObjectTypes.length; - while (j >= 0) { - --j; - const inputType = inputObjectTypes[j]; - if ( - inputType && - (hiddenModels.includes(inputType?.meta?.source as string) || - hiddenModels.find((model) => inputType.name.startsWith(model))) - ) { - inputObjectTypes.splice(j, 1); - } else { - let k = inputType?.fields?.length ?? 0; - while (k >= 0) { - --k; - const field = inputType?.fields?.[k]; - if (field && hiddenFields.includes(field.name)) { - inputObjectTypes[j].fields.splice(k, 1); - } - } - } - } -} diff --git a/packages/plugins/trpc/src/zod/helpers/helpers.ts b/packages/plugins/trpc/src/zod/helpers/helpers.ts deleted file mode 100644 index 82895f226..000000000 --- a/packages/plugins/trpc/src/zod/helpers/helpers.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DMMF, ConnectorType, Dictionary } from '@prisma/generator-helper'; -import Transformer from '../transformer'; -import { addMissingInputObjectTypesForMongoDbRawOpsAndQueries } from './mongodb-helpers'; -import { - addMissingInputObjectTypesForAggregate, - addMissingInputObjectTypesForSelect, - addMissingInputObjectTypesForInclude, - addMissingInputObjectTypesForModelArgs, -} from '@zenstackhq/sdk/dmmf-helpers'; - -interface AddMissingInputObjectTypeOptions { - isGenerateSelect: boolean; - isGenerateInclude: boolean; -} - -export function addMissingInputObjectTypes( - inputObjectTypes: DMMF.InputType[], - outputObjectTypes: DMMF.OutputType[], - models: DMMF.Model[], - modelOperations: DMMF.ModelMapping[], - dataSourceProvider: ConnectorType, - options: AddMissingInputObjectTypeOptions -) { - // TODO: remove once Prisma fix this issue: https://github.com/prisma/prisma/issues/14900 - if (dataSourceProvider === 'mongodb') { - addMissingInputObjectTypesForMongoDbRawOpsAndQueries(modelOperations, outputObjectTypes, inputObjectTypes); - } - addMissingInputObjectTypesForAggregate(inputObjectTypes, outputObjectTypes); - if (options.isGenerateSelect) { - addMissingInputObjectTypesForSelect(inputObjectTypes, outputObjectTypes, models); - Transformer.setIsGenerateSelect(true); - } - if (options.isGenerateSelect || options.isGenerateInclude) { - addMissingInputObjectTypesForModelArgs(inputObjectTypes, models); - } - if (options.isGenerateInclude) { - addMissingInputObjectTypesForInclude(inputObjectTypes, models); - Transformer.setIsGenerateInclude(true); - } -} - -export function resolveAddMissingInputObjectTypeOptions( - generatorConfigOptions: Dictionary -): AddMissingInputObjectTypeOptions { - return { - isGenerateSelect: generatorConfigOptions.isGenerateSelect !== 'false', - isGenerateInclude: generatorConfigOptions.isGenerateInclude !== 'false', - }; -} diff --git a/packages/plugins/trpc/src/zod/helpers/include-helpers.ts b/packages/plugins/trpc/src/zod/helpers/include-helpers.ts deleted file mode 100644 index 686b678a7..000000000 --- a/packages/plugins/trpc/src/zod/helpers/include-helpers.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { checkIsModelRelationField, checkModelHasModelRelation, checkModelHasManyModelRelation } from './model-helpers'; - -export function addMissingInputObjectTypesForInclude( - inputObjectTypes: DMMF.InputType[], - models: DMMF.Model[], - isGenerateSelect: boolean -) { - // generate input object types necessary to support ModelInclude with relation support - const generatedIncludeInputObjectTypes = generateModelIncludeInputObjectTypes(models, isGenerateSelect); - - for (const includeInputObjectType of generatedIncludeInputObjectTypes) { - inputObjectTypes.push(includeInputObjectType); - } -} -function generateModelIncludeInputObjectTypes(models: DMMF.Model[], isGenerateSelect: boolean) { - const modelIncludeInputObjectTypes: DMMF.InputType[] = []; - for (const model of models) { - const { name: modelName, fields: modelFields } = model; - const fields: DMMF.SchemaArg[] = []; - - for (const modelField of modelFields) { - const { name: modelFieldName, isList, type } = modelField; - - const isRelationField = checkIsModelRelationField(modelField); - - if (isRelationField) { - const field: DMMF.SchemaArg = { - name: modelFieldName, - isRequired: false, - isNullable: false, - inputTypes: [ - { isList: false, type: 'Boolean', location: 'scalar' }, - { - isList: false, - type: isList ? `${type}FindManyArgs` : `${type}Args`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(field); - } - } - - /** - * include is not generated for models that do not have a relation with any other models - * -> continue onto the next model - */ - const hasRelationToAnotherModel = checkModelHasModelRelation(model); - if (!hasRelationToAnotherModel) { - continue; - } - - const hasManyRelationToAnotherModel = checkModelHasManyModelRelation(model); - - const shouldAddCountField = hasManyRelationToAnotherModel; - if (shouldAddCountField) { - const inputTypes: DMMF.SchemaArgInputType[] = [{ isList: false, type: 'Boolean', location: 'scalar' }]; - if (isGenerateSelect) { - inputTypes.push({ - isList: false, - type: `${modelName}CountOutputTypeArgs`, - location: 'inputObjectTypes', - namespace: 'prisma', - }); - } - const _countField: DMMF.SchemaArg = { - name: '_count', - isRequired: false, - isNullable: false, - inputTypes, - }; - fields.push(_countField); - } - - const modelIncludeInputObjectType: DMMF.InputType = { - name: `${modelName}Include`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields, - }; - modelIncludeInputObjectTypes.push(modelIncludeInputObjectType); - } - return modelIncludeInputObjectTypes; -} diff --git a/packages/plugins/trpc/src/zod/helpers/index.ts b/packages/plugins/trpc/src/zod/helpers/index.ts deleted file mode 100644 index 64ce0cf0c..000000000 --- a/packages/plugins/trpc/src/zod/helpers/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './comments-helpers'; -export * from './helpers'; -export * from './model-helpers'; -export * from './mongodb-helpers'; diff --git a/packages/plugins/trpc/src/zod/helpers/model-helpers.ts b/packages/plugins/trpc/src/zod/helpers/model-helpers.ts deleted file mode 100644 index 255638bd8..000000000 --- a/packages/plugins/trpc/src/zod/helpers/model-helpers.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; - -export function checkModelHasModelRelation(model: DMMF.Model) { - const { fields: modelFields } = model; - for (const modelField of modelFields) { - const isRelationField = checkIsModelRelationField(modelField); - if (isRelationField) { - return true; - } - } - return false; -} - -export function checkModelHasManyModelRelation(model: DMMF.Model) { - const { fields: modelFields } = model; - for (const modelField of modelFields) { - const isManyRelationField = checkIsManyModelRelationField(modelField); - if (isManyRelationField) { - return true; - } - } - return false; -} - -export function checkIsModelRelationField(modelField: DMMF.Field) { - const { kind, relationName } = modelField; - return kind === 'object' && !!relationName; -} - -export function checkIsManyModelRelationField(modelField: DMMF.Field) { - return checkIsModelRelationField(modelField) && modelField.isList; -} - -export function findModelByName(models: DMMF.Model[], modelName: string) { - return models.find(({ name }) => name === modelName); -} diff --git a/packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts b/packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts deleted file mode 100644 index e03759fe6..000000000 --- a/packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { checkModelHasModelRelation } from './model-helpers'; - -export function addMissingInputObjectTypesForModelArgs( - inputObjectTypes: DMMF.InputType[], - models: DMMF.Model[], - isGenerateSelect: boolean, - isGenerateInclude: boolean -) { - const modelArgsInputObjectTypes = generateModelArgsInputObjectTypes(models, isGenerateSelect, isGenerateInclude); - - for (const modelArgsInputObjectType of modelArgsInputObjectTypes) { - inputObjectTypes.push(modelArgsInputObjectType); - } -} -function generateModelArgsInputObjectTypes( - models: DMMF.Model[], - isGenerateSelect: boolean, - isGenerateInclude: boolean -) { - const modelArgsInputObjectTypes: DMMF.InputType[] = []; - for (const model of models) { - const { name: modelName } = model; - const fields: DMMF.SchemaArg[] = []; - - if (isGenerateSelect) { - const selectField: DMMF.SchemaArg = { - name: 'select', - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `${modelName}Select`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(selectField); - } - - const hasRelationToAnotherModel = checkModelHasModelRelation(model); - - if (isGenerateInclude && hasRelationToAnotherModel) { - const includeField: DMMF.SchemaArg = { - name: 'include', - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `${modelName}Include`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(includeField); - } - - const modelArgsInputObjectType: DMMF.InputType = { - name: `${modelName}Args`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields, - }; - modelArgsInputObjectTypes.push(modelArgsInputObjectType); - } - return modelArgsInputObjectTypes; -} diff --git a/packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts b/packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts deleted file mode 100644 index 9377925b4..000000000 --- a/packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import Transformer from '../transformer'; - -export function addMissingInputObjectTypesForMongoDbRawOpsAndQueries( - modelOperations: DMMF.ModelMapping[], - outputObjectTypes: DMMF.OutputType[], - inputObjectTypes: DMMF.InputType[] -) { - const rawOpsMap = resolveMongoDbRawOperations(modelOperations); - Transformer.rawOpsMap = rawOpsMap ?? {}; - - const mongoDbRawQueryInputObjectTypes = resolveMongoDbRawQueryInputObjectTypes(outputObjectTypes); - for (const mongoDbRawQueryInputType of mongoDbRawQueryInputObjectTypes) { - inputObjectTypes.push(mongoDbRawQueryInputType); - } -} - -function resolveMongoDbRawOperations(modelOperations: DMMF.ModelMapping[]) { - const rawOpsMap: { [name: string]: string } = {}; - const rawOpsNames = [ - ...new Set( - modelOperations.reduce((result, current) => { - const keys = Object.keys(current); - keys?.forEach((key) => { - if (key.includes('Raw')) { - result.push(key); - } - }); - return result; - }, []) - ), - ]; - - const modelNames = modelOperations.map((item) => item.model); - - rawOpsNames.forEach((opName) => { - modelNames.forEach((modelName) => { - const isFind = opName === 'findRaw'; - const opWithModel = `${opName.replace('Raw', '')}${modelName}Raw`; - rawOpsMap[opWithModel] = isFind ? `${modelName}FindRawArgs` : `${modelName}AggregateRawArgs`; - }); - }); - - return rawOpsMap; -} - -function resolveMongoDbRawQueryInputObjectTypes(outputObjectTypes: DMMF.OutputType[]) { - const mongoDbRawQueries = getMongoDbRawQueries(outputObjectTypes); - const mongoDbRawQueryInputObjectTypes = mongoDbRawQueries.map((item) => ({ - name: item.name, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields: item.args.map((arg) => ({ - name: arg.name, - isRequired: arg.isRequired, - isNullable: arg.isNullable, - inputTypes: arg.inputTypes, - })), - })); - return mongoDbRawQueryInputObjectTypes; -} - -function getMongoDbRawQueries(outputObjectTypes: DMMF.OutputType[]) { - const queryOutputTypes = outputObjectTypes.filter((item) => item.name === 'Query'); - - const mongodbRawQueries = queryOutputTypes?.[0].fields.filter((field) => field.name.includes('Raw')) ?? []; - - return mongodbRawQueries; -} - -export const isMongodbRawOp = (name: string) => /find([^]*?)Raw/.test(name) || /aggregate([^]*?)Raw/.test(name); diff --git a/packages/plugins/trpc/src/zod/helpers/select-helpers.ts b/packages/plugins/trpc/src/zod/helpers/select-helpers.ts deleted file mode 100644 index 691bf0269..000000000 --- a/packages/plugins/trpc/src/zod/helpers/select-helpers.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { checkIsModelRelationField, checkModelHasManyModelRelation } from './model-helpers'; - -export function addMissingInputObjectTypesForSelect( - inputObjectTypes: DMMF.InputType[], - outputObjectTypes: DMMF.OutputType[], - models: DMMF.Model[] -) { - // generate input object types necessary to support ModelSelect._count - const modelCountOutputTypes = getModelCountOutputTypes(outputObjectTypes); - const modelCountOutputTypeSelectInputObjectTypes = - generateModelCountOutputTypeSelectInputObjectTypes(modelCountOutputTypes); - const modelCountOutputTypeArgsInputObjectTypes = - generateModelCountOutputTypeArgsInputObjectTypes(modelCountOutputTypes); - - const modelSelectInputObjectTypes = generateModelSelectInputObjectTypes(models); - - const generatedInputObjectTypes = [ - modelCountOutputTypeSelectInputObjectTypes, - modelCountOutputTypeArgsInputObjectTypes, - modelSelectInputObjectTypes, - ].flat(); - - for (const inputObjectType of generatedInputObjectTypes) { - inputObjectTypes.push(inputObjectType); - } -} - -function getModelCountOutputTypes(outputObjectTypes: DMMF.OutputType[]) { - return outputObjectTypes.filter(({ name }) => name.includes('CountOutputType')); -} - -function generateModelCountOutputTypeSelectInputObjectTypes(modelCountOutputTypes: DMMF.OutputType[]) { - const modelCountOutputTypeSelectInputObjectTypes: DMMF.InputType[] = []; - for (const modelCountOutputType of modelCountOutputTypes) { - const { name: modelCountOutputTypeName, fields: modelCountOutputTypeFields } = modelCountOutputType; - const modelCountOutputTypeSelectInputObjectType: DMMF.InputType = { - name: `${modelCountOutputTypeName}Select`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields: modelCountOutputTypeFields.map(({ name }) => ({ - name, - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `Boolean`, - location: 'scalar', - }, - ], - })), - }; - modelCountOutputTypeSelectInputObjectTypes.push(modelCountOutputTypeSelectInputObjectType); - } - return modelCountOutputTypeSelectInputObjectTypes; -} - -function generateModelCountOutputTypeArgsInputObjectTypes(modelCountOutputTypes: DMMF.OutputType[]) { - const modelCountOutputTypeArgsInputObjectTypes: DMMF.InputType[] = []; - for (const modelCountOutputType of modelCountOutputTypes) { - const { name: modelCountOutputTypeName } = modelCountOutputType; - const modelCountOutputTypeArgsInputObjectType: DMMF.InputType = { - name: `${modelCountOutputTypeName}Args`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields: [ - { - name: 'select', - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `${modelCountOutputTypeName}Select`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }, - ], - }; - modelCountOutputTypeArgsInputObjectTypes.push(modelCountOutputTypeArgsInputObjectType); - } - return modelCountOutputTypeArgsInputObjectTypes; -} - -function generateModelSelectInputObjectTypes(models: DMMF.Model[]) { - const modelSelectInputObjectTypes: DMMF.InputType[] = []; - for (const model of models) { - const { name: modelName, fields: modelFields } = model; - const fields: DMMF.SchemaArg[] = []; - - for (const modelField of modelFields) { - const { name: modelFieldName, isList, type } = modelField; - - const isRelationField = checkIsModelRelationField(modelField); - - const field: DMMF.SchemaArg = { - name: modelFieldName, - isRequired: false, - isNullable: false, - inputTypes: [{ isList: false, type: 'Boolean', location: 'scalar' }], - }; - - if (isRelationField) { - const schemaArgInputType: DMMF.SchemaArgInputType = { - isList: false, - type: isList ? `${type}FindManyArgs` : `${type}Args`, - location: 'inputObjectTypes', - namespace: 'prisma', - }; - field.inputTypes.push(schemaArgInputType); - } - - fields.push(field); - } - - const hasManyRelationToAnotherModel = checkModelHasManyModelRelation(model); - - const shouldAddCountField = hasManyRelationToAnotherModel; - if (shouldAddCountField) { - const _countField: DMMF.SchemaArg = { - name: '_count', - isRequired: false, - isNullable: false, - inputTypes: [ - { isList: false, type: 'Boolean', location: 'scalar' }, - { - isList: false, - type: `${modelName}CountOutputTypeArgs`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(_countField); - } - - const modelSelectInputObjectType: DMMF.InputType = { - name: `${modelName}Select`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields, - }; - modelSelectInputObjectTypes.push(modelSelectInputObjectType); - } - return modelSelectInputObjectTypes; -} diff --git a/packages/plugins/trpc/src/zod/transformer.ts b/packages/plugins/trpc/src/zod/transformer.ts index e1601f2c1..3349c0471 100644 --- a/packages/plugins/trpc/src/zod/transformer.ts +++ b/packages/plugins/trpc/src/zod/transformer.ts @@ -1,10 +1,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import type { DMMF as PrismaDMMF } from '@prisma/generator-helper'; import { AUXILIARY_FIELDS } from '@zenstackhq/sdk'; +import { checkModelHasModelRelation, findModelByName, isAggregateInputType } from '@zenstackhq/sdk/dmmf-helpers'; import indentString from '@zenstackhq/sdk/utils'; import path from 'path'; -import { checkModelHasModelRelation, findModelByName, isMongodbRawOp } from './helpers'; -import { isAggregateInputType } from './helpers/aggregate-helpers'; import { AggregateOperationSupport, TransformerParams } from './types'; import { writeFileSafely } from './utils/writeFileSafely'; @@ -24,8 +23,6 @@ export default class Transformer { private hasJson = false; private static prismaClientOutputPath = '@prisma/client'; private static isCustomPrismaClientOutputPath = false; - private static isGenerateSelect = false; - private static isGenerateInclude = false; constructor(params: TransformerParams) { this.name = params.name ?? ''; @@ -40,14 +37,6 @@ export default class Transformer { this.outputPath = outPath; } - static setIsGenerateSelect(isGenerateSelect: boolean) { - this.isGenerateSelect = isGenerateSelect; - } - - static setIsGenerateInclude(isGenerateInclude: boolean) { - this.isGenerateInclude = isGenerateInclude; - } - static getOutputPath() { return this.outputPath; } @@ -83,10 +72,9 @@ export default class Transformer { async generateObjectSchema() { const zodObjectSchemaFields = this.generateObjectSchemaFields(); const objectSchema = this.prepareObjectSchema(zodObjectSchemaFields); - const objectSchemaName = this.resolveObjectSchemaName(); await writeFileSafely( - path.join(Transformer.outputPath, `schemas/objects/${objectSchemaName}.schema.ts`), + path.join(Transformer.outputPath, `schemas/objects/${this.name}.schema.ts`), '/* eslint-disable */\n' + objectSchema ); } @@ -246,18 +234,11 @@ export default class Transformer { generateExportObjectSchemaStatement(schema: string) { let name = this.name; - let exportName = this.name; - if (Transformer.provider === 'mongodb') { - if (isMongodbRawOp(name)) { - name = Transformer.rawOpsMap[name]; - exportName = name.replace('Args', ''); - } - } if (isAggregateInputType(name)) { name = `${name}Type`; } - const end = `export const ${exportName}ObjectSchema = Schema`; + const end = `export const ${this.name}ObjectSchema = Schema`; return `const Schema: z.ZodType "'" + f + "'").join( '|' )}>> = ${schema};\n\n ${end}`; @@ -374,16 +355,6 @@ export default class Transformer { return wrapped; } - resolveObjectSchemaName() { - let name = this.name; - let exportName = this.name; - if (isMongodbRawOp(name)) { - name = Transformer.rawOpsMap[name]; - exportName = name.replace('Args', ''); - } - return exportName; - } - async generateModelSchemas() { const globalImports: string[] = []; let globalExport = ''; @@ -605,27 +576,22 @@ export default schemas; const hasRelationToAnotherModel = checkModelHasModelRelation(model); - const selectImport = Transformer.isGenerateSelect - ? `import { ${modelName}SelectObjectSchema } from './objects/${modelName}Select.schema'` - : ''; + const selectImport = `import { ${modelName}SelectObjectSchema } from './objects/${modelName}Select.schema'`; - const includeImport = - Transformer.isGenerateInclude && hasRelationToAnotherModel - ? `import { ${modelName}IncludeObjectSchema } from './objects/${modelName}Include.schema'` - : ''; + const includeImport = hasRelationToAnotherModel + ? `import { ${modelName}IncludeObjectSchema } from './objects/${modelName}Include.schema'` + : ''; let selectZodSchemaLine = ''; let includeZodSchemaLine = ''; let selectZodSchemaLineLazy = ''; let includeZodSchemaLineLazy = ''; - if (Transformer.isGenerateSelect) { - const zodSelectObjectSchema = `${modelName}SelectObjectSchema.optional()`; - selectZodSchemaLine = `select: ${zodSelectObjectSchema},`; - selectZodSchemaLineLazy = `select: z.lazy(() => ${zodSelectObjectSchema}),`; - } + const zodSelectObjectSchema = `${modelName}SelectObjectSchema.optional()`; + selectZodSchemaLine = `select: ${zodSelectObjectSchema},`; + selectZodSchemaLineLazy = `select: z.lazy(() => ${zodSelectObjectSchema}),`; - if (Transformer.isGenerateInclude && hasRelationToAnotherModel) { + if (hasRelationToAnotherModel) { const zodIncludeObjectSchema = `${modelName}IncludeObjectSchema.optional()`; includeZodSchemaLine = `include: ${zodIncludeObjectSchema},`; includeZodSchemaLineLazy = `include: z.lazy(() => ${zodIncludeObjectSchema}),`; diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 832754774..1a0367eb3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/enhancements/preset.ts b/packages/runtime/src/enhancements/preset.ts index e6764a56e..e28bdbb2e 100644 --- a/packages/runtime/src/enhancements/preset.ts +++ b/packages/runtime/src/enhancements/preset.ts @@ -11,9 +11,9 @@ import { ModelMeta, PolicyDef } from './types'; * * @param prisma The Prisma client to enhance. * @param context The context to for evaluating access policies. - * @param policy The access policy data, generated by @zenstack/access-policy plugin. + * @param policy The access policy data, generated by @core/access-policy plugin. * You only need to pass it if you configured the plugin to generate into custom location. - * @param modelMeta The model metadata, generated by @zenstack/model-meta plugin. + * @param modelMeta The model metadata, generated by @core/model-meta plugin. * You only need to pass it if you configured the plugin to generate into custom location. */ export function withPresets( diff --git a/packages/runtime/src/zod.ts b/packages/runtime/src/zod.ts new file mode 100644 index 000000000..0d2375163 --- /dev/null +++ b/packages/runtime/src/zod.ts @@ -0,0 +1,19 @@ +import z from 'zod'; +import { DbOperations } from './types'; + +/** + * Mapping from model type name to zod schema for Prisma operations + */ +export type ModelZodSchema = Record>; + +/** + * Load zod schema from standard location. + */ +export function getModelZodSchemas(): ModelZodSchema { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('.zenstack/zod').default; + } catch { + throw new Error('Model meta cannot be loaded'); + } +} diff --git a/packages/schema/package.json b/packages/schema/package.json index 84e932d5d..72b23ddd9 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "A toolkit for building secure CRUD apps with Next.js + Typescript", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/cli/plugin-runner.ts b/packages/schema/src/cli/plugin-runner.ts index 82b67b573..721358170 100644 --- a/packages/schema/src/cli/plugin-runner.ts +++ b/packages/schema/src/cli/plugin-runner.ts @@ -30,7 +30,7 @@ export class PluginRunner { }> = []; const pluginDecls = context.schema.declarations.filter((d): d is Plugin => isPlugin(d)); - const prereqPlugins = ['@zenstack/prisma', '@zenstack/model-meta', '@zenstack/access-policy']; + const prereqPlugins = ['@core/prisma', '@core/model-meta', '@core/access-policy', '@core/zod']; const allPluginProviders = prereqPlugins.concat( pluginDecls .map((p) => this.getPluginProvider(p)) @@ -72,7 +72,7 @@ export class PluginRunner { options, }); - if (pluginProvider === '@zenstack/prisma' && options.output) { + if (pluginProvider === '@core/prisma' && options.output) { // record custom prisma output path prismaOutput = options.output as string; } @@ -93,7 +93,7 @@ export class PluginRunner { let dmmf: DMMF.Document | undefined = undefined; for (const { name, provider, run, options } of plugins) { await this.runPlugin(name, run, context, options, dmmf, warnings); - if (provider === '@zenstack/prisma') { + if (provider === '@core/prisma') { // load prisma DMMF dmmf = await getDMMF({ datamodel: fs.readFileSync(prismaOutput, { encoding: 'utf-8' }), @@ -152,8 +152,8 @@ export class PluginRunner { private getPluginModulePath(provider: string) { let pluginModulePath = provider; - if (pluginModulePath.startsWith('@zenstack/')) { - pluginModulePath = pluginModulePath.replace(/^@zenstack/, path.join(__dirname, '../plugins')); + if (pluginModulePath.startsWith('@core/')) { + pluginModulePath = pluginModulePath.replace(/^@core/, path.join(__dirname, '../plugins')); } return pluginModulePath; } diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts new file mode 100644 index 000000000..50d135101 --- /dev/null +++ b/packages/schema/src/plugins/zod/generator.ts @@ -0,0 +1,111 @@ +import { ConnectorType, DMMF } from '@prisma/generator-helper'; +import { Dictionary } from '@prisma/internals'; +import { emitProject, getLiteral, PluginOptions } from '@zenstackhq/sdk'; +import { DataSource, isDataSource, Model } from '@zenstackhq/sdk/ast'; +import { + addMissingInputObjectTypes, + AggregateOperationSupport, + resolveAggregateOperationSupport, +} from '@zenstackhq/sdk/dmmf-helpers'; +import { promises as fs } from 'fs'; +import path from 'path'; +import { Project } from 'ts-morph'; +import { getDefaultOutputFolder } from '../plugin-utils'; +import Transformer from './transformer'; +import removeDir from './utils/removeDir'; + +export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + let output = options.output as string; + if (!output) { + const defaultOutputFolder = getDefaultOutputFolder(); + if (defaultOutputFolder) { + output = path.join(defaultOutputFolder, 'zod'); + } else { + output = './generated/zod'; + } + } + await handleGeneratorOutputValue(output); + + const prismaClientDmmf = dmmf; + + const modelOperations = prismaClientDmmf.mappings.modelOperations; + const inputObjectTypes = prismaClientDmmf.schema.inputObjectTypes.prisma; + const outputObjectTypes = prismaClientDmmf.schema.outputObjectTypes.prisma; + const models: DMMF.Model[] = prismaClientDmmf.datamodel.models; + + const project = new Project(); + + await generateEnumSchemas( + prismaClientDmmf.schema.enumTypes.prisma, + prismaClientDmmf.schema.enumTypes.model ?? [], + project + ); + + const dataSource = model.declarations.find((d): d is DataSource => isDataSource(d)); + + const dataSourceProvider = getLiteral( + dataSource?.fields.find((f) => f.name === 'provider')?.value + ) as ConnectorType; + + Transformer.provider = dataSourceProvider; + + const generatorConfigOptions: Dictionary = {}; + Object.entries(options).forEach(([k, v]) => (generatorConfigOptions[k] = v as string)); + + addMissingInputObjectTypes(inputObjectTypes, outputObjectTypes, models); + + const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes); + + await generateObjectSchemas(inputObjectTypes, project); + await generateModelSchemas(models, modelOperations, aggregateOperationSupport, project); + + await emitProject(project); +} + +async function handleGeneratorOutputValue(output: string) { + // create the output directory and delete contents that might exist from a previous run + await fs.mkdir(output, { recursive: true }); + const isRemoveContentsOnly = true; + await removeDir(output, isRemoveContentsOnly); + + Transformer.setOutputPath(output); +} + +async function generateEnumSchemas( + prismaSchemaEnum: DMMF.SchemaEnum[], + modelSchemaEnum: DMMF.SchemaEnum[], + project: Project +) { + const enumTypes = [...prismaSchemaEnum, ...modelSchemaEnum]; + const enumNames = enumTypes.map((enumItem) => enumItem.name); + Transformer.enumNames = enumNames ?? []; + const transformer = new Transformer({ + enumTypes, + project, + }); + await transformer.generateEnumSchemas(); +} + +async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[], project: Project) { + for (let i = 0; i < inputObjectTypes.length; i += 1) { + const fields = inputObjectTypes[i]?.fields; + const name = inputObjectTypes[i]?.name; + const transformer = new Transformer({ name, fields, project }); + await transformer.generateObjectSchema(); + } +} + +async function generateModelSchemas( + models: DMMF.Model[], + modelOperations: DMMF.ModelMapping[], + aggregateOperationSupport: AggregateOperationSupport, + project: Project +) { + const transformer = new Transformer({ + models, + modelOperations, + aggregateOperationSupport, + project, + }); + await transformer.generateModelSchemas(); +} diff --git a/packages/schema/src/plugins/zod/index.ts b/packages/schema/src/plugins/zod/index.ts new file mode 100644 index 000000000..aee2371d4 --- /dev/null +++ b/packages/schema/src/plugins/zod/index.ts @@ -0,0 +1,10 @@ +import { DMMF } from '@prisma/generator-helper'; +import { PluginOptions } from '@zenstackhq/sdk'; +import { Model } from '@zenstackhq/sdk/ast'; +import { generate } from './generator'; + +export const name = 'Zod'; + +export default async function run(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + return generate(model, options, dmmf); +} diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts new file mode 100644 index 000000000..c6a0f0501 --- /dev/null +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -0,0 +1,616 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import type { DMMF as PrismaDMMF } from '@prisma/generator-helper'; +import { AUXILIARY_FIELDS } from '@zenstackhq/sdk'; +import { checkModelHasModelRelation, findModelByName, isAggregateInputType } from '@zenstackhq/sdk/dmmf-helpers'; +import indentString from '@zenstackhq/sdk/utils'; +import path from 'path'; +import { Project } from 'ts-morph'; +import { AggregateOperationSupport, TransformerParams } from './types'; + +export default class Transformer { + name: string; + fields: PrismaDMMF.SchemaArg[]; + schemaImports = new Set(); + models: PrismaDMMF.Model[]; + modelOperations: PrismaDMMF.ModelMapping[]; + aggregateOperationSupport: AggregateOperationSupport; + enumTypes: PrismaDMMF.SchemaEnum[]; + + static enumNames: string[] = []; + static rawOpsMap: { [name: string]: string } = {}; + static provider: string; + private static outputPath = './generated'; + private hasJson = false; + private static prismaClientOutputPath = '@prisma/client'; + private static isCustomPrismaClientOutputPath = false; + private project: Project; + + constructor(params: TransformerParams) { + this.name = params.name ?? ''; + this.fields = params.fields ?? []; + this.models = params.models ?? []; + this.modelOperations = params.modelOperations ?? []; + this.aggregateOperationSupport = params.aggregateOperationSupport ?? {}; + this.enumTypes = params.enumTypes ?? []; + this.project = params.project; + } + + static setOutputPath(outPath: string) { + this.outputPath = outPath; + } + + static getOutputPath() { + return this.outputPath; + } + + static setPrismaClientOutputPath(prismaClientCustomPath: string) { + this.prismaClientOutputPath = prismaClientCustomPath; + this.isCustomPrismaClientOutputPath = prismaClientCustomPath !== '@prisma/client'; + } + + async generateEnumSchemas() { + for (const enumType of this.enumTypes) { + const { name, values } = enumType; + const filteredValues = values.filter((v) => !AUXILIARY_FIELDS.includes(v)); + + const filePath = path.join(Transformer.outputPath, `enums/${name}.schema.ts`); + const content = `/* eslint-disable */\n${this.generateImportZodStatement()}\n${this.generateExportSchemaStatement( + `${name}`, + `z.enum(${JSON.stringify(filteredValues)})` + )}`; + this.project.createSourceFile(filePath, content, { overwrite: true }); + } + } + + generateImportZodStatement() { + return "import { z } from 'zod';\n"; + } + + generateExportSchemaStatement(name: string, schema: string) { + return `export const ${name}Schema = ${schema}`; + } + + async generateObjectSchema() { + const zodObjectSchemaFields = this.generateObjectSchemaFields(); + const objectSchema = this.prepareObjectSchema(zodObjectSchemaFields); + const objectSchemaName = this.resolveObjectSchemaName(); + + const filePath = path.join(Transformer.outputPath, `objects/${objectSchemaName}.schema.ts`); + const content = '/* eslint-disable */\n' + objectSchema; + this.project.createSourceFile(filePath, content, { overwrite: true }); + } + + generateObjectSchemaFields() { + const zodObjectSchemaFields = this.fields + .filter((field) => !AUXILIARY_FIELDS.includes(field.name)) + .map((field) => this.generateObjectSchemaField(field)) + .flatMap((item) => item) + .map((item) => { + const [zodStringWithMainType, field, skipValidators] = item; + + const value = skipValidators + ? zodStringWithMainType + : this.generateFieldValidators(zodStringWithMainType, field); + + return value.trim(); + }); + return zodObjectSchemaFields; + } + + generateObjectSchemaField(field: PrismaDMMF.SchemaArg): [string, PrismaDMMF.SchemaArg, boolean][] { + const lines = field.inputTypes; + + if (lines.length === 0) { + return []; + } + + let alternatives = lines.reduce((result, inputType) => { + if (inputType.type === 'String') { + result.push(this.wrapWithZodValidators('z.string()', field, inputType)); + } else if (inputType.type === 'Int' || inputType.type === 'Float' || inputType.type === 'Decimal') { + result.push(this.wrapWithZodValidators('z.number()', field, inputType)); + } else if (inputType.type === 'BigInt') { + result.push(this.wrapWithZodValidators('z.bigint()', field, inputType)); + } else if (inputType.type === 'Boolean') { + result.push(this.wrapWithZodValidators('z.boolean()', field, inputType)); + } else if (inputType.type === 'DateTime') { + result.push(this.wrapWithZodValidators('z.date()', field, inputType)); + } else if (inputType.type === 'Json') { + this.hasJson = true; + + result.push(this.wrapWithZodValidators('jsonSchema', field, inputType)); + } else if (inputType.type === 'True') { + result.push(this.wrapWithZodValidators('z.literal(true)', field, inputType)); + } else { + const isEnum = inputType.location === 'enumTypes'; + + if (inputType.namespace === 'prisma' || isEnum) { + if (inputType.type !== this.name && typeof inputType.type === 'string') { + this.addSchemaImport(inputType.type); + } + + result.push(this.generatePrismaStringLine(field, inputType, lines.length)); + } + } + + return result; + }, []); + + if (alternatives.length === 0) { + return []; + } + + if (alternatives.length > 1) { + alternatives = alternatives.map((alter) => alter.replace('.optional()', '')); + } + + const fieldName = alternatives.some((alt) => alt.includes(':')) ? '' : ` ${field.name}:`; + + const opt = !field.isRequired ? '.optional()' : ''; + + let resString = + alternatives.length === 1 ? alternatives.join(',\r\n') : `z.union([${alternatives.join(',\r\n')}])${opt}`; + + if (field.isNullable) { + resString += '.nullable()'; + } + + return [[` ${fieldName} ${resString} `, field, true]]; + } + + wrapWithZodValidators( + mainValidator: string, + field: PrismaDMMF.SchemaArg, + inputType: PrismaDMMF.SchemaArgInputType + ) { + let line = ''; + line = mainValidator; + + if (inputType.isList) { + line += '.array()'; + } + + if (!field.isRequired) { + line += '.optional()'; + } + + return line; + } + + addSchemaImport(name: string) { + this.schemaImports.add(name); + } + + generatePrismaStringLine( + field: PrismaDMMF.SchemaArg, + inputType: PrismaDMMF.SchemaArgInputType, + inputsLength: number + ) { + const isEnum = inputType.location === 'enumTypes'; + + const { isModelQueryType, modelName, queryName } = this.checkIsModelQueryType(inputType.type as string); + + const objectSchemaLine = isModelQueryType + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.resolveModelQuerySchemaName(modelName!, queryName!) + : `${inputType.type}ObjectSchema`; + const enumSchemaLine = `${inputType.type}Schema`; + + const schema = inputType.type === this.name ? objectSchemaLine : isEnum ? enumSchemaLine : objectSchemaLine; + + const arr = inputType.isList ? '.array()' : ''; + + const opt = !field.isRequired ? '.optional()' : ''; + + return inputsLength === 1 + ? ` ${field.name}: z.lazy(() => ${schema})${arr}${opt}` + : `z.lazy(() => ${schema})${arr}${opt}`; + } + + generateFieldValidators(zodStringWithMainType: string, field: PrismaDMMF.SchemaArg) { + const { isRequired, isNullable } = field; + + if (!isRequired) { + zodStringWithMainType += '.optional()'; + } + + if (isNullable) { + zodStringWithMainType += '.nullable()'; + } + + return zodStringWithMainType; + } + + prepareObjectSchema(zodObjectSchemaFields: string[]) { + const objectSchema = `${this.generateExportObjectSchemaStatement( + this.addFinalWrappers({ zodStringFields: zodObjectSchemaFields }) + )}\n`; + + const prismaImportStatement = this.generateImportPrismaStatement(); + + const json = this.generateJsonSchemaImplementation(); + + return `${this.generateObjectSchemaImportStatements()}${prismaImportStatement}${json}${objectSchema}`; + } + + generateExportObjectSchemaStatement(schema: string) { + let name = this.name; + + if (isAggregateInputType(name)) { + name = `${name}Type`; + } + const end = `export const ${this.name}ObjectSchema = Schema`; + return `const Schema: z.ZodType "'" + f + "'").join( + '|' + )}>> = ${schema};\n\n ${end}`; + } + + addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }) { + const fields = [...zodStringFields]; + + return this.wrapWithZodObject(fields) + '.strict()'; + } + + generateImportPrismaStatement() { + let prismaClientImportPath: string; + if (Transformer.isCustomPrismaClientOutputPath) { + /** + * If a custom location was designated for the prisma client, we need to figure out the + * relative path from {outputPath}/objects to {prismaClientCustomPath} + */ + const fromPath = path.join(Transformer.outputPath, 'objects'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const toPath = Transformer.prismaClientOutputPath!; + const relativePathFromOutputToPrismaClient = path + .relative(fromPath, toPath) + .split(path.sep) + .join(path.posix.sep); + prismaClientImportPath = relativePathFromOutputToPrismaClient; + } else { + /** + * If the default output path for prisma client (@prisma/client) is being used, we can import from it directly + * without having to resolve a relative path + */ + prismaClientImportPath = Transformer.prismaClientOutputPath; + } + return `import type { Prisma } from '${prismaClientImportPath}';\n\n`; + } + + generateJsonSchemaImplementation() { + let jsonSchemaImplementation = ''; + + if (this.hasJson) { + jsonSchemaImplementation += `\n`; + jsonSchemaImplementation += `const literalSchema = z.union([z.string(), z.number(), z.boolean()]);\n`; + jsonSchemaImplementation += `const jsonSchema: z.ZodType = z.lazy(() =>\n`; + jsonSchemaImplementation += ` z.union([literalSchema, z.array(jsonSchema.nullable()), z.record(jsonSchema.nullable())])\n`; + jsonSchemaImplementation += `);\n\n`; + } + + return jsonSchemaImplementation; + } + + generateObjectSchemaImportStatements() { + let generatedImports = this.generateImportZodStatement(); + generatedImports += this.generateSchemaImports(); + generatedImports += '\n\n'; + return generatedImports; + } + + generateSchemaImports() { + return [...this.schemaImports] + .map((name) => { + const { isModelQueryType, modelName } = this.checkIsModelQueryType(name); + if (isModelQueryType) { + return `import { ${modelName}Schema } from '../${modelName}.schema'`; + } else if (Transformer.enumNames.includes(name)) { + return `import { ${name}Schema } from '../enums/${name}.schema'`; + } else { + return `import { ${name}ObjectSchema } from './${name}.schema'`; + } + }) + .join(';\r\n'); + } + + checkIsModelQueryType(type: string) { + const modelQueryTypeSuffixToQueryName: Record = { + FindManyArgs: 'findMany', + }; + for (const modelQueryType of ['FindManyArgs']) { + if (type.includes(modelQueryType)) { + const modelQueryTypeSuffixIndex = type.indexOf(modelQueryType); + return { + isModelQueryType: true, + modelName: type.substring(0, modelQueryTypeSuffixIndex), + queryName: modelQueryTypeSuffixToQueryName[modelQueryType], + }; + } + } + return { isModelQueryType: false }; + } + + resolveModelQuerySchemaName(modelName: string, queryName: string) { + const modelNameCapitalized = modelName.charAt(0).toUpperCase() + modelName.slice(1); + return `${modelNameCapitalized}Schema.${queryName}`; + } + + wrapWithZodUnion(zodStringFields: string[]) { + let wrapped = ''; + + wrapped += 'z.union(['; + wrapped += '\n'; + wrapped += ' ' + zodStringFields.join(','); + wrapped += '\n'; + wrapped += '])'; + return wrapped; + } + + wrapWithZodObject(zodStringFields: string | string[]) { + let wrapped = ''; + + wrapped += 'z.object({'; + wrapped += '\n'; + wrapped += ' ' + zodStringFields; + wrapped += '\n'; + wrapped += '})'; + return wrapped; + } + + resolveObjectSchemaName() { + return this.name; + } + + async generateModelSchemas() { + const globalImports: string[] = []; + let globalExport = ''; + + for (const modelOperation of this.modelOperations) { + const { + model: modelName, + findUnique, + findFirst, + findMany, + // @ts-expect-error + createOne, + createMany, + // @ts-expect-error + deleteOne, + // @ts-expect-error + updateOne, + deleteMany, + updateMany, + // @ts-expect-error + upsertOne, + aggregate, + groupBy, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } = modelOperation; + + globalImports.push(`import { ${modelName}Schema } from './${modelName}.schema'`); + globalExport += `${modelName}: ${modelName}Schema,`; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const model = findModelByName(this.models, modelName)!; + + const { + selectImport, + includeImport, + selectZodSchemaLine, + includeZodSchemaLine, + selectZodSchemaLineLazy, + includeZodSchemaLineLazy, + } = this.resolveSelectIncludeImportAndZodSchemaLine(model); + + let imports = [`import { z } from 'zod'`, selectImport, includeImport]; + let codeBody = ''; + + if (findUnique) { + imports.push( + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + codeBody += `findUnique: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema }),`; + } + + if (findFirst) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithRelationInputObjectSchema } from './objects/${modelName}OrderByWithRelationInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, + `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` + ); + codeBody += `findFirst: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + } + + if (findMany) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithRelationInputObjectSchema } from './objects/${modelName}OrderByWithRelationInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, + `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` + ); + codeBody += `findMany: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + } + + if (createOne) { + imports.push( + `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'` + ); + codeBody += `create: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} data: ${modelName}CreateInputObjectSchema }),`; + } + + if (createMany) { + imports.push( + `import { ${modelName}CreateManyInputObjectSchema } from './objects/${modelName}CreateManyInput.schema'` + ); + codeBody += `createMany: z.object({ data: ${modelName}CreateManyInputObjectSchema }),`; + } + + if (deleteOne) { + imports.push( + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + codeBody += `'delete': z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema }),`; + } + + if (deleteMany) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'` + ); + codeBody += `deleteMany: z.object({ where: ${modelName}WhereInputObjectSchema.optional() }),`; + } + + if (updateOne) { + imports.push( + `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + codeBody += `update: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} data: ${modelName}UpdateInputObjectSchema, where: ${modelName}WhereUniqueInputObjectSchema }),`; + } + + if (updateMany) { + imports.push( + `import { ${modelName}UpdateManyMutationInputObjectSchema } from './objects/${modelName}UpdateManyMutationInput.schema'`, + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'` + ); + codeBody += `updateMany: z.object({ data: ${modelName}UpdateManyMutationInputObjectSchema, where: ${modelName}WhereInputObjectSchema.optional() }),`; + } + + if (upsertOne) { + imports.push( + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, + `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'`, + `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'` + ); + codeBody += `upsert: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema, create: ${modelName}CreateInputObjectSchema, update: ${modelName}UpdateInputObjectSchema }),`; + } + + const aggregateOperations = []; + if (this.aggregateOperationSupport[modelName].count) { + imports.push( + `import { ${modelName}CountAggregateInputObjectSchema } from './objects/${modelName}CountAggregateInput.schema'` + ); + aggregateOperations.push( + `_count: z.union([ z.literal(true), ${modelName}CountAggregateInputObjectSchema ]).optional()` + ); + } + if (this.aggregateOperationSupport[modelName].min) { + imports.push( + `import { ${modelName}MinAggregateInputObjectSchema } from './objects/${modelName}MinAggregateInput.schema'` + ); + aggregateOperations.push(`_min: ${modelName}MinAggregateInputObjectSchema.optional()`); + } + if (this.aggregateOperationSupport[modelName].max) { + imports.push( + `import { ${modelName}MaxAggregateInputObjectSchema } from './objects/${modelName}MaxAggregateInput.schema'` + ); + aggregateOperations.push(`_max: ${modelName}MaxAggregateInputObjectSchema.optional()`); + } + if (this.aggregateOperationSupport[modelName].avg) { + imports.push( + `import { ${modelName}AvgAggregateInputObjectSchema } from './objects/${modelName}AvgAggregateInput.schema'` + ); + aggregateOperations.push(`_avg: ${modelName}AvgAggregateInputObjectSchema.optional()`); + } + if (this.aggregateOperationSupport[modelName].sum) { + imports.push( + `import { ${modelName}SumAggregateInputObjectSchema } from './objects/${modelName}SumAggregateInput.schema'` + ); + aggregateOperations.push(`_sum: ${modelName}SumAggregateInputObjectSchema.optional()`); + } + + if (aggregate) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithRelationInputObjectSchema } from './objects/${modelName}OrderByWithRelationInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + + codeBody += `aggregate: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), ${aggregateOperations.join( + ', ' + )} }),`; + } + + if (groupBy) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithAggregationInputObjectSchema } from './objects/${modelName}OrderByWithAggregationInput.schema'`, + `import { ${modelName}ScalarWhereWithAggregatesInputObjectSchema } from './objects/${modelName}ScalarWhereWithAggregatesInput.schema'`, + `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` + ); + codeBody += `groupBy: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithAggregationInputObjectSchema, ${modelName}OrderByWithAggregationInputObjectSchema.array()]).optional(), having: ${modelName}ScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(${modelName}ScalarFieldEnumSchema), ${aggregateOperations.join( + ', ' + )} }),`; + } + + imports = [...new Set(imports)]; + + const filePath = path.join(Transformer.outputPath, `${modelName}.schema.ts`); + const content = ` + /* eslint-disable */ + ${imports.join(';\n')} + + export const ${modelName}Schema = { + ${indentString(codeBody, 4)} + }; + `; + + this.project.createSourceFile(filePath, content, { overwrite: true }); + } + + const indexFilePath = path.join(Transformer.outputPath, 'index.ts'); + const indexContent = ` +/* eslint-disable */ +${globalImports.join(';\n')} + +const schemas = { +${indentString(globalExport, 4)} +}; + +export default schemas; +`; + this.project.createSourceFile(indexFilePath, indexContent, { overwrite: true }); + } + + generateImportStatements(imports: (string | undefined)[]) { + let generatedImports = this.generateImportZodStatement(); + generatedImports += imports?.filter((importItem) => !!importItem).join(';\r\n') ?? ''; + generatedImports += '\n\n'; + return generatedImports; + } + + resolveSelectIncludeImportAndZodSchemaLine(model: PrismaDMMF.Model) { + const { name: modelName } = model; + + const hasRelationToAnotherModel = checkModelHasModelRelation(model); + + const selectImport = `import { ${modelName}SelectObjectSchema } from './objects/${modelName}Select.schema'`; + + const includeImport = hasRelationToAnotherModel + ? `import { ${modelName}IncludeObjectSchema } from './objects/${modelName}Include.schema'` + : ''; + + let selectZodSchemaLine = ''; + let includeZodSchemaLine = ''; + let selectZodSchemaLineLazy = ''; + let includeZodSchemaLineLazy = ''; + + const zodSelectObjectSchema = `${modelName}SelectObjectSchema.optional()`; + selectZodSchemaLine = `select: ${zodSelectObjectSchema},`; + selectZodSchemaLineLazy = `select: z.lazy(() => ${zodSelectObjectSchema}),`; + + if (hasRelationToAnotherModel) { + const zodIncludeObjectSchema = `${modelName}IncludeObjectSchema.optional()`; + includeZodSchemaLine = `include: ${zodIncludeObjectSchema},`; + includeZodSchemaLineLazy = `include: z.lazy(() => ${zodIncludeObjectSchema}),`; + } + + return { + selectImport, + includeImport, + selectZodSchemaLine, + includeZodSchemaLine, + selectZodSchemaLineLazy, + includeZodSchemaLineLazy, + }; + } +} diff --git a/packages/schema/src/plugins/zod/types.ts b/packages/schema/src/plugins/zod/types.ts new file mode 100644 index 000000000..49c35c023 --- /dev/null +++ b/packages/schema/src/plugins/zod/types.ts @@ -0,0 +1,24 @@ +import { DMMF as PrismaDMMF } from '@prisma/generator-helper'; +import { Project } from 'ts-morph'; + +export type TransformerParams = { + enumTypes?: PrismaDMMF.SchemaEnum[]; + fields?: PrismaDMMF.SchemaArg[]; + name?: string; + models?: PrismaDMMF.Model[]; + modelOperations?: PrismaDMMF.ModelMapping[]; + aggregateOperationSupport?: AggregateOperationSupport; + isDefaultPrismaClientOutput?: boolean; + prismaClientOutputPath?: string; + project: Project; +}; + +export type AggregateOperationSupport = { + [model: string]: { + count?: boolean; + min?: boolean; + max?: boolean; + sum?: boolean; + avg?: boolean; + }; +}; diff --git a/packages/schema/src/plugins/zod/utils/removeDir.ts b/packages/schema/src/plugins/zod/utils/removeDir.ts new file mode 100644 index 000000000..03f8d74f5 --- /dev/null +++ b/packages/schema/src/plugins/zod/utils/removeDir.ts @@ -0,0 +1,15 @@ +import path from 'path'; +import { promises as fs } from 'fs'; + +export default async function removeDir(dirPath: string, onlyContent: boolean) { + const dirEntries = await fs.readdir(dirPath, { withFileTypes: true }); + await Promise.all( + dirEntries.map(async (dirEntry) => { + const fullPath = path.join(dirPath, dirEntry.name); + return dirEntry.isDirectory() ? await removeDir(fullPath, false) : await fs.unlink(fullPath); + }) + ); + if (!onlyContent) { + await fs.rmdir(dirPath); + } +} diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index 52f354749..0e1ad71a1 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -153,6 +153,16 @@ attribute @map(_ name: String) @@@prisma */ attribute @@map(_ name: String) @@@prisma +/* +* Exclude a field from the Prisma Client (for example, a model that you do not want Prisma users to update). + */ +attribute @ignore() @@@prisma + +/* +* Exclude a model from the Prisma Client (for example, a model that you do not want Prisma users to update). + */ +attribute @@ignore() @@@prisma + /* * Automatically stores the time when a record was last updated. */ diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index aff1db9fa..263c335e6 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -24,7 +24,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -55,7 +55,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -90,7 +90,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -129,7 +129,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -179,7 +179,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, generateClient: false, diff --git a/packages/schema/tests/schema/cal-com.zmodel b/packages/schema/tests/schema/cal-com.zmodel index 26dea14a7..9d3f40196 100644 --- a/packages/schema/tests/schema/cal-com.zmodel +++ b/packages/schema/tests/schema/cal-com.zmodel @@ -13,12 +13,12 @@ generator client { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 7cb14631c..2d4e8b028 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { @@ -20,7 +20,9 @@ "license": "MIT", "dependencies": { "@prisma/generator-helper": "^4.7.1", - "@zenstackhq/language": "workspace:*" + "@zenstackhq/language": "workspace:*", + "prettier": "^2.8.3", + "ts-morph": "^16.0.0" }, "devDependencies": { "copyfiles": "^2.4.1", diff --git a/packages/sdk/src/code-gen.ts b/packages/sdk/src/code-gen.ts new file mode 100644 index 000000000..f58e6c9fd --- /dev/null +++ b/packages/sdk/src/code-gen.ts @@ -0,0 +1,38 @@ +import prettier from 'prettier'; +import { Project, SourceFile } from 'ts-morph'; + +const formatOptions = { + trailingComma: 'all', + tabWidth: 4, + printWidth: 120, + bracketSpacing: true, + semi: true, + singleQuote: true, + useTabs: false, + parser: 'typescript', +} as const; + +async function formatFile(sourceFile: SourceFile) { + try { + const content = sourceFile.getFullText(); + const formatted = prettier.format(content, formatOptions); + sourceFile.replaceWithText(formatted); + await sourceFile.save(); + } catch { + /* empty */ + } +} + +/** + * Emit a TS project to JS files. + */ +export async function emitProject(project: Project, format = true) { + if (format) { + await Promise.all( + project.getSourceFiles().map(async (sf) => { + await formatFile(sf); + }) + ); + } + await project.emit(); +} diff --git a/packages/sdk/src/dmmf-helpers/index.ts b/packages/sdk/src/dmmf-helpers/index.ts index 09e55e5ed..07c5b5a8a 100644 --- a/packages/sdk/src/dmmf-helpers/index.ts +++ b/packages/sdk/src/dmmf-helpers/index.ts @@ -4,3 +4,4 @@ export * from './model-helpers'; export * from './modelArgs-helpers'; export * from './select-helpers'; export * from './types'; +export * from './missing-types-helper'; diff --git a/packages/sdk/src/dmmf-helpers/missing-types-helper.ts b/packages/sdk/src/dmmf-helpers/missing-types-helper.ts new file mode 100644 index 000000000..8a6b553e4 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/missing-types-helper.ts @@ -0,0 +1,16 @@ +import { DMMF } from '@prisma/generator-helper'; +import { addMissingInputObjectTypesForAggregate } from './aggregate-helpers'; +import { addMissingInputObjectTypesForInclude } from './include-helpers'; +import { addMissingInputObjectTypesForModelArgs } from './modelArgs-helpers'; +import { addMissingInputObjectTypesForSelect } from './select-helpers'; + +export function addMissingInputObjectTypes( + inputObjectTypes: DMMF.InputType[], + outputObjectTypes: DMMF.OutputType[], + models: DMMF.Model[] +) { + addMissingInputObjectTypesForAggregate(inputObjectTypes, outputObjectTypes); + addMissingInputObjectTypesForSelect(inputObjectTypes, outputObjectTypes, models); + addMissingInputObjectTypesForModelArgs(inputObjectTypes, models); + addMissingInputObjectTypesForInclude(inputObjectTypes, models); +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 5aca99340..130e9ea8d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,3 +1,4 @@ +export * from './code-gen'; export * from './constants'; export * from './types'; export * from './utils'; diff --git a/packages/server/package.json b/packages/server/package.json index 60f9b387d..d99ea1560 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", @@ -26,17 +26,19 @@ "@zenstackhq/openapi": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", - "tiny-invariant": "^1.3.1" + "change-case": "^4.1.2", + "tiny-invariant": "^1.3.1", + "zod-validation-error": "^0.2.1" }, "devDependencies": { "@types/jest": "^29.4.0", + "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", "fastify": "^4.14.1", "fastify-plugin": "^4.5.0", "jest": "^29.4.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.5", - "typescript": "^4.9.4", - "@zenstackhq/testtools": "workspace:*" + "typescript": "^4.9.4" } } diff --git a/packages/server/src/fastify/plugin.ts b/packages/server/src/fastify/plugin.ts index fd6735c9a..37e03c6ad 100644 --- a/packages/server/src/fastify/plugin.ts +++ b/packages/server/src/fastify/plugin.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { DbClientContract } from '@zenstackhq/runtime'; +import { ModelZodSchema } from '@zenstackhq/runtime/zod'; import { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify'; import fp from 'fastify-plugin'; import { handleRequest, LoggerConfig } from '../openapi'; @@ -22,6 +23,11 @@ export interface PluginOptions { * Logger settings */ logger?: LoggerConfig; + + /** + * Path to the generated zod schemas + */ + zodSchemas?: ModelZodSchema; } const pluginHandler: FastifyPluginCallback = (fastify, options, done) => { @@ -47,6 +53,7 @@ const pluginHandler: FastifyPluginCallback = (fastify, options, d requestBody: request.body, prisma, logger: options.logger, + zodSchemas: options.zodSchemas, }); reply.status(response.status).send(response.body); diff --git a/packages/server/src/openapi/index.ts b/packages/server/src/openapi/index.ts index b6ea1f221..daa67be77 100644 --- a/packages/server/src/openapi/index.ts +++ b/packages/server/src/openapi/index.ts @@ -5,7 +5,10 @@ import { isPrismaClientUnknownRequestError, isPrismaClientValidationError, } from '@zenstackhq/runtime'; +import { getModelZodSchemas, ModelZodSchema } from '@zenstackhq/runtime/zod'; +import { capitalCase } from 'change-case'; import invariant from 'tiny-invariant'; +import { fromZodError } from 'zod-validation-error'; import { stripAuxFields } from './utils'; type LoggerMethod = (message: string, code?: string) => void; @@ -41,6 +44,7 @@ export type RequestContext = { requestBody: unknown; prisma: DbClientContract; logger?: LoggerConfig; + zodSchemas?: ModelZodSchema; }; /** @@ -51,6 +55,38 @@ export type Response = { body: unknown; }; +function getZodSchema(zodSchemas: ModelZodSchema | undefined, model: string, operation: keyof DbOperations) { + if (!zodSchemas) { + zodSchemas = getModelZodSchemas(); + } + if (zodSchemas[model]) { + return zodSchemas[model][operation]; + } else if (zodSchemas[capitalCase(model)]) { + return zodSchemas[capitalCase(model)][operation]; + } else { + return undefined; + } +} + +function zodValidate( + zodSchemas: ModelZodSchema | undefined, + model: string, + operation: keyof DbOperations, + args: unknown +) { + const zodSchema = getZodSchema(zodSchemas, model, operation); + if (zodSchema) { + const parseResult = zodSchema.safeParse(args); + if (parseResult.success) { + return { data: parseResult.data, error: undefined }; + } else { + return { data: undefined, error: fromZodError(parseResult.error).message }; + } + } else { + return { data: args, error: undefined }; + } +} + /** * Handles OpenApi requests */ @@ -61,6 +97,7 @@ export async function handleRequest({ requestBody, prisma, logger, + zodSchemas, }: RequestContext): Promise { const parts = path.split('/'); if (parts.length < 2) { @@ -85,6 +122,7 @@ export async function handleRequest({ return { status: 400, body: { message: 'invalid request method, only POST is supported' } }; } args = requestBody; + // TODO: upsert's status code should be conditional resCode = 201; break; @@ -121,6 +159,13 @@ export async function handleRequest({ return { status: 400, body: { message: 'invalid operation: ' + op } }; } + const { data, error } = zodValidate(zodSchemas, model, dbOp, args); + if (error) { + return { status: 400, body: { message: error } }; + } else { + args = data; + } + try { if (!prisma[model]) { return { status: 400, body: { message: `unknown model name: ${model}` } }; diff --git a/packages/server/tests/fastify-plugin.test.ts b/packages/server/tests/fastify-plugin.test.ts index 202eae87b..8b6f4e120 100644 --- a/packages/server/tests/fastify-plugin.test.ts +++ b/packages/server/tests/fastify-plugin.test.ts @@ -27,10 +27,14 @@ function makeUrl(path: string, q?: object) { describe('Fastify plugin tests', () => { it('run plugin', async () => { - const { prisma } = await loadSchema(schema); + const { prisma, zodSchemas } = await loadSchema(schema); const app = fastify(); - app.register(ZenStackFastifyPlugin, { prefix: '/api', getPrisma: () => prisma }); + app.register(ZenStackFastifyPlugin, { + prefix: '/api', + getPrisma: () => prisma, + zodSchemas, + }); let r = await app.inject({ method: 'GET', diff --git a/packages/testtools/package.json b/packages/testtools/package.json index bb64aa998..3df6f673c 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 933a3ff94..985d32937 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -59,14 +59,19 @@ generator js { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } + +plugin zod { + provider = '@core/zod' + output = '.zenstack/zod' +} `; export async function loadSchemaFromFile(schemaFile: string, addPrelude = true, pushDb = true) { @@ -106,6 +111,7 @@ export async function loadSchema(schema: string, addPrelude = true, pushDb = tru const policy = require(path.join(workDir, '.zenstack/policy')).default; const modelMeta = require(path.join(workDir, '.zenstack/model-meta')).default; + const zodSchemas = require(path.join(workDir, '.zenstack/zod')).default; return { prisma, @@ -113,5 +119,6 @@ export async function loadSchema(schema: string, addPrelude = true, pushDb = tru withOmit: () => withOmit(prisma, modelMeta), withPassword: () => withPassword(prisma, modelMeta), withPresets: (user?: AuthUser) => withPresets(prisma, { user }, policy, modelMeta), + zodSchemas, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34bf09523..f1ac5d157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -337,11 +337,15 @@ importers: '@prisma/generator-helper': ^4.7.1 '@zenstackhq/language': workspace:* copyfiles: ^2.4.1 + prettier: ^2.8.3 rimraf: ^3.0.2 + ts-morph: ^16.0.0 typescript: ^4.9.4 dependencies: '@prisma/generator-helper': 4.7.1 '@zenstackhq/language': link:../language/dist + prettier: 2.8.3 + ts-morph: 16.0.0 devDependencies: copyfiles: 2.4.1 rimraf: 3.0.2 @@ -355,6 +359,7 @@ importers: '@zenstackhq/runtime': workspace:* '@zenstackhq/sdk': workspace:* '@zenstackhq/testtools': workspace:* + change-case: ^4.1.2 copyfiles: ^2.4.1 fastify: ^4.14.1 fastify-plugin: ^4.5.0 @@ -363,11 +368,14 @@ importers: tiny-invariant: ^1.3.1 ts-jest: ^29.0.5 typescript: ^4.9.4 + zod-validation-error: ^0.2.1 dependencies: '@zenstackhq/openapi': link:../plugins/openapi/dist '@zenstackhq/runtime': link:../runtime/dist '@zenstackhq/sdk': link:../sdk/dist + change-case: 4.1.2 tiny-invariant: 1.3.1 + zod-validation-error: 0.2.1_zod@3.19.1 devDependencies: '@types/jest': 29.4.0 '@zenstackhq/testtools': link:../testtools/dist diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index 84f29c18c..123f67a13 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -126,7 +126,7 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "license": "MIT", "dependencies": { "@types/bcryptjs": "^2.4.2", @@ -156,7 +156,7 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/tests/integration/tests/schema/cal-com.zmodel b/tests/integration/tests/schema/cal-com.zmodel index 26dea14a7..9d3f40196 100644 --- a/tests/integration/tests/schema/cal-com.zmodel +++ b/tests/integration/tests/schema/cal-com.zmodel @@ -13,12 +13,12 @@ generator client { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } diff --git a/tests/integration/tests/schema/todo.zmodel b/tests/integration/tests/schema/todo.zmodel index 313cb04ac..19697086c 100644 --- a/tests/integration/tests/schema/todo.zmodel +++ b/tests/integration/tests/schema/todo.zmodel @@ -14,15 +14,20 @@ generator js { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } +plugin zod { + provider = '@core/zod' + output = '.zenstack/zod' +} + /* * Model for a space in which users can collaborate on Lists and Todos */ From 2d8bda5e57e6448319de4ae6d50fa321775eeef9 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 13 Mar 2023 17:36:53 +0800 Subject: [PATCH 2/4] fix test --- tests/integration/tests/schema/cal-com.zmodel | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/integration/tests/schema/cal-com.zmodel b/tests/integration/tests/schema/cal-com.zmodel index 9d3f40196..8984bc758 100644 --- a/tests/integration/tests/schema/cal-com.zmodel +++ b/tests/integration/tests/schema/cal-com.zmodel @@ -22,6 +22,11 @@ plugin policy { output = '.zenstack' } +plugin zod { + provider = '@core/zod' + output = '.zenstack/zod' +} + enum SchedulingType { ROUND_ROBIN @map("roundRobin") COLLECTIVE @map("collective") From 250537f0f200803d66b3f5fc0174a26cf8a38e01 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 13 Mar 2023 18:48:05 +0800 Subject: [PATCH 3/4] fix zod generation --- .../schema/src/plugins/zod/transformer.ts | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index c6a0f0501..8b73fe825 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -392,14 +392,8 @@ export default class Transformer { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const model = findModelByName(this.models, modelName)!; - const { - selectImport, - includeImport, - selectZodSchemaLine, - includeZodSchemaLine, - selectZodSchemaLineLazy, - includeZodSchemaLineLazy, - } = this.resolveSelectIncludeImportAndZodSchemaLine(model); + const { selectImport, includeImport, selectZodSchemaLineLazy, includeZodSchemaLineLazy } = + this.resolveSelectIncludeImportAndZodSchemaLine(model); let imports = [`import { z } from 'zod'`, selectImport, includeImport]; let codeBody = ''; @@ -408,7 +402,7 @@ export default class Transformer { imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `findUnique: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema }),`; + codeBody += `findUnique: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; } if (findFirst) { @@ -418,7 +412,7 @@ export default class Transformer { `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` ); - codeBody += `findFirst: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + codeBody += `findFirst: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; } if (findMany) { @@ -435,7 +429,7 @@ export default class Transformer { imports.push( `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'` ); - codeBody += `create: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} data: ${modelName}CreateInputObjectSchema }),`; + codeBody += `create: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${modelName}CreateInputObjectSchema }),`; } if (createMany) { @@ -449,7 +443,7 @@ export default class Transformer { imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `'delete': z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema }),`; + codeBody += `'delete': z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; } if (deleteMany) { @@ -464,7 +458,7 @@ export default class Transformer { `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'`, `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `update: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} data: ${modelName}UpdateInputObjectSchema, where: ${modelName}WhereUniqueInputObjectSchema }),`; + codeBody += `update: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${modelName}UpdateInputObjectSchema, where: ${modelName}WhereUniqueInputObjectSchema }),`; } if (updateMany) { @@ -481,7 +475,7 @@ export default class Transformer { `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'`, `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'` ); - codeBody += `upsert: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema, create: ${modelName}CreateInputObjectSchema, update: ${modelName}UpdateInputObjectSchema }),`; + codeBody += `upsert: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema, create: ${modelName}CreateInputObjectSchema, update: ${modelName}UpdateInputObjectSchema }),`; } const aggregateOperations = []; From b3ec4bf620659da6f311271641619489192c8091 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 13 Mar 2023 22:51:09 +0800 Subject: [PATCH 4/4] add tests --- packages/server/src/openapi/index.ts | 17 ++- packages/server/tests/open-api.test.ts | 172 +++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 packages/server/tests/open-api.test.ts diff --git a/packages/server/src/openapi/index.ts b/packages/server/src/openapi/index.ts index daa67be77..d150386b6 100644 --- a/packages/server/src/openapi/index.ts +++ b/packages/server/src/openapi/index.ts @@ -40,8 +40,8 @@ export type RequestHandlerOptions = { export type RequestContext = { method: string; path: string; - query: Record; - requestBody: unknown; + query?: Record; + requestBody?: unknown; prisma: DbClientContract; logger?: LoggerConfig; zodSchemas?: ModelZodSchema; @@ -104,6 +104,7 @@ export async function handleRequest({ return { status: 400, body: { error: 'invalid request path' } }; } + method = method.toUpperCase(); const op = parts.pop(); const model = parts.pop(); @@ -121,6 +122,10 @@ export async function handleRequest({ if (method !== 'POST') { return { status: 400, body: { message: 'invalid request method, only POST is supported' } }; } + if (!requestBody) { + return { status: 400, body: { message: 'missing request body' } }; + } + args = requestBody; // TODO: upsert's status code should be conditional @@ -136,7 +141,7 @@ export async function handleRequest({ if (method !== 'GET') { return { status: 400, body: { message: 'invalid request method, only GET is supported' } }; } - args = query.q ? unmarshal(query.q as string) : {}; + args = query?.q ? unmarshal(query.q as string) : {}; break; case 'update': @@ -144,6 +149,10 @@ export async function handleRequest({ if (method !== 'PUT' && method !== 'PATCH') { return { status: 400, body: { message: 'invalid request method, only PUT AND PATCH are supported' } }; } + if (!requestBody) { + return { status: 400, body: { message: 'missing request body' } }; + } + args = requestBody; break; @@ -152,7 +161,7 @@ export async function handleRequest({ if (method !== 'DELETE') { return { status: 400, body: { message: 'invalid request method, only DELETE is supported' } }; } - args = query.q ? unmarshal(query.q as string) : {}; + args = query?.q ? unmarshal(query.q as string) : {}; break; default: diff --git a/packages/server/tests/open-api.test.ts b/packages/server/tests/open-api.test.ts new file mode 100644 index 000000000..dee845e03 --- /dev/null +++ b/packages/server/tests/open-api.test.ts @@ -0,0 +1,172 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/// + +import { loadSchema } from '@zenstackhq/testtools'; +import { handleRequest } from '../src/openapi'; + +const schema = ` +model User { + id String @id @default(cuid()) + email String @unique + posts Post[] +} + +model Post { + id String @id @default(cuid()) + title String + author User? @relation(fields: [authorId], references: [id]) + authorId String? + published Boolean @default(false) + viewCount Int @default(0) +} +`; + +describe('OpenAPI server tests', () => { + it('crud', async () => { + const { prisma, zodSchemas } = await loadSchema(schema); + + let r = await handleRequest({ + method: 'get', + path: '/api/post/findMany', + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toHaveLength(0); + + r = await handleRequest({ + method: 'post', + path: '/api/user/create', + query: {}, + requestBody: { + include: { posts: true }, + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { title: 'post1', published: true, viewCount: 1 }, + { title: 'post2', published: false, viewCount: 2 }, + ], + }, + }, + }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(201); + expect(r.body).toEqual( + expect.objectContaining({ + email: 'user1@abc.com', + posts: expect.arrayContaining([ + expect.objectContaining({ title: 'post1' }), + expect.objectContaining({ title: 'post2' }), + ]), + }) + ); + const data: any = r.body; + expect(data.zenstack_guard).toBeUndefined(); + expect(data.zenstack_transaction).toBeUndefined(); + expect(data.posts[0].zenstack_guard).toBeUndefined(); + expect(data.posts[0].zenstack_transaction).toBeUndefined(); + + r = await handleRequest({ + method: 'get', + path: '/api/post/findMany', + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toHaveLength(2); + + r = await handleRequest({ + method: 'get', + path: '/api/post/findMany', + query: { q: JSON.stringify({ where: { viewCount: { gt: 1 } } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toHaveLength(1); + + r = await handleRequest({ + method: 'put', + path: '/api/user/update', + requestBody: { where: { id: 'user1' }, data: { email: 'user1@def.com' } }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect((r.body as any).email).toBe('user1@def.com'); + + r = await handleRequest({ + method: 'get', + path: '/api/post/count', + query: { q: JSON.stringify({ where: { viewCount: { gt: 1 } } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toBe(1); + + r = await handleRequest({ + method: 'get', + path: '/api/post/aggregate', + query: { q: JSON.stringify({ _sum: { viewCount: true } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect((r.body as any)._sum.viewCount).toBe(3); + + r = await handleRequest({ + method: 'get', + path: '/api/post/groupBy', + query: { q: JSON.stringify({ by: ['published'], _sum: { viewCount: true } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), + expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), + ]) + ); + + r = await handleRequest({ + method: 'delete', + path: '/api/user/deleteMany', + query: { q: JSON.stringify({ where: { id: 'user1' } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect((r.body as any).count).toBe(1); + }); + + it('validation error', async () => { + const { prisma, zodSchemas } = await loadSchema(schema); + + let r = await handleRequest({ + method: 'get', + path: '/api/post/findUnique', + prisma, + zodSchemas, + }); + expect(r.status).toBe(400); + expect((r.body as any).message).toContain('Validation error'); + expect((r.body as any).message).toContain('where'); + + r = await handleRequest({ + method: 'post', + path: '/api/post/create', + requestBody: { data: {} }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(400); + expect((r.body as any).message).toContain('Validation error'); + expect((r.body as any).message).toContain('data.title'); + }); +});