diff --git a/example/test.graphql b/example/test.graphql index 0322defd..5698e075 100644 --- a/example/test.graphql +++ b/example/test.graphql @@ -122,6 +122,25 @@ directive @constraint( uniqueTypeName: String ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION +directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + type Comment { replies: [Comment!] } + +# @oneOf examples - exactly one field must be provided +input SearchFilter @oneOf { + userId: ID + email: String + username: String +} + +input MediaInput @oneOf { + url: URL + file: FileInput +} + +input FileInput { + name: String! + size: Int! +} diff --git a/example/types.ts b/example/types.ts index 9c0aec8b..484cae51 100644 --- a/example/types.ts +++ b/example/types.ts @@ -21,10 +21,9 @@ export type Admin = { lastModifiedAt?: Maybe; }; -export type AttributeInput = { - key?: InputMaybe; - val?: InputMaybe; -}; +export type AttributeInput = + { key: Scalars['String']['input']; val?: never; } + | { key?: never; val: Scalars['String']['input']; }; export enum ButtonComponentType { Button = 'BUTTON', @@ -36,18 +35,16 @@ export type Comment = { replies?: Maybe>; }; -export type ComponentInput = { - child?: InputMaybe; - childrens?: InputMaybe>>; - event?: InputMaybe; - name: Scalars['String']['input']; - type: ButtonComponentType; -}; +export type ComponentInput = + { child: ComponentInput; childrens?: never; event?: never; name?: never; type?: never; } + | { child?: never; childrens: Array>; event?: never; name?: never; type?: never; } + | { child?: never; childrens?: never; event: EventInput; name?: never; type?: never; } + | { child?: never; childrens?: never; event?: never; name: Scalars['String']['input']; type?: never; } + | { child?: never; childrens?: never; event?: never; name?: never; type: ButtonComponentType; }; -export type DropDownComponentInput = { - dropdownComponent?: InputMaybe; - getEvent: EventInput; -}; +export type DropDownComponentInput = + { dropdownComponent: ComponentInput; getEvent?: never; } + | { dropdownComponent?: never; getEvent: EventInput; }; export type EventArgumentInput = { name: Scalars['String']['input']; @@ -69,10 +66,9 @@ export type Guest = { lastLoggedIn?: Maybe; }; -export type HttpInput = { - method?: InputMaybe; - url: Scalars['URL']['input']; -}; +export type HttpInput = + { method: HttpMethod; url?: never; } + | { method?: never; url: Scalars['URL']['input']; }; export enum HttpMethod { Get = 'GET', @@ -100,19 +96,19 @@ export type Namer = { name?: Maybe; }; -export type PageInput = { - attributes?: InputMaybe>; - date?: InputMaybe; - height: Scalars['Float']['input']; - id: Scalars['ID']['input']; - layout: LayoutInput; - pageType: PageType; - postIDs?: InputMaybe>; - show: Scalars['Boolean']['input']; - tags?: InputMaybe>>; - title: Scalars['String']['input']; - width: Scalars['Int']['input']; -}; +export type PageInput = + { attributes: Array; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date: Scalars['Date']['input']; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height: Scalars['Float']['input']; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id: Scalars['ID']['input']; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout: LayoutInput; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType: PageType; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs: Array>>>; postIDs2?: never; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2: Array>>; show?: never; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show: Scalars['Boolean']['input']; tags?: never; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags: Array>; title?: never; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title: Scalars['String']['input']; width?: never; } + | { attributes?: never; date?: never; height?: never; id?: never; layout?: never; pageType?: never; postIDs?: never; postIDs2?: never; show?: never; tags?: never; title?: never; width: Scalars['Int']['input']; }; export enum PageType { BasicAuth = 'BASIC_AUTH', diff --git a/package.json b/package.json index 1fbe5a5e..612079af 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "ts-jest": "29.4.6", "typescript": "5.9.3", "valibot": "1.2.0", - "vitest": "^4.0.0", + "vitest": "^3.2.4", "yup": "1.7.1", "zod": "4.3.5" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37342ac4..c58e4121 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^7.0.0 - version: 7.0.1(@vue/compiler-sfc@3.5.12)(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2)) + version: 7.0.1(@vue/compiler-sfc@3.5.12)(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2)) '@graphql-codegen/cli': specifier: 6.1.1 version: 6.1.1(@types/node@24.10.9)(graphql@16.12.0)(typescript@5.9.3) @@ -70,8 +70,8 @@ importers: specifier: 1.2.0 version: 1.2.0(typescript@5.9.3) vitest: - specifier: ^4.0.0 - version: 4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) + specifier: ^3.2.4 + version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) yup: specifier: 1.7.1 version: 1.7.1 @@ -1316,9 +1316,6 @@ packages: '@sinonjs/fake-timers@13.0.5': resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@stylistic/eslint-plugin@5.7.0': resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1554,34 +1551,34 @@ packages: vitest: optional: true - '@vitest/expect@4.0.17': - resolution: {integrity: sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@4.0.17': - resolution: {integrity: sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.0.17': - resolution: {integrity: sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@4.0.17': - resolution: {integrity: sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@4.0.17': - resolution: {integrity: sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@4.0.17': - resolution: {integrity: sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@4.0.17': - resolution: {integrity: sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@vue/compiler-core@3.5.12': resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} @@ -1784,8 +1781,8 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} chalk@4.1.2: @@ -1811,6 +1808,10 @@ packages: chardet@2.1.0: resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + ci-info@4.3.0: resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} engines: {node: '>=8'} @@ -1950,6 +1951,10 @@ packages: babel-plugin-macros: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2820,6 +2825,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -2927,6 +2935,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lower-case-first@2.0.2: resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==} @@ -3222,9 +3233,6 @@ packages: object-deep-merge@2.0.0: resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3328,6 +3336,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3624,6 +3636,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3665,6 +3680,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -3673,8 +3691,16 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} title-case@3.0.3: @@ -3852,6 +3878,11 @@ packages: resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==} engines: {node: '>=12'} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3892,32 +3923,26 @@ packages: yaml: optional: true - vitest@4.0.17: - resolution: {integrity: sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.17 - '@vitest/browser-preview': 4.0.17 - '@vitest/browser-webdriverio': 4.0.17 - '@vitest/ui': 4.0.17 + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@opentelemetry/api': + '@types/debug': optional: true '@types/node': optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': + '@vitest/browser': optional: true '@vitest/ui': optional: true @@ -4058,7 +4083,7 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@antfu/eslint-config@7.0.1(@vue/compiler-sfc@3.5.12)(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2))': + '@antfu/eslint-config@7.0.1(@vue/compiler-sfc@3.5.12)(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2))': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 @@ -4067,7 +4092,7 @@ snapshots: '@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.4.0)) '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3) '@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2)) + '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2)) ansis: 4.2.0 cac: 6.7.14 eslint: 9.39.2(jiti@2.4.0) @@ -5563,8 +5588,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.7.0(eslint@9.39.2(jiti@2.4.0))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.4.0)) @@ -5800,55 +5823,58 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.7.13': optional: true - '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2))': + '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2))': dependencies: '@typescript-eslint/scope-manager': 8.53.0 '@typescript-eslint/utils': 8.53.0(eslint@9.39.2(jiti@2.4.0))(typescript@5.9.3) eslint: 9.39.2(jiti@2.4.0) optionalDependencies: typescript: 5.9.3 - vitest: 4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.17': + '@vitest/expect@3.2.4': dependencies: - '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.17 - '@vitest/utils': 4.0.17 - chai: 6.2.2 - tinyrainbow: 3.0.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 - '@vitest/mocker@4.0.17(vite@7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.0.17 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) - '@vitest/pretty-format@4.0.17': + '@vitest/pretty-format@3.2.4': dependencies: - tinyrainbow: 3.0.3 + tinyrainbow: 2.0.0 - '@vitest/runner@4.0.17': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 4.0.17 + '@vitest/utils': 3.2.4 pathe: 2.0.3 + strip-literal: 3.1.0 - '@vitest/snapshot@4.0.17': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 4.0.17 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.17': {} + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 - '@vitest/utils@4.0.17': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 4.0.17 - tinyrainbow: 3.0.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 '@vue/compiler-core@3.5.12': dependencies: @@ -6075,7 +6101,13 @@ snapshots: ccount@2.0.1: {} - chai@6.2.2: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 chalk@4.1.2: dependencies: @@ -6118,6 +6150,8 @@ snapshots: chardet@2.1.0: {} + check-error@2.1.3: {} + ci-info@4.3.0: {} ci-info@4.3.1: {} @@ -6231,6 +6265,8 @@ snapshots: dedent@1.6.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -7331,6 +7367,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -7433,6 +7471,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lower-case-first@2.0.2: dependencies: tslib: 2.8.1 @@ -7884,8 +7924,6 @@ snapshots: object-deep-merge@2.0.0: {} - obug@2.1.1: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -7990,6 +8028,8 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -8279,6 +8319,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8319,6 +8363,8 @@ snapshots: tinybench@2.9.0: {} + tinyexec@0.3.2: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8326,7 +8372,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinyrainbow@3.0.3: {} + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} title-case@3.0.3: dependencies: @@ -8492,6 +8542,27 @@ snapshots: value-or-promise@1.0.12: {} + vite-node@3.2.4(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2): dependencies: esbuild: 0.27.2 @@ -8506,29 +8577,33 @@ snapshots: jiti: 2.4.0 yaml: 2.8.2 - vitest@4.0.17(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2): dependencies: - '@vitest/expect': 4.0.17 - '@vitest/mocker': 4.0.17(vite@7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.17 - '@vitest/runner': 4.0.17 - '@vitest/snapshot': 4.0.17 - '@vitest/spy': 4.0.17 - '@vitest/utils': 4.0.17 - es-module-lexer: 1.7.0 + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 expect-type: 1.3.0 magic-string: 0.30.21 - obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 1.0.2 + tinyexec: 0.3.2 tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 vite: 7.3.1(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@24.10.9)(jiti@2.4.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 '@types/node': 24.10.9 transitivePeerDependencies: - jiti @@ -8539,6 +8614,7 @@ snapshots: - sass-embedded - stylus - sugarss + - supports-color - terser - tsx - yaml diff --git a/src/zod/index.ts b/src/zod/index.ts index 9161369b..488b6174 100644 --- a/src/zod/index.ts +++ b/src/zod/index.ts @@ -45,31 +45,30 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor { initialEmit(): string { return ( - `\n${ - [ - new DeclarationBlock({}) - .asKind('type') - .withName('Properties') - .withContent(['Required<{', ' [K in keyof T]: z.ZodType;', '}>'].join('\n')) - .string, - // Unfortunately, zod doesn’t provide non-null defined any schema. - // This is a temporary hack until it is fixed. - // see: https://github.com/colinhacks/zod/issues/884 - new DeclarationBlock({}).asKind('type').withName('definedNonNullAny').withContent('{}').string, - new DeclarationBlock({}) - .export() - .asKind('const') - .withName(`isDefinedNonNullAny`) - .withContent(`(v: any): v is definedNonNullAny => v !== undefined && v !== null`) - .string, - new DeclarationBlock({}) - .export() - .asKind('const') - .withName(`${anySchema}`) - .withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`) - .string, - ...this.enumDeclarations, - ].join('\n')}` + `\n${[ + new DeclarationBlock({}) + .asKind('type') + .withName('Properties') + .withContent(['Required<{', ' [K in keyof T]: z.ZodType;', '}>'].join('\n')) + .string, + // Unfortunately, zod doesn’t provide non-null defined any schema. + // This is a temporary hack until it is fixed. + // see: https://github.com/colinhacks/zod/issues/884 + new DeclarationBlock({}).asKind('type').withName('definedNonNullAny').withContent('{}').string, + new DeclarationBlock({}) + .export() + .asKind('const') + .withName(`isDefinedNonNullAny`) + .withContent(`(v: any): v is definedNonNullAny => v !== undefined && v !== null`) + .string, + new DeclarationBlock({}) + .export() + .asKind('const') + .withName(`${anySchema}`) + .withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`) + .string, + ...this.enumDeclarations, + ].join('\n')}` ); } @@ -79,6 +78,13 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor { const visitor = this.createVisitor('input'); const name = visitor.convertName(node.name.value); this.importTypes.push(name); + + const hasOneOf = node.directives?.some(directive => directive?.name.value === 'oneOf'); + if (hasOneOf) { + const result = this.buildOneOfInputFields(node.fields ?? [], visitor, name) + return result; + } + return this.buildInputFields(node.fields ?? [], visitor, name); }, }; @@ -276,6 +282,34 @@ export class ZodSchemaVisitor extends BaseSchemaVisitor { .string; } } + + protected buildOneOfInputFields( + fields: readonly (FieldDefinitionNode | InputValueDefinitionNode)[], + visitor: Visitor, + name: string, + ) { + const typeName = visitor.prefixTypeNamespace(name); + const union = generateFieldUnionZodSchema(this.config, visitor, fields, 2) + + switch (this.config.validationSchemaExportType) { + case 'const': + return new DeclarationBlock({}) + .export() + .asKind('const') + .withName(`${typeName}Schema`) + .withContent(['z.union([', union, '])'].join('\n')) + .string; + + case 'function': + default: + return new DeclarationBlock({}) + .export() + .asKind('function') + .withName(`${name}Schema(): z.ZodUnion`) + .withBlock([indent(`return z.union([`), `${union}`, indent('])')].join('\n')) + .string; + } + } } function generateFieldZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string { @@ -283,6 +317,51 @@ function generateFieldZodSchema(config: ValidationSchemaPluginConfig, visitor: V return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount); } +function generateFieldUnionZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, fields: readonly (FieldDefinitionNode | InputValueDefinitionNode)[], indentCount: number): string { + const union = fields.map((field) => { + const objectFields = fields.map((nestedObjectField) => { + const isSameField = field.name === nestedObjectField.name; + + if (!isSameField) { + return indent(`${nestedObjectField.name.value}: z.never()`, indentCount + 1); + } + + if (isListType(nestedObjectField.type)) { + const gen = generateFieldTypeZodSchema(config, visitor, nestedObjectField, nestedObjectField.type.type, nestedObjectField.type); + + const maybeLazyGen = `z.array(${maybeLazy(visitor, nestedObjectField.type.type, gen)})`; + const appliedDirectivesGen = applyDirectives(config, nestedObjectField, maybeLazyGen); + + return indent(`${nestedObjectField.name.value}: ${appliedDirectivesGen}`, 3); + } + + if (isNonNullType(nestedObjectField.type)) { + const gen = generateFieldTypeZodSchema(config, visitor, field, nestedObjectField.type.type, nestedObjectField.type); + + return indent(`${nestedObjectField.name.value}: ${maybeLazy(visitor, nestedObjectField.type.type, gen)}`, indentCount + 1); + } + + if (isNamedType(nestedObjectField.type)) { + const gen = generateNameNodeZodSchema(config, visitor, nestedObjectField.type.name); + + const appliedDirectivesGen = applyDirectives(config, nestedObjectField, gen); + + if (visitor.shouldEmitAsNotAllowEmptyString(nestedObjectField.type.name.value)) + return indent(`${nestedObjectField.name.value}: ${maybeLazy(visitor, nestedObjectField.type, appliedDirectivesGen)}.min(1)`, indentCount + 1); + + return indent(`${nestedObjectField.name.value}: ${maybeLazy(visitor, nestedObjectField.type, appliedDirectivesGen)}`, indentCount + 1); + } + + console.warn('unhandled type:', field.type); + return ''; + }); + + return [indent('z.object({', indentCount), objectFields.join(',\n'), indent('})', indentCount)].join('\n'); + }).join(',\n'); + + return union; +} + function generateFieldTypeZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string { if (isListType(type)) { const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type); diff --git a/src/zodv4/index.ts b/src/zodv4/index.ts index 38e1aabe..d59ebc49 100644 --- a/src/zodv4/index.ts +++ b/src/zodv4/index.ts @@ -44,31 +44,43 @@ export class ZodV4SchemaVisitor extends BaseSchemaVisitor { initialEmit(): string { return ( - `\n${ - [ - new DeclarationBlock({}) - .asKind('type') - .withName('Properties') - .withContent(['Required<{', ' [K in keyof T]: z.ZodType;', '}>'].join('\n')) - .string, - // Unfortunately, zod doesn’t provide non-null defined any schema. - // This is a temporary hack until it is fixed. - // see: https://github.com/colinhacks/zod/issues/884 - new DeclarationBlock({}).asKind('type').withName('definedNonNullAny').withContent('{}').string, - new DeclarationBlock({}) - .export() - .asKind('const') - .withName(`isDefinedNonNullAny`) - .withContent(`(v: any): v is definedNonNullAny => v !== undefined && v !== null`) - .string, - new DeclarationBlock({}) - .export() - .asKind('const') - .withName(`${anySchema}`) - .withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`) - .string, - ...this.enumDeclarations, - ].join('\n')}` + `\n${[ + new DeclarationBlock({}) + .asKind('type') + .withName('Properties') + .withContent(['Required<{', ' [K in keyof T]: z.ZodType;', '}>'].join('\n')) + .string, + new DeclarationBlock({}) + .asKind('type') + .withName('OneOf') + .withContent([ + '{', + ' [K in keyof Required]: Required<{', + ' [V in keyof Pick, K>]: z.ZodType, K>[V], unknown, any>', + ' } & {', + ' [P in Exclude]: z.ZodNever', + ' }>', + '}[keyof T]', + ].join('\n')) + .string, + // Unfortunately, zod doesn’t provide non-null defined any schema. + // This is a temporary hack until it is fixed. + // see: https://github.com/colinhacks/zod/issues/884 + new DeclarationBlock({}).asKind('type').withName('definedNonNullAny').withContent('{}').string, + new DeclarationBlock({}) + .export() + .asKind('const') + .withName(`isDefinedNonNullAny`) + .withContent(`(v: any): v is definedNonNullAny => v !== undefined && v !== null`) + .string, + new DeclarationBlock({}) + .export() + .asKind('const') + .withName(`${anySchema}`) + .withContent(`z.any().refine((v) => isDefinedNonNullAny(v))`) + .string, + ...this.enumDeclarations, + ].join('\n')}` ); } @@ -78,6 +90,13 @@ export class ZodV4SchemaVisitor extends BaseSchemaVisitor { const visitor = this.createVisitor('input'); const name = visitor.convertName(node.name.value); this.importTypes.push(name); + + const hasOneOf = node.directives?.some(directive => directive?.name.value === 'oneOf'); + if (hasOneOf) { + const result = this.buildOneOfInputFields(node.fields ?? [], visitor, name) + return result; + } + return this.buildInputFields(node.fields ?? [], visitor, name); }, }; @@ -272,6 +291,34 @@ export class ZodV4SchemaVisitor extends BaseSchemaVisitor { .string; } } + + protected buildOneOfInputFields( + fields: readonly (FieldDefinitionNode | InputValueDefinitionNode)[], + visitor: Visitor, + name: string, + ) { + const typeName = visitor.prefixTypeNamespace(name); + const union = generateFieldUnionZodSchema(this.config, visitor, fields, 2) + + switch (this.config.validationSchemaExportType) { + case 'const': + return new DeclarationBlock({}) + .export() + .asKind('const') + .withName(`${typeName}Schema`) + .withContent(['z.union([', union, '])'].join('\n')) + .string; + + case 'function': + default: + return new DeclarationBlock({}) + .export() + .asKind('function') + .withName(`${name}Schema(): z.ZodUnion>[]>`) + .withBlock([indent(`return z.union([`), `${union}`, indent('])')].join('\n')) + .string; + } + } } function generateFieldZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, indentCount: number): string { @@ -279,6 +326,51 @@ function generateFieldZodSchema(config: ValidationSchemaPluginConfig, visitor: V return indent(`${field.name.value}: ${maybeLazy(visitor, field.type, gen)}`, indentCount); } +function generateFieldUnionZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, fields: readonly (FieldDefinitionNode | InputValueDefinitionNode)[], indentCount: number): string { + const union = fields.map((field) => { + const objectFields = fields.map((nestedObjectField) => { + const isSameField = field.name === nestedObjectField.name; + + if (!isSameField) { + return indent(`${nestedObjectField.name.value}: z.never()`, indentCount + 1); + } + + if (isListType(nestedObjectField.type)) { + const gen = generateFieldTypeZodSchema(config, visitor, nestedObjectField, nestedObjectField.type.type, nestedObjectField.type); + + const maybeLazyGen = `z.array(${maybeLazy(visitor, nestedObjectField.type.type, gen)})`; + const appliedDirectivesGen = applyDirectives(config, nestedObjectField, maybeLazyGen); + + return indent(`${nestedObjectField.name.value}: ${appliedDirectivesGen}`, 3); + } + + if (isNonNullType(nestedObjectField.type)) { + const gen = generateFieldTypeZodSchema(config, visitor, field, nestedObjectField.type.type, nestedObjectField.type); + + return indent(`${nestedObjectField.name.value}: ${maybeLazy(visitor, nestedObjectField.type.type, gen)}`, indentCount + 1); + } + + if (isNamedType(nestedObjectField.type)) { + const gen = generateNameNodeZodSchema(config, visitor, nestedObjectField.type.name); + + const appliedDirectivesGen = applyDirectives(config, nestedObjectField, gen); + + if (visitor.shouldEmitAsNotAllowEmptyString(nestedObjectField.type.name.value)) + return indent(`${nestedObjectField.name.value}: ${maybeLazy(visitor, nestedObjectField.type, appliedDirectivesGen)}.min(1)`, indentCount + 1); + + return indent(`${nestedObjectField.name.value}: ${maybeLazy(visitor, nestedObjectField.type, appliedDirectivesGen)}`, indentCount + 1); + } + + console.warn('unhandled type:', field.type); + return ''; + }); + + return [indent('z.object({', indentCount), objectFields.join(',\n'), indent('})', indentCount)].join('\n'); + }).join(',\n'); + + return union; +} + function generateFieldTypeZodSchema(config: ValidationSchemaPluginConfig, visitor: Visitor, field: InputValueDefinitionNode | FieldDefinitionNode, type: TypeNode, parentType?: TypeNode): string { if (isListType(type)) { const gen = generateFieldTypeZodSchema(config, visitor, field, type.type, type); diff --git a/tests/zod.spec.ts b/tests/zod.spec.ts index e13d420c..9b37bcce 100644 --- a/tests/zod.spec.ts +++ b/tests/zod.spec.ts @@ -118,6 +118,43 @@ describe('zod', () => { `) }) + it('array w/ scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ArrayInput { + a: [Count] + b: [Text!] + c: [Count!]! + d: [[Text]] + e: [[Count]!] + f: [[Text]!]! + } + + scalar Count + scalar Text + `); + + const scalars = { + Text: 'string', + Count: 'number', + } + + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ArrayInputSchema(): z.ZodObject> { + return z.object({ + a: z.array(z.number().nullable()).nullish(), + b: z.array(z.string()).nullish(), + c: z.array(z.number()), + d: z.array(z.array(z.string().nullable()).nullish()).nullish(), + e: z.array(z.array(z.number().nullable())).nullish(), + f: z.array(z.array(z.string().nullable())) + }) + } + " + `) + }) + it('ref input object', async () => { const schema = buildSchema(/* GraphQL */ ` input AInput { @@ -1861,4 +1898,809 @@ describe('zod', () => { " `); }); + + describe('with oneOf directive', () => { + it('primitive defined', async () => { + const schema = buildSchema(/* GraphQL */ ` + input PrimitiveInput @oneOf { + a: ID + b: String + c: Boolean + d: Int + e: Float + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = { + ID: 'string', + } + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(result.prepend).toMatchInlineSnapshot(` + [ + "import { z } from 'zod'", + ] + `); + + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function PrimitiveInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + a: z.string(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.string(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.boolean(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.number(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.number() + }) + ]) + } + " + `); + }); + + it('array input', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ArrayInput @oneOf { + a: [String] + b: [String!] + c: [[String]] + d: [[String]!] + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ArrayInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + a: z.array(z.string().nullable()), + b: z.never(), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.array(z.string()), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.array(z.array(z.string().nullable()).nullish()), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.array(z.array(z.string().nullable())) + }) + ]) + } + " + `) + }) + + it('array input w/ scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ArrayInput @oneOf { + a: [Count] + b: [Text!] + c: [[Text]] + d: [[Count]!] + } + + scalar Count + scalar Text + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + + const scalars = { + Text: 'string', + Count: 'number', + } + + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ArrayInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + a: z.array(z.number().nullable()), + b: z.never(), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.array(z.string()), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.array(z.array(z.string().nullable()).nullish()), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.array(z.array(z.number().nullable())) + }) + ]) + } + " + `) + }) + + it('ref input object', async () => { + const schema = buildSchema(/* GraphQL */ ` + input AInput @oneOf { + b: BInput + c: CInput + } + input BInput @oneOf { + c: CInput + d: DInput + } + input CInput @oneOf { + d: DInput + e: EInput + } + input DInput @oneOf { + e: EInput + a: AInput + } + input EInput @oneOf { + a: AInput + b: BInput + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function AInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + b: z.lazy(() => BInputSchema()), + c: z.never() + }), + z.object({ + b: z.never(), + c: z.lazy(() => CInputSchema()) + }) + ]) + } + + export function BInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + c: z.lazy(() => CInputSchema()), + d: z.never() + }), + z.object({ + c: z.never(), + d: z.lazy(() => DInputSchema()) + }) + ]) + } + + export function CInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + d: z.lazy(() => DInputSchema()), + e: z.never() + }), + z.object({ + d: z.never(), + e: z.lazy(() => EInputSchema()) + }) + ]) + } + + export function DInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + e: z.lazy(() => EInputSchema()), + a: z.never() + }), + z.object({ + e: z.never(), + a: z.lazy(() => AInputSchema()) + }) + ]) + } + + export function EInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + a: z.lazy(() => AInputSchema()), + b: z.never() + }), + z.object({ + a: z.never(), + b: z.lazy(() => BInputSchema()) + }) + ]) + } + " + `) + }) + + it('nested input object', async () => { + const schema = buildSchema(/* GraphQL */ ` + input NestedInput @oneOf { + child: NestedInput + childrens: [NestedInput] + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function NestedInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + child: z.lazy(() => NestedInputSchema()), + childrens: z.never() + }), + z.object({ + child: z.never(), + childrens: z.array(z.lazy(() => NestedInputSchema().nullable())) + }) + ]) + } + " + `) + }) + + it('enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum PageType { + PUBLIC + BASIC_AUTH + } + enum AuthMethod { + OAUTH + JWT + API_KEY + } + input PageInput @oneOf { + pageType: PageType + authMethod: AuthMethod + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zod', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export const PageTypeSchema = z.nativeEnum(PageType); + + export const AuthMethodSchema = z.nativeEnum(AuthMethod); + + export function PageInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + pageType: PageTypeSchema, + authMethod: z.never() + }), + z.object({ + pageType: z.never(), + authMethod: AuthMethodSchema + }) + ]) + } + " + `) + }) + + it('with scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + input Say @oneOf { + phrase: Text + times: Count + word: Word + } + + scalar Count + scalar Text + scalar Word # unknown scalar, should be any + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + scalars: { + Text: 'string', + Count: 'number', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function SaySchema(): z.ZodUnion { + return z.union([ + z.object({ + phrase: z.string(), + times: z.never(), + word: z.never() + }), + z.object({ + phrase: z.never(), + times: z.number(), + word: z.never() + }), + z.object({ + phrase: z.never(), + times: z.never(), + word: definedNonNullAnySchema + }) + ]) + } + " + `) + }); + + it('with notAllowEmptyString', async () => { + const schema = buildSchema(/* GraphQL */ ` + input PrimitiveInput @oneOf { + a: ID + b: String + c: Boolean + d: Int + e: Float + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function PrimitiveInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + a: z.string().min(1), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.string().min(1), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.boolean(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.number(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.number() + }) + ]) + } + " + `) + }); + + it('with notAllowEmptyString and custom directive', async () => { + const schema = buildSchema(/* GraphQL */ ` + input PrimitiveInput @oneOf { + a: ID + b: String + c: Boolean + d: Int + e: Float + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function PrimitiveInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + a: z.string().min(1), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.string().min(1), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.boolean(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.number(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.number() + }) + ]) + } + " + `) + }); + + it('with notAllowEmptyString and nested input', async () => { + const schema = buildSchema(/* GraphQL */ ` + input InputOne @oneOf { + fieldOne: InputNestedOne + fieldTwo: InputNestedTwo + } + + input InputNestedOne { + field: String! + } + + input InputNestedTwo { + field: String! + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function InputOneSchema(): z.ZodUnion { + return z.union([ + z.object({ + fieldOne: z.lazy(() => InputNestedOneSchema()), + fieldTwo: z.never() + }), + z.object({ + fieldOne: z.never(), + fieldTwo: z.lazy(() => InputNestedTwoSchema()) + }) + ]) + } + + export function InputNestedOneSchema(): z.ZodObject> { + return z.object({ + field: z.string().min(1) + }) + } + + export function InputNestedTwoSchema(): z.ZodObject> { + return z.object({ + field: z.string().min(1) + }) + } + " + `) + }) + + it('with scalarSchemas', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ScalarsInput @oneOf { + date: Date + email: Email + str: String + } + scalar Date + scalar Email + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + scalarSchemas: { + Date: 'z.date()', + Email: 'z.email()', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ScalarsInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + date: z.date(), + email: z.never(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.email(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.never(), + str: z.string() + }) + ]) + } + " + `) + }); + + it('with defaultScalarTypeSchema', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ScalarsInput @oneOf { + date: Date + email: Email + str: String + } + scalar Date + scalar Email + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + scalarSchemas: { + Email: 'z.email()', + }, + defaultScalarTypeSchema: 'z.string()', + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ScalarsInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + date: z.string(), + email: z.never(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.email(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.never(), + str: z.string() + }) + ]) + } + " + `) + }); + + it('properly generates custom directive values', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput @oneOf { + name: String @constraint(startsWith: "Sir") + age: Int @constraint(min: 0, max: 100) + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + directive @constraint(startsWith: String, min: Int, max: Int) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + directives: { + constraint: { + min: 'min', + max: 'max', + startsWith: ['regex', '/^$1/'], + }, + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function UserCreateInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + name: z.string().regex(/^Sir/), + age: z.never() + }), + z.object({ + name: z.never(), + age: z.number().min(0).max(100) + }) + ]) + } + " + `) + }); + + it('properly generates custom directive values for arrays', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput @oneOf { + profile: [String] @constraint(minLength: 1, maxLength: 5000) + prefs: [String] @constraint(minLength: 1, maxLength: 2000) + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + directive @constraint(minLength: Int!, maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + directives: { + constraint: { + minLength: ['min', '$1', 'Please input more than $1'], + maxLength: ['max', '$1', 'Please input less than $1'], + }, + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function UserCreateInputSchema(): z.ZodUnion { + return z.union([ + z.object({ + profile: z.array(z.string().nullable()).min(1, "Please input more than 1").max(5000, "Please input less than 5000"), + prefs: z.never() + }), + z.object({ + profile: z.never(), + prefs: z.array(z.string().nullable()).min(1, "Please input more than 1").max(2000, "Please input less than 2000") + }) + ]) + } + " + `) + }); + + it('exports as const instead of func', async () => { + const schema = buildSchema(/* GraphQL */ ` + input Say @oneOf { + phrase: String + word: String + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zod', + validationSchemaExportType: 'const', + }, + {}, + ); + + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export const SaySchema = z.union([ + z.object({ + phrase: z.string(), + word: z.never() + }), + z.object({ + phrase: z.never(), + word: z.string() + }) + ]); + " + `) + }); + }); }); diff --git a/tests/zodv4.spec.ts b/tests/zodv4.spec.ts index 9b784890..ad5612bd 100644 --- a/tests/zodv4.spec.ts +++ b/tests/zodv4.spec.ts @@ -5,9 +5,17 @@ import { plugin } from '../src/index'; const initialEmitValue = dedent(` type Properties = Required<{ - [K in keyof T]: z.ZodType; + [K in keyof T]: z.ZodType; }>; + type OneOf = { + [K in keyof Required]: Required<{ + [V in keyof Pick, K>]: z.ZodType, K>[V], unknown, any> + } & { + [P in Exclude]: z.ZodNever + }> + }[keyof T]; + type definedNonNullAny = {}; export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; @@ -15,7 +23,7 @@ const initialEmitValue = dedent(` export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - `) +`) function removedInitialEmitValue(content: string) { return content.replace(initialEmitValue, ''); @@ -44,16 +52,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function PrimitiveInputSchema(): z.ZodObject> { return z.object({ a: z.string(), @@ -85,16 +83,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function PrimitiveInputSchema(): z.ZodObject> { return z.object({ a: z.string().nullish(), @@ -124,23 +112,50 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; + export function ArrayInputSchema(): z.ZodObject> { + return z.object({ + a: z.array(z.string().nullable()).nullish(), + b: z.array(z.string()).nullish(), + c: z.array(z.string()), + d: z.array(z.array(z.string().nullable()).nullish()).nullish(), + e: z.array(z.array(z.string().nullable())).nullish(), + f: z.array(z.array(z.string().nullable())) + }) + } + " + `) + }) - type definedNonNullAny = {}; + it('array w/ scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ArrayInput { + a: [Count] + b: [Text!] + c: [Count!]! + d: [[Text]] + e: [[Count]!] + f: [[Text]!]! + } - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; + scalar Count + scalar Text + `); - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); + const scalars = { + Text: 'string', + Count: 'number', + } + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " export function ArrayInputSchema(): z.ZodObject> { return z.object({ - a: z.array(z.string().nullable()).nullish(), + a: z.array(z.number().nullable()).nullish(), b: z.array(z.string()).nullish(), - c: z.array(z.string()), + c: z.array(z.number()), d: z.array(z.array(z.string().nullable()).nullish()).nullish(), - e: z.array(z.array(z.string().nullable())).nullish(), + e: z.array(z.array(z.number().nullable())).nullish(), f: z.array(z.array(z.string().nullable())) }) } @@ -164,16 +179,6 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function AInputSchema(): z.ZodObject> { return z.object({ b: z.lazy(() => BInputSchema()) @@ -211,16 +216,6 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars, importFrom: './types', schemaNamespacedImportName: 't' }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function AInputSchema(): z.ZodObject> { return z.object({ b: z.lazy(() => BInputSchema()) @@ -253,16 +248,6 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function NestedInputSchema(): z.ZodObject> { return z.object({ child: z.lazy(() => NestedInputSchema().nullish()), @@ -287,16 +272,6 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const PageTypeSchema = z.enum(PageType); export function PageInputSchema(): z.ZodObject> { @@ -322,16 +297,6 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars, importFrom: './', schemaNamespacedImportName: 't' }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const PageTypeSchema = z.enum(t.PageType); export function PageInputSchema(): z.ZodObject> { @@ -361,16 +326,6 @@ describe('zodv4', () => { const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const HttpMethodSchema = z.enum(HttpMethod); export function HttpInputSchema(): z.ZodObject> { @@ -407,16 +362,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SaySchema(): z.ZodObject> { return z.object({ phrase: z.string(), @@ -450,16 +395,6 @@ describe('zodv4', () => { `); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SaySchema(): z.ZodObject> { return z.object({ phrase: z.string() @@ -493,16 +428,6 @@ describe('zodv4', () => { `); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SaySchema(): z.ZodObject> { return z.object({ phrase: z.string() @@ -536,16 +461,6 @@ describe('zodv4', () => { `); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SaySchema(): z.ZodObject> { return z.object({ phrase: z.string() @@ -573,16 +488,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const PageTypeSchema = z.enum(['PUBLIC', 'BASIC_AUTH']); " `) @@ -608,16 +513,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const PageTypeSchema = z.enum(['PUBLIC', 'BASIC_AUTH']); " `) @@ -647,16 +542,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function PrimitiveInputSchema(): z.ZodObject> { return z.object({ a: z.string().min(1), @@ -694,16 +579,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function InputOneSchema(): z.ZodObject> { return z.object({ field: z.lazy(() => InputNestedSchema()) @@ -736,27 +611,17 @@ describe('zodv4', () => { schema: 'zodv4', scalarSchemas: { Date: 'z.date()', - Email: 'z.string().email()', + Email: 'z.email()', }, }, {}, ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function ScalarsInputSchema(): z.ZodObject> { return z.object({ date: z.date(), - email: z.string().email().nullish(), + email: z.email().nullish(), str: z.string() }) } @@ -780,7 +645,7 @@ describe('zodv4', () => { { schema: 'zodv4', scalarSchemas: { - Email: 'z.string().email()', + Email: 'z.email()', }, defaultScalarTypeSchema: 'z.string()', }, @@ -788,20 +653,10 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function ScalarsInputSchema(): z.ZodObject> { return z.object({ date: z.string(), - email: z.string().email().nullish(), + email: z.email().nullish(), str: z.string() }) } @@ -833,16 +688,6 @@ describe('zodv4', () => { `); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function ISaySchema(): z.ZodObject> { return z.object({ phrase: z.string() @@ -876,16 +721,6 @@ describe('zodv4', () => { `); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SayISchema(): z.ZodObject> { return z.object({ phrase: z.string() @@ -1033,16 +868,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const PageTypeSchema = z.enum(PageType); export function PageInputSchema(): z.ZodObject> { @@ -1084,16 +909,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ profile: z.string().min(1, "Please input more than 1").max(5000, "Please input less than 5000").nullish() @@ -1127,16 +942,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ profile: z.string().min(1, "Please input more than 1").max(5000, "Please input less than 5000") @@ -1170,16 +975,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ profile: z.array(z.string().nullable()).min(1, "Please input more than 1").max(5000, "Please input less than 5000").nullish() @@ -1216,16 +1011,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ profile: z.string().max(5000, "Please input less than 5000").min(1), @@ -1260,16 +1045,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ profile: z.string().max(5000, "Please input less than 5000"), @@ -1323,16 +1098,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function BookSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Book').optional(), @@ -1397,7 +1162,7 @@ describe('zodv4', () => { withObjectType: true, scalarSchemas: { Date: 'z.date()', - Email: 'z.string().email()', + Email: 'z.email()', }, scalars: { ID: { @@ -1410,21 +1175,11 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ name: z.string(), date: z.date(), - email: z.string().email() + email: z.email() }) } @@ -1441,7 +1196,7 @@ describe('zodv4', () => { id: z.string(), name: z.string().nullish(), age: z.number().nullish(), - email: z.string().email().nullish(), + email: z.email().nullish(), isMember: z.boolean().nullish(), createdAt: z.date() }) @@ -1476,16 +1231,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SquareSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Square').optional(), @@ -1532,16 +1277,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SquareSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Square').optional(), @@ -1590,16 +1325,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function SquareSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Square').optional(), @@ -1648,16 +1373,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function CircleSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Circle').optional(), @@ -1699,16 +1414,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const PageTypeSchema = z.enum(PageType); export const MethodTypeSchema = z.enum(MethodType); @@ -1748,16 +1453,6 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const CircleSchema: z.ZodObject> = z.object({ __typename: z.literal('Circle').optional(), radius: z.number().nullish() @@ -1799,16 +1494,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function MyTypeSchema(): z.ZodObject> { return z.object({ __typename: z.literal('MyType').optional(), @@ -1866,16 +1551,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function BookSchema(): z.ZodObject> { return z.object({ title: z.string().nullish() @@ -1912,16 +1587,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function BookSchema(): z.ZodObject> { return z.object({ author: z.lazy(() => AuthorSchema().nullish()), @@ -1974,16 +1639,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function BookSchema(): z.ZodObject> { return z.object({ title: z.string(), @@ -2047,16 +1702,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export function UserCreateInputSchema(): z.ZodObject> { return z.object({ name: z.string().regex(/^Sir/), @@ -2084,16 +1729,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const SaySchema: z.ZodObject> = z.object({ phrase: z.string() }); @@ -2136,7 +1771,7 @@ describe('zodv4', () => { withObjectType: true, scalarSchemas: { Date: 'z.date()', - Email: 'z.string().email()', + Email: 'z.email()', }, validationSchemaExportType: 'const', }, @@ -2145,22 +1780,12 @@ describe('zodv4', () => { expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const UserSchema: z.ZodObject> = z.object({ __typename: z.literal('User').optional(), id: z.string(), name: z.string().nullish(), age: z.number().nullish(), - email: z.string().email().nullish(), + email: z.email().nullish(), isMember: z.boolean().nullish(), createdAt: z.date() }); @@ -2168,7 +1793,7 @@ describe('zodv4', () => { export const UserCreateInputSchema: z.ZodObject> = z.object({ name: z.string(), date: z.date(), - email: z.string().email() + email: z.email() }); " `) @@ -2207,16 +1832,6 @@ describe('zodv4', () => { ); expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` " - type Properties = Required<{ - [K in keyof T]: z.ZodType; - }>; - - type definedNonNullAny = {}; - - export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null; - - export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v)); - export const TestSchema = z.enum(Test); export function QueryInputSchema(): z.ZodObject> { @@ -2227,4 +1842,815 @@ describe('zodv4', () => { " `) }); + + describe('with oneOf directive', () => { + it('primitive defined', async () => { + const schema = buildSchema(/* GraphQL */ ` + input PrimitiveInput @oneOf { + a: ID + b: String + c: Boolean + d: Int + e: Float + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = { + ID: 'string', + } + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(result.prepend).toMatchInlineSnapshot(` + [ + "import * as z from 'zod'", + ] + `); + + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function PrimitiveInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + a: z.string(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.string(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.boolean(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.number(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.number() + }) + ]) + } + " + `); + }); + + it('array input', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ArrayInput @oneOf { + a: [String] + b: [String!] + c: [[String]] + d: [[String]!] + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ArrayInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + a: z.array(z.string().nullable()), + b: z.never(), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.array(z.string()), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.array(z.array(z.string().nullable()).nullish()), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.array(z.array(z.string().nullable())) + }) + ]) + } + " + `) + }) + + it('array input w/ scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ArrayInput @oneOf { + a: [Count] + b: [Text!] + c: [[Text]] + d: [[Count]!] + } + + scalar Count + scalar Text + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + + const scalars = { + Text: 'string', + Count: 'number', + } + + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ArrayInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + a: z.array(z.number().nullable()), + b: z.never(), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.array(z.string()), + c: z.never(), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.array(z.array(z.string().nullable()).nullish()), + d: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.array(z.array(z.number().nullable())) + }) + ]) + } + " + `) + }) + + it('ref input object', async () => { + const schema = buildSchema(/* GraphQL */ ` + input AInput @oneOf { + b: BInput + c: CInput + } + input BInput @oneOf { + c: CInput + d: DInput + } + input CInput @oneOf { + d: DInput + e: EInput + } + input DInput @oneOf { + e: EInput + a: AInput + } + input EInput @oneOf { + a: AInput + b: BInput + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function AInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + b: z.lazy(() => BInputSchema()), + c: z.never() + }), + z.object({ + b: z.never(), + c: z.lazy(() => CInputSchema()) + }) + ]) + } + + export function BInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + c: z.lazy(() => CInputSchema()), + d: z.never() + }), + z.object({ + c: z.never(), + d: z.lazy(() => DInputSchema()) + }) + ]) + } + + export function CInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + d: z.lazy(() => DInputSchema()), + e: z.never() + }), + z.object({ + d: z.never(), + e: z.lazy(() => EInputSchema()) + }) + ]) + } + + export function DInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + e: z.lazy(() => EInputSchema()), + a: z.never() + }), + z.object({ + e: z.never(), + a: z.lazy(() => AInputSchema()) + }) + ]) + } + + export function EInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + a: z.lazy(() => AInputSchema()), + b: z.never() + }), + z.object({ + a: z.never(), + b: z.lazy(() => BInputSchema()) + }) + ]) + } + " + `) + }) + + it('nested input object', async () => { + const schema = buildSchema(/* GraphQL */ ` + input NestedInput @oneOf { + child: NestedInput + childrens: [NestedInput] + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function NestedInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + child: z.lazy(() => NestedInputSchema()), + childrens: z.never() + }), + z.object({ + child: z.never(), + childrens: z.array(z.lazy(() => NestedInputSchema().nullable())) + }) + ]) + } + " + `) + }) + + it('enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + enum PageType { + PUBLIC + BASIC_AUTH + } + enum AuthMethod { + OAUTH + JWT + API_KEY + } + input PageInput @oneOf { + pageType: PageType + authMethod: AuthMethod + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const scalars = undefined + const result = await plugin(schema, [], { schema: 'zodv4', scalars }, {}); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export const PageTypeSchema = z.enum(PageType); + + export const AuthMethodSchema = z.enum(AuthMethod); + + export function PageInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + pageType: PageTypeSchema, + authMethod: z.never() + }), + z.object({ + pageType: z.never(), + authMethod: AuthMethodSchema + }) + ]) + } + " + `) + }) + + it('with scalars', async () => { + const schema = buildSchema(/* GraphQL */ ` + input Say @oneOf { + phrase: Text + times: Count + word: Word + } + + scalar Count + scalar Text + scalar Word # unknown scalar, should be any + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + scalars: { + Text: 'string', + Count: 'number', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function SaySchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + phrase: z.string(), + times: z.never(), + word: z.never() + }), + z.object({ + phrase: z.never(), + times: z.number(), + word: z.never() + }), + z.object({ + phrase: z.never(), + times: z.never(), + word: definedNonNullAnySchema + }) + ]) + } + " + `) + }); + + it('with notAllowEmptyString', async () => { + const schema = buildSchema(/* GraphQL */ ` + input PrimitiveInput @oneOf { + a: ID + b: String + c: Boolean + d: Int + e: Float + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function PrimitiveInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + a: z.string().min(1), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.string().min(1), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.boolean(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.number(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.number() + }) + ]) + } + " + `) + }); + + it('with notAllowEmptyString and custom directive', async () => { + const schema = buildSchema(/* GraphQL */ ` + input PrimitiveInput @oneOf { + a: ID + b: String @constraint(maxLength: 5000) + c: Boolean + d: Int + e: Float + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + directive @constraint(maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + notAllowEmptyString: true, + directives: { + constraint: { + maxLength: ['max', '$1', 'Please input less than $1'], + }, + }, + scalars: { + ID: 'string', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function PrimitiveInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + a: z.string().min(1), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.string().max(5000, "Please input less than 5000").min(1), + c: z.never(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.boolean(), + d: z.never(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.number(), + e: z.never() + }), + z.object({ + a: z.never(), + b: z.never(), + c: z.never(), + d: z.never(), + e: z.number() + }) + ]) + } + " + `) + }); + + it('with notAllowEmptyString and nested input', async () => { + const schema = buildSchema(/* GraphQL */ ` + input InputOne @oneOf { + fieldOne: InputNestedOne + fieldTwo: InputNestedTwo + } + + input InputNestedOne { + field: String! + } + + input InputNestedTwo { + field: String! + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + notAllowEmptyString: true, + scalars: { + ID: 'string', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function InputOneSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + fieldOne: z.lazy(() => InputNestedOneSchema()), + fieldTwo: z.never() + }), + z.object({ + fieldOne: z.never(), + fieldTwo: z.lazy(() => InputNestedTwoSchema()) + }) + ]) + } + + export function InputNestedOneSchema(): z.ZodObject> { + return z.object({ + field: z.string().min(1) + }) + } + + export function InputNestedTwoSchema(): z.ZodObject> { + return z.object({ + field: z.string().min(1) + }) + } + " + `) + }) + + it('with scalarSchemas', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ScalarsInput @oneOf { + date: Date + email: Email + str: String + } + scalar Date + scalar Email + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + scalarSchemas: { + Date: 'z.date()', + Email: 'z.email()', + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ScalarsInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + date: z.date(), + email: z.never(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.email(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.never(), + str: z.string() + }) + ]) + } + " + `) + }); + + it('with defaultScalarTypeSchema', async () => { + const schema = buildSchema(/* GraphQL */ ` + input ScalarsInput @oneOf { + date: Date + email: Email + str: String + } + scalar Date + scalar Email + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + scalarSchemas: { + Email: 'z.email()', + }, + defaultScalarTypeSchema: 'z.string()', + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function ScalarsInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + date: z.string(), + email: z.never(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.email(), + str: z.never() + }), + z.object({ + date: z.never(), + email: z.never(), + str: z.string() + }) + ]) + } + " + `) + }); + + it('properly generates custom directive values', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput @oneOf { + name: String @constraint(startsWith: "Sir") + age: Int @constraint(min: 0, max: 100) + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + directive @constraint(startsWith: String, min: Int, max: Int) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + directives: { + constraint: { + min: 'min', + max: 'max', + startsWith: ['regex', '/^$1/'], + }, + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function UserCreateInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + name: z.string().regex(/^Sir/), + age: z.never() + }), + z.object({ + name: z.never(), + age: z.number().min(0).max(100) + }) + ]) + } + " + `) + }); + + it('properly generates custom directive values for arrays', async () => { + const schema = buildSchema(/* GraphQL */ ` + input UserCreateInput @oneOf { + profile: [String] @constraint(minLength: 1, maxLength: 5000) + prefs: [String] @constraint(minLength: 1, maxLength: 2000) + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + directive @constraint(minLength: Int!, maxLength: Int!) on INPUT_FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + directives: { + constraint: { + minLength: ['min', '$1', 'Please input more than $1'], + maxLength: ['max', '$1', 'Please input less than $1'], + }, + }, + }, + {}, + ); + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export function UserCreateInputSchema(): z.ZodUnion>[]> { + return z.union([ + z.object({ + profile: z.array(z.string().nullable()).min(1, "Please input more than 1").max(5000, "Please input less than 5000"), + prefs: z.never() + }), + z.object({ + profile: z.never(), + prefs: z.array(z.string().nullable()).min(1, "Please input more than 1").max(2000, "Please input less than 2000") + }) + ]) + } + " + `) + }); + + it('exports as const instead of func', async () => { + const schema = buildSchema(/* GraphQL */ ` + input Say @oneOf { + phrase: String + word: String + } + + directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION + `); + const result = await plugin( + schema, + [], + { + schema: 'zodv4', + validationSchemaExportType: 'const', + }, + {}, + ); + + expect(removedInitialEmitValue(result.content)).toMatchInlineSnapshot(` + " + export const SaySchema = z.union([ + z.object({ + phrase: z.string(), + word: z.never() + }), + z.object({ + phrase: z.never(), + word: z.string() + }) + ]); + " + `) + }); + }); });