diff --git a/DOCUMENTATION_NEXT.md b/DOCUMENTATION_NEXT.md index 1e2ed6f0b..7e320e4a0 100644 --- a/DOCUMENTATION_NEXT.md +++ b/DOCUMENTATION_NEXT.md @@ -1,7 +1,9 @@ -# GraphQL Request Documentation +# Graffle Documentation # Output +This section covers the ways you can control the return types of methods in a Graffle instance. + The standard GraphQL execution result type in the JavaScript ecosystem (from the `graphql` package) has roughly this type: ```ts @@ -111,6 +113,106 @@ assertType< >(await graffle.query.foo()) ``` +# Raw + +Raw methods allow you to work directly with GraphQL queries and data types. They have [`...OrThrow`](#orthrow) variants like other methods. They force you to use the [envelope](#envelope), however your configuration for [error channels](#errors) is still honoured. + +> Aside: These methods are approximately what `graphql-request` was before it turned into Graffle. + +## DocumentNode Document + +Use the `gql` template tag to get syntax highlighting (GraphQL editor extensions special case this template tag name) and construction of `TypedQueryDocumentNode`s from strings: + +Example ([see full runnable example](./examples/raw.ts)): + +```ts +import { gql } from 'graffle/utils' + +const document = gql` + query MyThing { + stuff { + foo + bar + } + } +` + +const result = await graffle.raw({ document }) +``` + +### Type Safety + +You can attain type safety by creating your document with type variables. In a typical real project this would be something that a tool like [GraphQL Code Generator automatically](https://the-guild.dev/graphql/codegen) does for you. + +Example ([see full runnable example](./examples/raw-typed.ts)): + +```ts +const document = gql<{ stuff: { foo: string; bar: number }, { filter: boolean } }>` + query MyThing ($filter:boolean) { + stuff (filter:$filter) { + foo + bar + } + } +` + +const result = await graffle.raw({ + document, + // Correctly typed variables now required. + variables: { + filter: true, + } +}) +``` + +## String Document + +You can skip creating document nodes if you need or wish by using `.rawString`. + +> Aside: During interface design, using an overload to combine `.raw` and `.rawString` was at first used but soon abandoned because of the poor intellisense experience TypeScript overloads currently have in editors. + +Example ([see full runnable example](./examples/rawString.ts)): + +```ts +const document = ` + query MyThing { + stuff (filter:$filter) { + foo + bar + } + } +` + +const result = await graffle.rawString({ document }) +``` + +### Type Safety + +You can attain type safety by casting your document with `TypedDocumentString`. In a typical project your tooling (like GraphQL Code Generator) would do this for you. + +Example ([see full runnable example](./examples/rawString-typed.ts)): + +```ts +import { TypedDocumentString } from 'graffle/utils' + +const document = ` + query MyThing ($filter: boolean) { + stuff (filter: $filter) { + foo + bar + } + } +` as TypedDocumentString<{ stuff: { foo: string; bar: number }, { filter: boolean } }> + +const result = await graffle.rawString({ + document, + // Correctly typed variables now required. + variables: { + filter: true, + } +}) +``` + # Schema Errors There is a GraphQL schema design pattern that advocates for encoding errors into your schema. It generally has two parts: One, objects that represent errors; Two, root fields that return unions of one success object and multiple error objects. The benefit of this approach is letting users know about error states and enabling clients to receive them in a type safe way. The general net positive is higher quality and easier to develop software. diff --git a/eslint.config.js b/eslint.config.js index 90681c42a..b003ad4d7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,7 +2,7 @@ import configPrisma from 'eslint-config-prisma' import tsEslint from 'typescript-eslint' export default tsEslint.config({ - ignores: ['**/build/**/*', 'eslint.config.js', 'vite.config.ts', '**/generated/**/*', '**/generated-clients/**/*'], + ignores: ['**/build/**/*', 'eslint.config.js', 'vite.config.ts', '**/generated/**/*', '**/$generated-clients/**/*'], extends: configPrisma, languageOptions: { parserOptions: { diff --git a/examples/generated-clients/SocialStudies/Client.ts b/examples/$generated-clients/SocialStudies/Client.ts similarity index 100% rename from examples/generated-clients/SocialStudies/Client.ts rename to examples/$generated-clients/SocialStudies/Client.ts diff --git a/examples/generated-clients/SocialStudies/Error.ts b/examples/$generated-clients/SocialStudies/Error.ts similarity index 87% rename from examples/generated-clients/SocialStudies/Error.ts rename to examples/$generated-clients/SocialStudies/Error.ts index 6a480e85c..cd9eff6c7 100644 --- a/examples/generated-clients/SocialStudies/Error.ts +++ b/examples/$generated-clients/SocialStudies/Error.ts @@ -9,6 +9,6 @@ const ErrorObjectsTypeNameSelected = Object.values(ErrorObjectsTypeNameSelectedE type ErrorObjectsTypeNameSelected = (typeof ErrorObjectsTypeNameSelected)[number] export const isError = <$Value>(value: $Value): value is Include<$Value, ErrorObjectsTypeNameSelected> => { - return typeof value === 'object' && value !== null && '__typename' in value + return typeof value === `object` && value !== null && `__typename` in value && ErrorObjectsTypeNameSelected.some(_ => _.__typename === value.__typename) } diff --git a/examples/generated-clients/SocialStudies/Global.ts b/examples/$generated-clients/SocialStudies/Global.ts similarity index 100% rename from examples/generated-clients/SocialStudies/Global.ts rename to examples/$generated-clients/SocialStudies/Global.ts diff --git a/examples/generated-clients/SocialStudies/Index.ts b/examples/$generated-clients/SocialStudies/Index.ts similarity index 100% rename from examples/generated-clients/SocialStudies/Index.ts rename to examples/$generated-clients/SocialStudies/Index.ts diff --git a/examples/generated-clients/SocialStudies/Scalar.ts b/examples/$generated-clients/SocialStudies/Scalar.ts similarity index 100% rename from examples/generated-clients/SocialStudies/Scalar.ts rename to examples/$generated-clients/SocialStudies/Scalar.ts diff --git a/examples/generated-clients/SocialStudies/SchemaBuildtime.ts b/examples/$generated-clients/SocialStudies/SchemaBuildtime.ts similarity index 100% rename from examples/generated-clients/SocialStudies/SchemaBuildtime.ts rename to examples/$generated-clients/SocialStudies/SchemaBuildtime.ts diff --git a/examples/generated-clients/SocialStudies/SchemaRuntime.ts b/examples/$generated-clients/SocialStudies/SchemaRuntime.ts similarity index 100% rename from examples/generated-clients/SocialStudies/SchemaRuntime.ts rename to examples/$generated-clients/SocialStudies/SchemaRuntime.ts diff --git a/examples/generated-clients/SocialStudies/Select.ts b/examples/$generated-clients/SocialStudies/Select.ts similarity index 97% rename from examples/generated-clients/SocialStudies/Select.ts rename to examples/$generated-clients/SocialStudies/Select.ts index 0d1a811f7..67710bfe4 100644 --- a/examples/generated-clients/SocialStudies/Select.ts +++ b/examples/$generated-clients/SocialStudies/Select.ts @@ -5,7 +5,7 @@ import type { Index } from './Index.js' // ------- import { createSelect } from '../../../src/entrypoints/alpha/client.js' -export const Select = createSelect('default') +export const Select = createSelect(`default`) // Buildtime // --------- diff --git a/examples/generated-clients/SocialStudies/_.ts b/examples/$generated-clients/SocialStudies/_.ts similarity index 100% rename from examples/generated-clients/SocialStudies/_.ts rename to examples/$generated-clients/SocialStudies/_.ts diff --git a/examples/generated-clients/SocialStudies/__.ts b/examples/$generated-clients/SocialStudies/__.ts similarity index 100% rename from examples/generated-clients/SocialStudies/__.ts rename to examples/$generated-clients/SocialStudies/__.ts diff --git a/examples/$helpers.ts b/examples/$helpers.ts new file mode 100644 index 000000000..b6b8407f9 --- /dev/null +++ b/examples/$helpers.ts @@ -0,0 +1,3 @@ +export const publicGraphQLSchemaEndpoints = { + SocialStudies: `https://countries.trevorblades.com/graphql`, +} diff --git a/examples/arguments.ts b/examples/arguments.ts new file mode 100644 index 000000000..63bdbf127 --- /dev/null +++ b/examples/arguments.ts @@ -0,0 +1,12 @@ +/* eslint-disable */ +import { SocialStudies } from './$generated-clients/SocialStudies/__.js' + +const socialStudies = SocialStudies.create() + +const countries = await socialStudies.query.countries({ + $: { filter: { name: { in: ['Canada', 'Germany', 'Japan'] } } }, + name: true, + continent: { name: true }, +}) + +console.log(countries) diff --git a/examples/config-fetch.ts b/examples/config-fetch.ts index 8a4a7f3ab..d0259a685 100644 --- a/examples/config-fetch.ts +++ b/examples/config-fetch.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -import { SocialStudies } from './generated-clients/SocialStudies/__.js' +import { SocialStudies } from './$generated-clients/SocialStudies/__.js' const socialStudies = SocialStudies.create() .use({ @@ -17,6 +17,8 @@ const socialStudies = SocialStudies.create() // todo $scalars does not work // todo intelisense for $ doesn't work -const countries = await socialStudies.query.countries({ name: true }) +const countries = await socialStudies.query.countries({ + name: true, +}) console.log(countries) diff --git a/examples/config-http-headers.ts b/examples/config-http-headers.ts index 7c7fd4a67..28f0cb206 100644 --- a/examples/config-http-headers.ts +++ b/examples/config-http-headers.ts @@ -1,4 +1,4 @@ -import { SocialStudies } from './generated-clients/SocialStudies/__.js' +import { SocialStudies } from './$generated-clients/SocialStudies/__.js' const socialStudies = SocialStudies.create({ headers: { diff --git a/examples/legacy/typescript-typed-document-node.ts b/examples/legacy/typescript-typed-document-node.ts index f936d3fbc..b92d598f4 100644 --- a/examples/legacy/typescript-typed-document-node.ts +++ b/examples/legacy/typescript-typed-document-node.ts @@ -23,7 +23,7 @@ import { gql, GraphQLClient, request } from '../../src/entrypoints/main.js' const client = new GraphQLClient(endpoint) - const query: TypedDocumentNode<{ greetings: string }, Record> = parse(gql` + const query: TypedDocumentNode<{ greetings: string }> = parse(gql` query greetings { greetings } diff --git a/examples/raw-typed.ts b/examples/raw-typed.ts new file mode 100644 index 000000000..d2636fa23 --- /dev/null +++ b/examples/raw-typed.ts @@ -0,0 +1,62 @@ +import type { TypedQueryDocumentNode } from 'graphql' +import { gql, Graffle } from '../src/entrypoints/alpha/main.js' +import { publicGraphQLSchemaEndpoints } from './$helpers.js' + +const graffle = Graffle.create({ + schema: publicGraphQLSchemaEndpoints.SocialStudies, +}) + +/*************************************** Variation 1 *************************************** + * - + * - + * - + * You can pass type variables to the `gql` template tag. + * - + */ + +{ + const document = gql<{ countries: { name: string; continent: { name: string } }[] }, { filter: string[] }>` + query countries ($filter: [String!]) { + countries (filter: { name: { in: $filter } }) { + name + continent { + name + } + } + } + ` + + const result = await graffle.raw({ document, variables: { filter: [`Canada`, `Germany`, `Japan`] } }) + + console.log(result.data?.countries) +} + +/*************************************** Variation 2 *************************************** + * - + * - + * - + * You can also cast the type if you have a reference to a pre constructed type. + * - + */ + +{ + type Document = TypedQueryDocumentNode< + { countries: { name: string; continent: { name: string } }[] }, + { filter: string[] } + > + + const document: Document = gql` + query countries ($filter: [String!]) { + countries (filter: { name: { in: $filter } }) { + name + continent { + name + } + } + } + ` + + const result = await graffle.raw({ document, variables: { filter: [`Canada`, `Germany`, `Japan`] } }) + + console.log(result.data?.countries) +} diff --git a/examples/raw.ts b/examples/raw.ts index 4f6a0e9de..b8a096b66 100644 --- a/examples/raw.ts +++ b/examples/raw.ts @@ -1,21 +1,22 @@ import { gql, Graffle } from '../src/entrypoints/alpha/main.js' +import { publicGraphQLSchemaEndpoints } from './$helpers.js' -const graffle = Graffle.create({ schema: `https://countries.trevorblades.com/graphql` }) +const graffle = Graffle.create({ + schema: publicGraphQLSchemaEndpoints.SocialStudies, +}) -// todo typed document node -// interface Data { -// countries: { name }[] -// } -// const { data } = await request( +const result = await graffle.raw({ + document: gql` + query countries ($filter: [String!]) { + countries (filter: { name: { in: $filter } }) { + name + continent { + name + } + } + } + `, + variables: { filter: [`Canada`, `Germany`, `Japan`] }, +}) -const { data } = await graffle.rawOrThrow( - gql` - { - countries { - name - } - } - `, -) - -console.log(data) +console.log(result.data) diff --git a/examples/rawString-typed.ts b/examples/rawString-typed.ts new file mode 100644 index 000000000..9281b22fd --- /dev/null +++ b/examples/rawString-typed.ts @@ -0,0 +1,37 @@ +import { Graffle } from '../src/entrypoints/alpha/main.js' +// todo from '../src/entrypoints/alpha/utils.js' +import type { TypedDocumentString } from '../src/layers/0_functions/types.js' +import { publicGraphQLSchemaEndpoints } from './$helpers.js' + +const graffle = Graffle.create({ + schema: publicGraphQLSchemaEndpoints.SocialStudies, +}) + +/** + * @remarks Typically this type would come from your code generation tool. + * + * @see https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#documentmode + * @see https://github.com/jasonkuhrt/graphql-request/issues/997 + */ +type Document = TypedDocumentString< + { countries: { name: string; continent: { name: string } }[] }, + { filter: string[] } +> + +const document: Document = /* gql */ ` + query countries ($filter: [String!]) { + countries (filter: { name: { in: $filter } }) { + name + continent { + name + } + } + } +` + +const result = await graffle.rawString({ + document, + variables: { filter: [`Canada`, `Germany`, `Japan`] }, +}) + +console.log(result.data?.countries) diff --git a/examples/rawString.ts b/examples/rawString.ts new file mode 100644 index 000000000..772fc9867 --- /dev/null +++ b/examples/rawString.ts @@ -0,0 +1,20 @@ +import { Graffle } from '../src/entrypoints/alpha/main.js' +import { publicGraphQLSchemaEndpoints } from './$helpers.js' + +const graffle = Graffle.create({ + schema: publicGraphQLSchemaEndpoints.SocialStudies, +}) + +const document = /* gql */ ` + { + countries { + name + } + } +` + +const result = await graffle.rawString({ + document, +}) + +console.log(result.data) diff --git a/package.json b/package.json index c9ba5e845..be125e6ca 100644 --- a/package.json +++ b/package.json @@ -113,8 +113,8 @@ "@types/express": "^4.17.21", "@types/json-bigint": "^1.0.4", "@types/node": "^22.1.0", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.1", + "@typescript-eslint/parser": "^8.0.1", "doctoc": "^2.2.1", "dripip": "^0.10.0", "es-toolkit": "^1.13.1", @@ -131,14 +131,14 @@ "graphql-scalars": "^1.23.0", "graphql-tag": "^2.12.6", "graphql-upload-minimal": "^1.6.1", - "graphql-yoga": "^5.6.2", + "graphql-yoga": "^5.6.3", "jsdom": "^24.1.1", "json-bigint": "^1.0.0", "publint": "^0.2.9", "tsx": "^4.16.5", "type-fest": "^4.23.0", "typescript": "^5.5.4", - "typescript-eslint": "^8.0.0", + "typescript-eslint": "^8.0.1", "vitest": "^2.0.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ede47aa7a..351b9bfbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,11 +58,11 @@ importers: specifier: ^22.1.0 version: 22.1.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + specifier: ^8.0.1 + version: 8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) '@typescript-eslint/parser': - specifier: ^8.0.0 - version: 8.0.0(eslint@9.8.0)(typescript@5.5.4) + specifier: ^8.0.1 + version: 8.0.1(eslint@9.8.0)(typescript@5.5.4) doctoc: specifier: ^2.2.1 version: 2.2.1 @@ -77,7 +77,7 @@ importers: version: 9.8.0 eslint-config-prisma: specifier: ^0.6.0 - version: 0.6.0(@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-deprecation@3.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-only-warn@1.1.0)(eslint-plugin-prefer-arrow@1.2.3(eslint@9.8.0))(eslint-plugin-tsdoc@0.3.0)(eslint@9.8.0)(typescript@5.5.4) + version: 0.6.0(@typescript-eslint/eslint-plugin@8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-deprecation@3.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-only-warn@1.1.0)(eslint-plugin-prefer-arrow@1.2.3(eslint@9.8.0))(eslint-plugin-tsdoc@0.3.0)(eslint@9.8.0)(typescript@5.5.4) eslint-plugin-deprecation: specifier: ^3.0.0 version: 3.0.0(eslint@9.8.0)(typescript@5.5.4) @@ -112,7 +112,7 @@ importers: specifier: ^1.6.1 version: 1.6.1(graphql@16.9.0) graphql-yoga: - specifier: ^5.6.2 + specifier: ^5.6.3 version: 5.6.3(graphql@16.9.0) jsdom: specifier: ^24.1.1 @@ -133,8 +133,8 @@ importers: specifier: ^5.5.4 version: 5.5.4 typescript-eslint: - specifier: ^8.0.0 - version: 8.0.0(eslint@9.8.0)(typescript@5.5.4) + specifier: ^8.0.1 + version: 8.0.1(eslint@9.8.0)(typescript@5.5.4) vitest: specifier: ^2.0.5 version: 2.0.5(@types/node@22.1.0)(happy-dom@14.12.3)(jsdom@24.1.1) @@ -379,8 +379,8 @@ packages: resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@graphql-tools/executor@1.3.0': - resolution: {integrity: sha512-e+rmEf/2EO4hDnbkO8mTS2FI+jGUNmYkSDKw5TgPVlO8VOKS+TXmJBK6E9v4Gc/39yVkZsffYfW/R8obJrA0mg==} + '@graphql-tools/executor@1.3.1': + resolution: {integrity: sha512-tgJDdGf9SCAm64ofEMZdv925u6/J+eTmv36TGNLxgP2DpCJsZ6gnJ4A+0D28EazDXqJIvMiPd+3d+o3cCRCAnQ==} engines: {node: '>=16.0.0'} peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 @@ -397,8 +397,8 @@ packages: peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - '@graphql-tools/utils@10.3.2': - resolution: {integrity: sha512-iaqOHS4f90KNADBHqVsRBjKpM6iSvsUg1q5GhWMK03loYLaDzftrEwcsl0OkSSnRhJvAsT7q4q3r3YzRoV0v1g==} + '@graphql-tools/utils@10.3.4': + resolution: {integrity: sha512-5xUXVsp1Yh1ttYHYDLJscoyv2LcW1BjpbcooIAkGUzINftD/qRBw9zvVmALx+oQWr/9MSIYghHHZzbYHsVO0/A==} engines: {node: '>=16.0.0'} peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 @@ -774,8 +774,8 @@ packages: typescript: optional: true - '@typescript-eslint/eslint-plugin@8.0.0': - resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + '@typescript-eslint/eslint-plugin@8.0.1': + resolution: {integrity: sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -811,8 +811,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.0.0': - resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + '@typescript-eslint/parser@8.0.1': + resolution: {integrity: sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -829,8 +829,8 @@ packages: resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@8.0.0': - resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + '@typescript-eslint/scope-manager@8.0.1': + resolution: {integrity: sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/type-utils@7.18.0': @@ -843,8 +843,8 @@ packages: typescript: optional: true - '@typescript-eslint/type-utils@8.0.0': - resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + '@typescript-eslint/type-utils@8.0.1': + resolution: {integrity: sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -860,8 +860,8 @@ packages: resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@8.0.0': - resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + '@typescript-eslint/types@8.0.1': + resolution: {integrity: sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@5.0.0': @@ -882,8 +882,8 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.0.0': - resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + '@typescript-eslint/typescript-estree@8.0.1': + resolution: {integrity: sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -897,8 +897,8 @@ packages: peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/utils@8.0.0': - resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + '@typescript-eslint/utils@8.0.1': + resolution: {integrity: sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -911,8 +911,8 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@8.0.0': - resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + '@typescript-eslint/visitor-keys@8.0.1': + resolution: {integrity: sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/expect@2.0.5': @@ -1041,8 +1041,8 @@ packages: aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - aws4@1.13.0: - resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==} + aws4@1.13.1: + resolution: {integrity: sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==} bail@1.0.5: resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==} @@ -1169,8 +1169,8 @@ packages: engines: {node: '>=0.8'} hasBin: true - cross-inspect@1.0.0: - resolution: {integrity: sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==} + cross-inspect@1.0.1: + resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} engines: {node: '>=16.0.0'} cross-spawn@7.0.3: @@ -2452,8 +2452,8 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - postcss@8.4.40: - resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -2881,8 +2881,8 @@ packages: typescript: optional: true - typescript-eslint@8.0.0: - resolution: {integrity: sha512-yQWBJutWL1PmpmDddIOl9/Mi6vZjqNCjqSGBMQ4vsc2Aiodk0SnbQQWPXbSy0HNuKCuGkw1+u4aQ2mO40TdhDQ==} + typescript-eslint@8.0.1: + resolution: {integrity: sha512-V3Y+MdfhawxEjE16dWpb7/IOgeXnLwAEEkS7v8oDqNcR1oYlqWhGH/iHqHdKVdpWme1VPZ0SoywXAkCqawj2eQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2987,8 +2987,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.3.5: - resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} + vite@5.4.0: + resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2996,6 +2996,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -3008,6 +3009,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -3313,9 +3316,9 @@ snapshots: '@eslint/object-schema@2.1.4': {} - '@graphql-tools/executor@1.3.0(graphql@16.9.0)': + '@graphql-tools/executor@1.3.1(graphql@16.9.0)': dependencies: - '@graphql-tools/utils': 10.3.2(graphql@16.9.0) + '@graphql-tools/utils': 10.3.4(graphql@16.9.0) '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) '@repeaterjs/repeater': 3.0.6 graphql: 16.9.0 @@ -3324,22 +3327,22 @@ snapshots: '@graphql-tools/merge@9.0.4(graphql@16.9.0)': dependencies: - '@graphql-tools/utils': 10.3.2(graphql@16.9.0) + '@graphql-tools/utils': 10.3.4(graphql@16.9.0) graphql: 16.9.0 tslib: 2.6.3 '@graphql-tools/schema@10.0.4(graphql@16.9.0)': dependencies: '@graphql-tools/merge': 9.0.4(graphql@16.9.0) - '@graphql-tools/utils': 10.3.2(graphql@16.9.0) + '@graphql-tools/utils': 10.3.4(graphql@16.9.0) graphql: 16.9.0 tslib: 2.6.3 value-or-promise: 1.0.12 - '@graphql-tools/utils@10.3.2(graphql@16.9.0)': + '@graphql-tools/utils@10.3.4(graphql@16.9.0)': dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) - cross-inspect: 1.0.0 + cross-inspect: 1.0.1 dset: 3.1.3 graphql: 16.9.0 tslib: 2.6.3 @@ -3820,14 +3823,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/type-utils': 8.0.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/parser': 8.0.1(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.0.1 + '@typescript-eslint/type-utils': 8.0.1(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/utils': 8.0.1(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.0.1 eslint: 9.8.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -3876,12 +3879,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/scope-manager': 8.0.1 + '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.0.1 debug: 4.3.6 eslint: 9.8.0 optionalDependencies: @@ -3899,10 +3902,10 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/scope-manager@8.0.0': + '@typescript-eslint/scope-manager@8.0.1': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/visitor-keys': 8.0.1 '@typescript-eslint/type-utils@7.18.0(eslint@9.8.0)(typescript@5.5.4)': dependencies: @@ -3916,10 +3919,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.0.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@8.0.1(eslint@9.8.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) + '@typescript-eslint/utils': 8.0.1(eslint@9.8.0)(typescript@5.5.4) debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: @@ -3932,7 +3935,7 @@ snapshots: '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/types@8.0.0': {} + '@typescript-eslint/types@8.0.1': {} '@typescript-eslint/typescript-estree@5.0.0(typescript@4.4.3)': dependencies: @@ -3963,10 +3966,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.0.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.0.1(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/visitor-keys': 8.0.0 + '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/visitor-keys': 8.0.1 debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 @@ -3989,12 +3992,12 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@8.0.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/utils@8.0.1(eslint@9.8.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - '@typescript-eslint/scope-manager': 8.0.0 - '@typescript-eslint/types': 8.0.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.0.1 + '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) eslint: 9.8.0 transitivePeerDependencies: - supports-color @@ -4010,9 +4013,9 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.0.0': + '@typescript-eslint/visitor-keys@8.0.1': dependencies: - '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/types': 8.0.1 eslint-visitor-keys: 3.4.3 '@vitest/expect@2.0.5': @@ -4172,7 +4175,7 @@ snapshots: aws-sign2@0.7.0: {} - aws4@1.13.0: {} + aws4@1.13.1: {} bail@1.0.5: {} @@ -4283,7 +4286,7 @@ snapshots: crc-32@1.2.2: {} - cross-inspect@1.0.0: + cross-inspect@1.0.1: dependencies: tslib: 2.6.3 @@ -4571,13 +4574,13 @@ snapshots: dependencies: eslint: 9.8.0 - eslint-config-prisma@0.6.0(@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-deprecation@3.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-only-warn@1.1.0)(eslint-plugin-prefer-arrow@1.2.3(eslint@9.8.0))(eslint-plugin-tsdoc@0.3.0)(eslint@9.8.0)(typescript@5.5.4): + eslint-config-prisma@0.6.0(@typescript-eslint/eslint-plugin@8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-deprecation@3.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-only-warn@1.1.0)(eslint-plugin-prefer-arrow@1.2.3(eslint@9.8.0))(eslint-plugin-tsdoc@0.3.0)(eslint@9.8.0)(typescript@5.5.4): dependencies: '@eslint/js': 9.8.0 '@types/eslint-config-prettier': 6.11.3 '@types/eslint__js': 8.42.3 - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 8.0.1(eslint@9.8.0)(typescript@5.5.4) '@typescript-eslint/utils': 7.18.0(eslint@9.8.0)(typescript@5.5.4) eslint: 9.8.0 eslint-config-prettier: 9.1.0(eslint@9.8.0) @@ -4593,7 +4596,7 @@ snapshots: eslint-config-standard@13.0.1(eslint-plugin-import@2.23.3(@typescript-eslint/parser@5.0.0(eslint@9.8.0)(typescript@4.4.3))(eslint-import-resolver-typescript@2.5.0)(eslint@9.8.0))(eslint-plugin-node@11.1.0(eslint@9.8.0))(eslint-plugin-promise@4.2.1)(eslint-plugin-standard@4.0.0(eslint@9.8.0))(eslint@9.8.0): dependencies: eslint: 9.8.0 - eslint-plugin-import: 2.23.3(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) + eslint-plugin-import: 2.23.3(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) eslint-plugin-node: 11.1.0(eslint@9.8.0) eslint-plugin-promise: 4.2.1 eslint-plugin-standard: 4.0.0(eslint@9.8.0) @@ -4610,7 +4613,7 @@ snapshots: dependencies: debug: 4.3.6 eslint: 9.8.0 - eslint-plugin-import: 2.23.3(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) + eslint-plugin-import: 2.23.3(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.8 @@ -4618,11 +4621,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 8.0.1(eslint@9.8.0)(typescript@5.5.4) eslint: 9.8.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -4644,7 +4647,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.23.3(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0): + eslint-plugin-import@2.23.3(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0): dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.2 @@ -4652,7 +4655,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.8.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0) find-up: 2.1.0 has: 1.0.4 is-core-module: 2.15.0 @@ -4663,7 +4666,7 @@ snapshots: resolve: 1.22.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 8.0.1(eslint@9.8.0)(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4745,7 +4748,7 @@ snapshots: eslint-config-prettier: 6.0.0(eslint@9.8.0) eslint-config-standard: 13.0.1(eslint-plugin-import@2.23.3(@typescript-eslint/parser@5.0.0(eslint@9.8.0)(typescript@4.4.3))(eslint-import-resolver-typescript@2.5.0)(eslint@9.8.0))(eslint-plugin-node@11.1.0(eslint@9.8.0))(eslint-plugin-promise@4.2.1)(eslint-plugin-standard@4.0.0(eslint@9.8.0))(eslint@9.8.0) eslint-import-resolver-typescript: 2.5.0(eslint-plugin-import@2.23.3)(eslint@9.8.0) - eslint-plugin-import: 2.23.3(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) + eslint-plugin-import: 2.23.3(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0) eslint-plugin-jsdoc: 33.1.1(eslint@9.8.0) eslint-plugin-mocha: 8.1.0(eslint@9.8.0) eslint-plugin-node: 11.1.0(eslint@9.8.0) @@ -5060,9 +5063,9 @@ snapshots: graphql-yoga@5.6.3(graphql@16.9.0): dependencies: '@envelop/core': 5.0.1 - '@graphql-tools/executor': 1.3.0(graphql@16.9.0) + '@graphql-tools/executor': 1.3.1(graphql@16.9.0) '@graphql-tools/schema': 10.0.4(graphql@16.9.0) - '@graphql-tools/utils': 10.3.2(graphql@16.9.0) + '@graphql-tools/utils': 10.3.4(graphql@16.9.0) '@graphql-yoga/logger': 2.0.0 '@graphql-yoga/subscription': 5.0.1 '@whatwg-node/fetch': 0.9.19 @@ -5769,7 +5772,7 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss@8.4.40: + postcss@8.4.41: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 @@ -5867,7 +5870,7 @@ snapshots: request@2.88.2: dependencies: aws-sign2: 0.7.0 - aws4: 1.13.0 + aws4: 1.13.1 caseless: 0.12.0 combined-stream: 1.0.8 extend: 3.0.2 @@ -6257,11 +6260,11 @@ snapshots: transitivePeerDependencies: - supports-color - typescript-eslint@8.0.0(eslint@9.8.0)(typescript@5.5.4): + typescript-eslint@8.0.1(eslint@9.8.0)(typescript@5.5.4): dependencies: - '@typescript-eslint/eslint-plugin': 8.0.0(@typescript-eslint/parser@8.0.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/parser': 8.0.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/eslint-plugin': 8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/parser': 8.0.1(eslint@9.8.0)(typescript@5.5.4) + '@typescript-eslint/utils': 8.0.1(eslint@9.8.0)(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -6364,21 +6367,22 @@ snapshots: debug: 4.3.6 pathe: 1.1.2 tinyrainbow: 1.2.0 - vite: 5.3.5(@types/node@22.1.0) + vite: 5.4.0(@types/node@22.1.0) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite@5.3.5(@types/node@22.1.0): + vite@5.4.0(@types/node@22.1.0): dependencies: esbuild: 0.21.5 - postcss: 8.4.40 + postcss: 8.4.41 rollup: 4.20.0 optionalDependencies: '@types/node': 22.1.0 @@ -6402,7 +6406,7 @@ snapshots: tinybench: 2.9.0 tinypool: 1.0.0 tinyrainbow: 1.2.0 - vite: 5.3.5(@types/node@22.1.0) + vite: 5.4.0(@types/node@22.1.0) vite-node: 2.0.5(@types/node@22.1.0) why-is-node-running: 2.3.0 optionalDependencies: @@ -6413,6 +6417,7 @@ snapshots: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color diff --git a/src/cli/_helpers.ts b/src/cli/_helpers.ts index b6eaee1c8..9c176797f 100644 --- a/src/cli/_helpers.ts +++ b/src/cli/_helpers.ts @@ -1,16 +1,23 @@ -import { type ExecutionResult, getIntrospectionQuery, type IntrospectionQuery } from 'graphql' +import { getIntrospectionQuery, type IntrospectionQuery } from 'graphql' import { Graffle } from '../entrypoints/alpha/__Graffle.js' +import type { TypedDocumentString } from '../layers/0_functions/types.js' export const introspectionQuery = async (endpoint: URL): Promise => { - const fullIntrospectionQuery = getIntrospectionQuery({ + const introspectionQueryDocument = getIntrospectionQuery({ descriptions: true, schemaDescription: true, directiveIsRepeatable: true, specifiedByUrl: true, inputValueDeprecation: true, + }) as TypedDocumentString + + const result = await Graffle.create({ schema: endpoint }).rawStringOrThrow({ + document: introspectionQueryDocument, }) - const result = (await Graffle.create({ schema: endpoint }).rawOrThrow(fullIntrospectionQuery)) as ExecutionResult< - IntrospectionQuery - > // todo use http execution result - return result.data! + + if (!result.data) { + throw new Error(`No data returned for introspection query.`) + } + + return result.data } diff --git a/src/entrypoints/alpha/main.ts b/src/entrypoints/alpha/main.ts index f6fe1e835..057ff6c5e 100644 --- a/src/entrypoints/alpha/main.ts +++ b/src/entrypoints/alpha/main.ts @@ -1,4 +1,3 @@ export { execute } from '../../layers/0_functions/execute.js' -export { request } from '../../layers/0_functions/request.js' -export { gql } from '../../legacy/functions/gql.js' +export { gql } from '../../layers/0_functions/gql.js' export * from './__Graffle.js' diff --git a/src/layers/0_functions/execute.ts b/src/layers/0_functions/execute.ts index a97bc2479..b908864e2 100644 --- a/src/layers/0_functions/execute.ts +++ b/src/layers/0_functions/execute.ts @@ -1,8 +1,8 @@ import type { ExecutionResult, GraphQLSchema } from 'graphql' import { execute as graphqlExecute, graphql } from 'graphql' -import type { BaseInput } from './types.js' +import type { BaseInput_ } from './types.js' -interface Input extends BaseInput { +type Input = BaseInput_ & { schema: GraphQLSchema } diff --git a/src/layers/0_functions/gql.ts b/src/layers/0_functions/gql.ts new file mode 100644 index 000000000..a4c00fc99 --- /dev/null +++ b/src/layers/0_functions/gql.ts @@ -0,0 +1,27 @@ +import type { TypedQueryDocumentNode } from 'graphql' +import { parse } from 'graphql' + +/** + * Returns the string with any variables given interpolated and then parsed into a DocumentNode. + * + * @example + * ``` + * import { gql } from 'graphql-request' + * + * await request('https://foo.bar/graphql', gql`...`) + * ``` + * + * @remarks + * + * Several tools in the Node GraphQL ecosystem are hardcoded to specially treat any template tag named "gql". For example see this prettier issue: https://github.com/prettier/prettier/issues/4360. Using this template tag has no runtime effect beyond variable interpolation. + */ +export const gql = <$Data, $Variables>( + chunks: TemplateStringsArray, + ...variables: unknown[] +): TypedQueryDocumentNode<$Data, $Variables> => { + const string = chunks.reduce( + (acc, chunk, index) => `${acc}${chunk}${index in variables ? String(variables[index]) : ``}`, + ``, + ) + return parse(string) as any +} diff --git a/src/layers/0_functions/request.ts b/src/layers/0_functions/request.ts deleted file mode 100644 index 431d57ef7..000000000 --- a/src/layers/0_functions/request.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { ExecutionResult } from 'graphql' -import { print } from 'graphql' -import { parseExecutionResult } from '../../lib/graphqlHTTP.js' -import { CONTENT_TYPE_GQL } from '../../lib/http.js' -import type { BaseInput } from './types.js' - -export type URLInput = URL | string - -export interface NetworkRequestInput extends BaseInput { - url: URLInput - headers?: HeadersInit -} - -export type NetworkRequest = (input: NetworkRequestInput) => Promise - -/** - * @see https://graphql.github.io/graphql-over-http/draft/ - */ -export const request: NetworkRequest = async (input) => { - const documentEncoded = typeof input.document === `string` ? input.document : print(input.document) - - const body = { - query: documentEncoded, - variables: input.variables, - operationName: input.operationName, - } - - const bodyEncoded = JSON.stringify(body) - - const requestObject = new Request(input.url, { - method: `POST`, - headers: new Headers({ - 'accept': CONTENT_TYPE_GQL, - ...Object.fromEntries(new Headers(input.headers).entries()), - }), - body: bodyEncoded, - }) - - const response = await fetch(requestObject) - - if (!response.ok) { - throw new Error(`Request to GraphQL endpoint failed. (Status: ${String(response.status)})`) - } - - const json = await response.json() as object - - const result = parseExecutionResult(json) - - return result -} diff --git a/src/layers/0_functions/types.ts b/src/layers/0_functions/types.ts index abf4edcea..a198600ff 100644 --- a/src/layers/0_functions/types.ts +++ b/src/layers/0_functions/types.ts @@ -1,8 +1,59 @@ +import type { DocumentTypeDecoration } from '@graphql-typed-document-node/core' +import type { DocumentNode, TypedQueryDocumentNode } from 'graphql' +import type { HasRequiredKeys, IsEmptyObject } from 'type-fest' import type { StandardScalarVariables } from '../../lib/graphql.js' +import type { Negate } from '../../lib/prelude.js' import type { DocumentInput, OperationNameInput } from '../6_client/types.js' -export interface BaseInput { - document: DocumentInput - variables?: StandardScalarVariables +export type BaseInput_ = { + document: DocumentNode | string operationName?: OperationNameInput + variables?: StandardScalarVariables } + +// dprint-ignore +export type BaseInput<$Document extends DocumentInput = DocumentInput> = + & { + document: $Document + operationName?: OperationNameInput + } + & ( + $Document extends TypedDocumentString + ? GetVariablesInputFromString> + : string extends $Document + ? { variables?: StandardScalarVariables } + : GetVariablesInputFromDocumentNode> + ) + +// dprint-ignore +type GetVariablesInputFromString<$Document extends TypedDocumentString> = + HasVariables<$Document> extends true + ? HasRequiredKeys> extends true + ? { variables: VariablesOf<$Document> } + : { variables?: VariablesOf<$Document> } + : {} // eslint-disable-line + +// dprint-ignore +type GetVariablesInputFromDocumentNode<$Document extends TypedQueryDocumentNode> = + HasVariables<$Document> extends true + ? HasRequiredKeys> extends true + ? { variables: VariablesOf<$Document> } + : { variables?: VariablesOf<$Document> } + : {} // eslint-disable-line + +export type HasVariables<$Document extends TypedQueryDocumentNode | TypedDocumentString> = Negate< + IsEmptyObject> +> + +// dprint-ignore +type VariablesOf<$Document extends TypedQueryDocumentNode | TypedDocumentString> = + $Document extends TypedQueryDocumentNode + ? V + : $Document extends TypedDocumentString + ? V + : never + +// TODO open issue asking for core library to expose this type. +export interface TypedDocumentString, TVariables = Record> + extends String, DocumentTypeDecoration // eslint-disable-line +{} diff --git a/src/layers/3_SelectionSet/types.ts b/src/layers/3_SelectionSet/types.ts index 228e16f55..bf26ff90f 100644 --- a/src/layers/3_SelectionSet/types.ts +++ b/src/layers/3_SelectionSet/types.ts @@ -88,7 +88,7 @@ export type Field_< $type extends Schema.Output.Nullable ? Field_<$typeInner, $Field, $Index, $Options> : $type extends Schema.Output.List ? Field_<$typeInner, $Field, $Index, $Options> : $type extends Schema.__typename ? NoArgsIndicator : - $type extends Schema.Scalar.Any ? Indicator<$Field> : + $type extends Schema.Scalar.Any ? Indicator<$Field> : // eslint-disable $type extends Schema.Enum ? Indicator<$Field> : $type extends Schema.Object$2 ? Object<$type, $Index> & ($Options['hideDirectives'] extends true ? {} : FieldDirectives) & Arguments<$Field> : $type extends Schema.Union ? Union<$type, $Index> & Arguments<$Field> : diff --git a/src/layers/6_client/Settings/Config.ts b/src/layers/6_client/Settings/Config.ts index 08f9360bb..488fd5ab8 100644 --- a/src/layers/6_client/Settings/Config.ts +++ b/src/layers/6_client/Settings/Config.ts @@ -1,7 +1,7 @@ -import type { ExecutionResult as GraphQLExecutionResult } from 'graphql' +import type { GraphQLError } from 'graphql' import type { Simplify } from 'type-fest' import type { GraphQLExecutionResultError } from '../../../lib/graphql.js' -import type { ConfigManager, StringKeyof } from '../../../lib/prelude.js' +import type { ConfigManager, StringKeyof, Values } from '../../../lib/prelude.js' import type { Schema } from '../../1_Schema/__.js' import type { GlobalRegistry } from '../../2_generator/globalRegistry.js' import type { SelectionSet } from '../../3_SelectionSet/__.js' @@ -108,7 +108,7 @@ export type Config = { // dprint-ignore export type ResolveOutputReturnRootType<$Config extends Config, $Index extends Schema.Index, $Data> = - | IfConfiguredGetOutputErrorReturns<$Config> + | Simplify> | ( $Config['output']['envelope']['enabled'] extends true ? Envelope<$Config, IfConfiguredStripSchemaErrorsFromDataRootType<$Config, $Index, $Data>> @@ -123,29 +123,47 @@ export type ResolveOutputReturnRootField<$Config extends Config, $Index extends // todo: a typed execution result that allows for additional error types. // currently it is always graphql execution error however envelope configuration can put more errors into that. ? Envelope<$Config, $DataRaw extends undefined - ? IfConfiguredStripSchemaErrorsFromDataRootField<$Config, $Index, $Data> - : IfConfiguredStripSchemaErrorsFromDataRootType<$Config, $Index, $DataRaw>> + ? Simplify> + : Simplify>> : Simplify> ) -// type ObjMap = { -// [key: string]: T -// } +type ObjMap = { + [key: string]: T +} // dprint-ignore -// todo use ObjMap -export type Envelope<$Config extends Config, $Data = unknown> = - Simplify< - $Config['transport'] extends 'http' - ? EnvelopeTransportHttp<$Data> - : EnvelopeTransportMemory<$Data> - > - -export type EnvelopeTransportHttp<$Data> = GraphQLExecutionResult<$Data> & { - response: Response -} +type IsEnvelopeWithoutErrors<$Config extends Config> = + $Config['output']['envelope']['enabled'] extends true + ? Values<$Config['output']['envelope']['errors']> extends false + ? true + : false + : false -export type EnvelopeTransportMemory<$Data> = GraphQLExecutionResult<$Data> +// dprint-ignore +// todo use ObjMap for $Data +export type Envelope<$Config extends Config, $Data = unknown, $Errors extends ReadonlyArray = ReadonlyArray> = + Simplify< + & { + data?: $Data | null + extensions?: ObjMap + } + & ( + $Config['transport'] extends 'http' + ? { response: Response } + : {} // eslint-disable-line + ) + // todo remove use of errors type variable. Rely only on $Config. + & ( + $Errors extends [] + ? {} // eslint-disable-line + : IsEnvelopeWithoutErrors<$Config> extends true + ? {} // eslint-disable-line + : { + errors?: ReadonlyArray + } + ) + > type ConfigResolveOutputErrorChannel<$Config extends Config, $Channel extends OutputChannelConfig | false> = $Channel extends 'default' ? $Config['output']['defaults']['errorChannel'] diff --git a/src/layers/6_client/Settings/Input.ts b/src/layers/6_client/Settings/Input.ts index 61ac375f8..b04e09966 100644 --- a/src/layers/6_client/Settings/Input.ts +++ b/src/layers/6_client/Settings/Input.ts @@ -1,6 +1,5 @@ import type { GraphQLSchema } from 'graphql' import type { ConfigManager } from '../../../lib/prelude.js' -import type { URLInput } from '../../0_functions/request.js' import type { Schema } from '../../1_Schema/__.js' import type { GlobalRegistry } from '../../2_generator/globalRegistry.js' import type { TransportHttp, TransportMemory } from '../../5_core/types.js' @@ -8,6 +7,8 @@ import { Transport } from '../../5_core/types.js' import type { InputPrefilled } from '../prefilled.js' import { type OutputChannel, type OutputChannelConfig, outputConfigDefault } from './Config.js' +export type URLInput = URL | string + export type InputOutputEnvelopeLonghand = { /** * @defaultValue `true` @@ -44,46 +45,51 @@ export type Input<$Schema extends GlobalRegistry.SchemaList> = { // TODO use code generation to display // TODO test that schema is optional when introspection was used to generate client. +// dprint-ignore export type InputRaw<$Schema extends GlobalRegistry.SchemaList> = | ( - & ($Schema['defaultSchemaUrl'] extends null ? { - schema: URLInput - } - : { + & ( + $Schema['defaultSchemaUrl'] extends null + ? { + schema: URLInput + } + : { + /** + * @defaultValue The introspection URL used to generate this Graffle client. + */ + schema?: URLInput + } + ) + & { /** - * @defaultValue The introspection URL used to generate this Graffle client. + * Headers to send with each sent request. */ - schema?: URLInput - }) - & { - /** - * Headers to send with each sent request. - */ - headers?: HeadersInit - /** - * Configure output behavior, such as if errors should be returned or thrown. - */ - output?: OutputInput<{ schemaErrors: GlobalRegistry.HasSchemaErrors<$Schema>; transport: 'http' }> - } - ) - | ( - & ($Schema['defaultSchemaUrl'] extends null ? { - schema: GraphQLSchema - } - : { + headers?: HeadersInit /** - * TODO this TSDoc is never rendered in VSCode... - * @defaultValue The introspection URL used to generate this Graffle client. + * Configure output behavior, such as if errors should be returned or thrown. */ - schema?: GraphQLSchema - }) + output?: OutputInput<{ schemaErrors: GlobalRegistry.HasSchemaErrors<$Schema>; transport: 'http' }> + } + ) + | ( + & ( + $Schema['defaultSchemaUrl'] extends null + ? { schema: GraphQLSchema } + : { + /** + * TODO this TSDoc is never rendered in VSCode... + * @defaultValue The introspection URL used to generate this Graffle client. + */ + schema?: GraphQLSchema + } + ) & { - headers?: never - /** - * Configure output behavior, such as if errors should be returned or thrown. - */ - output?: OutputInput<{ schemaErrors: GlobalRegistry.HasSchemaErrors<$Schema>; transport: 'memory' }> - } + headers?: never + /** + * Configure output behavior, such as if errors should be returned or thrown. + */ + output?: OutputInput<{ schemaErrors: GlobalRegistry.HasSchemaErrors<$Schema>; transport: 'memory' }> + } ) export type OutputInput = diff --git a/src/layers/6_client/Settings/client.create.config.output.test-d.ts b/src/layers/6_client/Settings/client.create.config.output.test-d.ts index 29f142086..b45fd9ae8 100644 --- a/src/layers/6_client/Settings/client.create.config.output.test-d.ts +++ b/src/layers/6_client/Settings/client.create.config.output.test-d.ts @@ -1,14 +1,16 @@ /* eslint-disable */ import { type ExecutionResult } from 'graphql' +import type { ObjMap } from 'graphql/jsutils/ObjMap.js' import { describe } from 'node:test' +import type { Simplify } from 'type-fest' +import type { ConditionalSimplify } from 'type-fest/source/conditional-simplify.js' import { expectTypeOf, test } from 'vitest' import { Graffle } from '../../../../tests/_/schema/generated/__.js' import { schema } from '../../../../tests/_/schema/schema.js' import { type GraphQLExecutionResultError } from '../../../lib/graphql.js' -import { type SimplifyDeep } from '../../../lib/prelude.js' -import { type EnvelopeTransportMemory } from './Config.js' +import { type Envelope, type OutputConfigDefault } from './Config.js' -const C = Graffle.create +const G = Graffle.create const defaultGraffle = Graffle.create({ schema }) @@ -16,7 +18,7 @@ const resultFieldSelect = Graffle.Select.Query({ resultNonNull: { $: { case: 'Object1' }, __typename: true } })['resultNonNull'] describe('default is errors thrown, no envelope, no schema errors', async () => { - const graffle = C({ + const graffle = G({ schema, output: { defaults: { @@ -60,7 +62,7 @@ describe('.envelope', () => { // const fieldMethod = <$Graffle extends {query:{__typename:()=>Promise}}>(g: $Graffle) => g.query.__typename() describe('false disables it ', () => { - const g = C({ schema, output: { envelope: false } }) + const g = G({ schema, output: { envelope: false } }) test('query.', () => { expectTypeOf(g.query.__typename()).resolves.toEqualTypeOf() @@ -101,20 +103,20 @@ describe('.envelope', () => { describe('with defaults.errorChannel: "return"', () => { describe('.errors', () => { test('defaults to execution errors in envelope', () => { - const g = C({ schema, output: { defaults: { errorChannel: 'return' }, envelope: true } }) + const g = G({ schema, output: { defaults: { errorChannel: 'return' }, envelope: true } }) expectTypeOf(g.query.__typename()).resolves.toEqualTypeOf | Error>() }) - test('.execution:false restores errors to return', () => { - const g = C({ + test('.execution:false restores errors to return', async () => { + const g = G({ schema, output: { defaults: { errorChannel: 'return' }, envelope: { errors: { execution: false } } }, }) expectTypeOf(g.query.__typename()).resolves.toEqualTypeOf< - ExecutionResult<{ __typename: 'Query' }> | Error | GraphQLExecutionResultError + Omit, 'errors'> | Error | GraphQLExecutionResultError >() }) test('.other:true raises them to envelope', () => { - const g = C({ + const g = G({ schema, output: { defaults: { errorChannel: 'return' }, envelope: { errors: { other: true } } }, }) @@ -122,32 +124,42 @@ describe('.envelope', () => { }) }) }) + test('with no errors included, then there are no error fields in the envelope', async () => { + const g = G({ + schema, + // todo allow this shorthand + // output: { envelope: false }, + output: { envelope: { errors: { execution:false, other:false, schema:false } } }, + }) + const result = await g.query.__typename() + expectTypeOf().toEqualTypeOf<'data'|'extensions'> // no errors + }) }) describe('defaults.errorChannel: "return"', () => { describe('puts errors into return type', () => { - const g = C({ schema, output: { defaults: { errorChannel: 'return' } } }) + const g = G({ schema, output: { defaults: { errorChannel: 'return' } } }) test('query.', () => { expectTypeOf(g.query.__typename()).resolves.toEqualTypeOf<'Query' | Error | GraphQLExecutionResultError>() }) }) describe('with .errors', () => { test('.execution: throw', async () => { - const g = C({ + const g = G({ schema, output: { defaults: { errorChannel: 'return' }, errors: { execution: 'throw' } }, }) expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | Error>() }) test('.other: throw', async () => { - const g = C({ + const g = G({ schema, output: { defaults: { errorChannel: 'return' }, errors: { other: 'throw' } }, }) expectTypeOf(await g.query.__typename()).toEqualTypeOf<'Query' | GraphQLExecutionResultError>() }) test('.*: throw', async () => { - const g = C({ + const g = G({ schema, output: { defaults: { errorChannel: 'return' }, errors: { other: 'throw', execution: 'throw' } }, }) @@ -158,23 +170,24 @@ describe('defaults.errorChannel: "return"', () => { describe('.errors.schema', () => { describe('throw', () => { - const g = C({ schema, output: { errors: { schema: 'throw' } } }) + const g = G({ schema, output: { errors: { schema: 'throw' } } }) test('query.', () => { expectTypeOf(g.query.resultNonNull(resultFieldSelect)).resolves.toEqualTypeOf<{ __typename: 'Object1' }>() }) }) describe('return', () => { - const g = C({ schema, output: { errors: { schema: 'return' } } }) + const g = G({ schema, output: { errors: { schema: 'return' } } }) test('query.', () => { expectTypeOf(g.query.resultNonNull(resultFieldSelect)).resolves.toEqualTypeOf<{ __typename: 'Object1' } | Error>() }) }) describe('envelope.schema', () => { - const g = C({ schema, output: { envelope: { errors: { schema: true } }, errors: { schema: 'return' } } }) + const g = G({ schema, output: { envelope: { errors: { schema: true } }, errors: { schema: 'return' } } }) + type Config = typeof g.internal.config test('query.', async () => { - // todo: once we have execution result with type variable errors, then enahnce this test to assert that the result errors come through in the errors field. + // todo: once we have execution result with type variable errors, then enhance this test to assert that the result errors come through in the errors field. expectTypeOf(g.query.resultNonNull(resultFieldSelect)).resolves.toEqualTypeOf< - EnvelopeTransportMemory<{ resultNonNull: { __typename: 'Object1' } }> + Envelope >() }) }) diff --git a/src/layers/6_client/client.raw.test-d.ts b/src/layers/6_client/client.raw.test-d.ts new file mode 100644 index 000000000..3747b6350 --- /dev/null +++ b/src/layers/6_client/client.raw.test-d.ts @@ -0,0 +1,70 @@ +/* eslint-disable */ +import { describe, expectTypeOf } from 'vitest' +import { test } from '../../../tests/_/helpers.js' +import { Graffle } from '../../../tests/_/schema/generated/__.js' +import { schema } from '../../../tests/_/schema/schema.js' +import { gql } from '../0_functions/gql.js' +import type { Envelope } from './Settings/Config.js' + +const g = Graffle.create({ schema }) + +describe(`TypedQueryDocumentNode with just data (no variables)`, () => { + const document = gql<{ id: string }, {}>` + query { + id + } + ` + test(`envelope data is typed`, () => { + // todo is it correct that result.data could be null or undefined? + expectTypeOf(g.raw({ document })).resolves.toEqualTypeOf< + Envelope + >() + }) + test('variables are not allowed to be passed in', () => { + // @ts-expect-error - variables not allowed. + g.raw({ document: document, variables: {} }) + }) +}) + +describe(`TypedQueryDocumentNode with data and optional variables`, () => { + const document = gql<{ id: string }, { filter?: boolean }>` + query { + id + } + ` + test(`envelope data is typed`, () => { + expectTypeOf(g.raw({ document })).resolves.toEqualTypeOf< + Envelope + >() + }) + test('variables are typed and allowed to be passed in or omitted', () => { + // Expect no type errors below + g.raw({ document }) + g.raw({ document, variables: {} }) + g.raw({ document, variables: { filter: true } }) + g.raw({ document, variables: { filter: false } }) + // @ts-expect-error - wrong type + g.raw({ document, variables: { filter: 'wrong type' } }) + }) +}) + +describe(`TypedQueryDocumentNode with data and one or more required variables`, () => { + const document = gql<{ id: string }, { filter: boolean }>` + query { + id + } + ` + test('valid variables can be given', () => { + g.raw({ document, variables: { filter: true } }) + }) + test('variables property cannot be omitted', () => { + // @ts-expect-error - variables missing + g.raw({ document }) + // @ts-expect-error - variables filter property missing + g.raw({ document, variables: {} }) + }) + test('given variables must be valid types', () => { + // @ts-expect-error - wrong type + g.raw({ document, variables: { filter: 'wrong type' } }) + }) +}) diff --git a/src/layers/6_client/client.raw.test.ts b/src/layers/6_client/client.raw.test.ts index 2d8687aa2..c249c75a6 100644 --- a/src/layers/6_client/client.raw.test.ts +++ b/src/layers/6_client/client.raw.test.ts @@ -9,13 +9,13 @@ import { createExtension } from '../5_createExtension/createExtension.js' const graffle = Graffle.create({ schema }) test(`.rawOrThrow() throws if errors array non-empty`, async () => { - await expect(graffle.rawOrThrow({ document: `query {}` })).rejects.toMatchInlineSnapshot( + await expect(graffle.rawStringOrThrow({ document: `query {}` })).rejects.toMatchInlineSnapshot( `[ContextualAggregateError: One or more errors in the execution result.]`, ) }) test(`.raw() returns errors in array`, async () => { - await expect(graffle.raw({ document: `query {}` })).resolves.toMatchInlineSnapshot(` + await expect(graffle.rawString({ document: `query {}` })).resolves.toMatchInlineSnapshot(` { "errors": [ [GraphQLError: Syntax Error: Expected Name, found "}".], @@ -26,8 +26,8 @@ test(`.raw() returns errors in array`, async () => { describe(`memory transport`, () => { let input: object | undefined - const peakInput = createExtension({ - name: `peak`, + const spyInput = createExtension({ + name: `spy`, anyware: ({ exchange }) => { if (exchange.input.transport === `memory`) { input = exchange.input @@ -40,11 +40,11 @@ describe(`memory transport`, () => { }) describe(`operationName`, () => { test(`undefined by default`, async () => { - await graffle.use(peakInput).raw(`query { id }`) + await graffle.use(spyInput).rawString({ document: `query { id }` }) expect(input).toMatchObject({ operationName: undefined }) }) test(`reflects explicit value`, async () => { - await graffle.use(peakInput).raw(`query { id }`, { operationName: `foo` }) + await graffle.use(spyInput).rawString({ document: `query { id }`, operationName: `foo` }) expect(input).toMatchObject({ operationName: `foo` }) }) }) diff --git a/src/layers/6_client/client.rawString.test-d.ts b/src/layers/6_client/client.rawString.test-d.ts new file mode 100644 index 000000000..940b9e560 --- /dev/null +++ b/src/layers/6_client/client.rawString.test-d.ts @@ -0,0 +1,72 @@ +/* eslint-disable */ +import { describe, expectTypeOf } from 'vitest' +import { test } from '../../../tests/_/helpers.js' +import { Graffle } from '../../../tests/_/schema/generated/__.js' +import { schema } from '../../../tests/_/schema/schema.js' +import type { TypedDocumentString } from '../0_functions/types.js' +import type { Envelope } from './Settings/Config.js' + +const g = Graffle.create({ schema }) + +describe(`TypedDocumentString with just data (no variables)`, () => { + const document = ` + query { + id + } + ` as TypedDocumentString<{ id: string }, {}> + + test(`envelope data is typed`, () => { + // todo is it correct that result.data could be null or undefined? + expectTypeOf(g.rawString({ document })).resolves.toEqualTypeOf< + Envelope + >() + }) + test('variables are not allowed to be passed in', () => { + // @ts-expect-error - variables not allowed. + g.rawString({ document: document, variables: {} }) + }) +}) + +describe(`TypedQueryDocumentNode with data and optional variables`, () => { + const document = ` + query { + id + } + ` as TypedDocumentString<{ id: string }, { filter?: boolean }> + + // dprint-ignore + test(`envelope data is typed`, () => { + expectTypeOf(g.rawString({ document })).resolves.toEqualTypeOf>() + }) + test('variables are typed and allowed to be passed in or omitted', () => { + // Expect no type errors below + g.rawString({ document }) + g.rawString({ document, variables: {} }) + g.rawString({ document, variables: { filter: true } }) + g.rawString({ document, variables: { filter: false } }) + // @ts-expect-error - wrong type + g.rawString({ document, variables: { filter: 'wrong type' } }) + }) +}) + +describe(`TypedQueryDocumentNode with data and one or more required variables`, () => { + const document = ` + query { + id + } + ` as TypedDocumentString<{ id: string }, { filter: boolean }> + + test('valid variables can be given', () => { + g.rawString({ document, variables: { filter: true } }) + }) + test('variables property cannot be omitted', () => { + // @ts-expect-error - variables missing + g.rawString({ document }) + // @ts-expect-error - variables filter property missing + g.rawString({ document, variables: {} }) + }) + test('given variables must be valid types', () => { + // @ts-expect-error - wrong type + g.rawString({ document, variables: { filter: 'wrong type' } }) + }) +}) diff --git a/src/layers/6_client/client.test.ts b/src/layers/6_client/client.test.ts index ef6bace22..13b7cfdda 100644 --- a/src/layers/6_client/client.test.ts +++ b/src/layers/6_client/client.test.ts @@ -32,14 +32,14 @@ describe(`interface`, () => { test(`can set headers in constructor`, async ({ fetch }) => { fetch.mockImplementationOnce(() => Promise.resolve(createResponse({ data: { id: `abc` } }))) const graffle = Graffle.create({ schema: endpoint, headers: { 'x-foo': `bar` } }) - await graffle.raw(`query { id }`) + await graffle.rawString({ document: `query { id }` }) const request = fetch.mock.calls[0]?.[0] expect(request?.headers.get(`x-foo`)).toEqual(`bar`) }) test(`sends well formed request`, async ({ fetch, graffle }) => { fetch.mockImplementationOnce(() => Promise.resolve(createResponse({ data: { greetings: `Hello World` } }))) - await graffle.raw({ document: `query { greetings }` }) + await graffle.rawString({ document: `query { greetings }` }) const request = fetch.mock.calls[0]?.[0] expect(request?.headers.get(`content-type`)).toEqual(CONTENT_TYPE_JSON) expect(request?.headers.get(`accept`)).toEqual(CONTENT_TYPE_GQL) diff --git a/src/layers/6_client/client.ts b/src/layers/6_client/client.ts index 6eef499dc..f813d5a8c 100644 --- a/src/layers/6_client/client.ts +++ b/src/layers/6_client/client.ts @@ -1,11 +1,9 @@ -import { type ExecutionResult, GraphQLSchema } from 'graphql' +import { type ExecutionResult, GraphQLSchema, type TypedQueryDocumentNode } from 'graphql' import type { Anyware } from '../../lib/anyware/__.js' import { Errors } from '../../lib/errors/__.js' -import type { SomeExecutionResultWithoutErrors } from '../../lib/graphql.js' import { isOperationTypeName, operationTypeNameToRootTypeName, type RootTypeName } from '../../lib/graphql.js' import { isPlainObject } from '../../lib/prelude.js' -import type { URLInput } from '../0_functions/request.js' -import type { BaseInput } from '../0_functions/types.js' +import type { BaseInput, BaseInput_, TypedDocumentString } from '../0_functions/types.js' import { Schema } from '../1_Schema/__.js' import { readMaybeThunk } from '../1_Schema/core/helpers.js' import type { GlobalRegistry } from '../2_generator/globalRegistry.js' @@ -25,8 +23,6 @@ import { } from './Settings/Config.js' import { type Input, type InputToConfig, inputToConfig } from './Settings/Input.js' -export type SchemaInput = URLInput | GraphQLSchema - // todo could list specific errors here // Anyware entrypoint // Extension @@ -55,29 +51,28 @@ export type TypedContext = Context & { const isTypedContext = (context: Context): context is TypedContext => `schemaIndex` in context -interface RawInput extends BaseInput {} - -type RawParameters = - | [RawInput] - | [ - document: RawInput['document'], - options?: Omit, - ] +type RawParameters = [BaseInput_] +// | [ +// document: BaseInput['document'], +// options?: Omit, +// ] +// +const resolveRawParameters = (parameters: RawParameters) => { + // return parameters.length === 2 + // ? { document: parameters[0], ...parameters[1] } + // return typeof parameters[0] === `string` || `kind` in parameters[0] + // ? { document: parameters[0], ...parameters[1] } + return parameters[0] +} // todo no config needed? +// dprint-ignore export type ClientRaw<$Config extends Config> = { - raw(input: RawInput): Promise> - // todo test this overload - raw( - document: RawInput['document'], - options?: Omit, - ): Promise - - rawOrThrow(input: RawInput): Promise - rawOrThrow( - document: RawInput['document'], - options?: Omit, - ): Promise + rawString<$Data, $Variables>(input: BaseInput>): Promise> + rawStringOrThrow<$Data, $Variables>(input: BaseInput>): Promise> + + raw<$Data, $Variables>(input: BaseInput>): Promise> + rawOrThrow<$Data, $Variables>(input: BaseInput>): Promise> } export type Extension = { @@ -87,6 +82,11 @@ export type Extension = { // dprint-ignore export type Client<$Index extends Schema.Index | null, $Config extends Config> = + { + internal: { + config: $Config + } + } & ClientRaw<$Config> & ( $Index extends Schema.Index @@ -247,7 +247,7 @@ export const createInternal = ( return handleOutput(context, result) } - const runRaw = async (context: Context, rawInput: RawInput) => { + const runRaw = async (context: Context, rawInput: BaseInput_) => { const interface_: InterfaceRaw = `raw` const transport = input.schema instanceof GraphQLSchema ? `memory` : `http` const initialInput = { @@ -267,15 +267,11 @@ export const createInternal = ( return await run(context, initialInput) } - const resolveRawParameters = (parameters: RawParameters) => { - return parameters.length === 2 - ? { document: parameters[0], ...parameters[1] } - : typeof parameters[0] === `string` || `kind` in parameters[0] - ? { document: parameters[0], ...parameters[1] } - : parameters[0] - } // @ts-expect-error ignoreme const client: Client = { + internal: { + config: context.config, + }, raw: async (...args: RawParameters) => { const input = resolveRawParameters(args) const contextWithOutputSet = updateContextConfig(context, { ...context.config, output: traditionalGraphqlOutput }) @@ -289,6 +285,14 @@ export const createInternal = ( }) return await runRaw(contextWithOutputSet, input) }, + rawString: async (...args: RawParameters) => { + // eslint-disable-next-line + return await client.raw(...args) + }, + rawStringOrThrow: async (...args: RawParameters) => { + // eslint-disable-next-line + return await client.rawOrThrow(...args) + }, // todo $use use: (extensionOrAnyware: Extension | Anyware.Extension2) => { const extension = typeof extensionOrAnyware === `function` diff --git a/src/layers/6_client/prefilled.ts b/src/layers/6_client/prefilled.ts index e9197ea83..6c8c7eb7f 100644 --- a/src/layers/6_client/prefilled.ts +++ b/src/layers/6_client/prefilled.ts @@ -1,4 +1,5 @@ -import type { Exact, IsSomePropertiesRequired } from '../../lib/prelude.js' +import type { HasRequiredKeys } from 'type-fest' +import type { Exact } from '../../lib/prelude.js' import type { Schema } from '../1_Schema/__.js' import type { GlobalRegistry } from '../2_generator/globalRegistry.js' import { type Client, create } from './client.js' @@ -12,7 +13,7 @@ export type CreatePrefilled = <$Name extends GlobalRegistry.SchemaNames>(name: $ >(...args: // eslint-disable-next-line // @ts-ignore passes after generation - IsSomePropertiesRequired> extends true + HasRequiredKeys> extends true // eslint-disable-next-line // @ts-ignore passes after generation ? [input: Exact<$Input, InputPrefilled>] diff --git a/src/layers/6_client/types.ts b/src/layers/6_client/types.ts index b0ac627df..68440421a 100644 --- a/src/layers/6_client/types.ts +++ b/src/layers/6_client/types.ts @@ -1,5 +1,6 @@ -import type { DocumentNode } from 'graphql' +import type { TypedQueryDocumentNode } from 'graphql' +import type { TypedDocumentString } from '../0_functions/types.js' -export type DocumentInput = DocumentNode | string +export type DocumentInput = string | TypedDocumentString | TypedQueryDocumentNode export type OperationNameInput = string diff --git a/src/layers/7_extensions/Upload/Upload.test.ts b/src/layers/7_extensions/Upload/Upload.test.ts index c01717616..9c669f09c 100644 --- a/src/layers/7_extensions/Upload/Upload.test.ts +++ b/src/layers/7_extensions/Upload/Upload.test.ts @@ -46,7 +46,7 @@ afterAll(async () => { }) test(`upload`, async () => { - const result = await graffle.raw({ + const result = await graffle.rawString({ document: ` mutation ($blob: Upload!) { readTextFile(blob: $blob) diff --git a/src/lib/graphql.ts b/src/lib/graphql.ts index 73c8a77c0..9be85c0de 100644 --- a/src/lib/graphql.ts +++ b/src/lib/graphql.ts @@ -11,7 +11,6 @@ import { isListType, isNonNullType, } from 'graphql' -import type { ObjMap } from 'graphql/jsutils/ObjMap.js' import type { Errors } from './errors/__.js' export type TypeMapByKind = @@ -253,12 +252,3 @@ export type OperationTypeName = 'query' | 'mutation' export const isOperationTypeName = (value: unknown): value is OperationTypeName => value === `query` || value === `mutation` - -export interface SomeExecutionResultWithoutErrors< - TData = ObjMap, - TExtensions = ObjMap, -> { - errors?: readonly [] - data?: TData | null - extensions?: TExtensions -} diff --git a/src/lib/prelude.ts b/src/lib/prelude.ts index 6e511bf21..b8ca50f89 100644 --- a/src/lib/prelude.ts +++ b/src/lib/prelude.ts @@ -394,4 +394,4 @@ export type PickRequiredProperties = { [K in keyof T as undefined extends T[K] ? never : K]: T[K] } -export type IsSomePropertiesRequired = keyof PickRequiredProperties extends never ? false : true +export type Negate = T extends true ? false : true