diff --git a/src/ResultSet/ResultSet.test-d.ts b/src/client/ResultSet/ResultSet.test-d.ts similarity index 97% rename from src/ResultSet/ResultSet.test-d.ts rename to src/client/ResultSet/ResultSet.test-d.ts index deb179b65..3a3bd5b78 100644 --- a/src/ResultSet/ResultSet.test-d.ts +++ b/src/client/ResultSet/ResultSet.test-d.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/ban-types */ import { expectTypeOf, test } from 'vitest' -import type { Index } from '../../tests/ts/_/schema/generated/Index.js' -import type * as Schema from '../../tests/ts/_/schema/generated/SchemaBuildtime.js' +import type { Index } from '../../../tests/ts/_/schema/generated/Index.js' +import type * as Schema from '../../../tests/ts/_/schema/generated/SchemaBuildtime.js' import type { SelectionSet } from '../SelectionSet/__.js' import type { ResultSet } from './__.js' diff --git a/src/ResultSet/ResultSet.ts b/src/client/ResultSet/ResultSet.ts similarity index 95% rename from src/ResultSet/ResultSet.ts rename to src/client/ResultSet/ResultSet.ts index 2ba4e823c..70e28330d 100644 --- a/src/ResultSet/ResultSet.ts +++ b/src/client/ResultSet/ResultSet.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/ban-types */ import type { Simplify } from 'type-fest' -import type { GetKeyOr, SimplifyDeep } from '../lib/prelude.js' -import type { TSError } from '../lib/TSError.js' -import type { Schema, SomeField } from '../Schema/__.js' -import type { PickScalarFields } from '../Schema/Output/Output.js' +import type { GetKeyOr, SimplifyDeep } from '../../lib/prelude.js' +import type { TSError } from '../../lib/TSError.js' +import type { Schema, SomeField } from '../../Schema/__.js' +import type { PickScalarFields } from '../../Schema/Output/Output.js' import type { SelectionSet } from '../SelectionSet/__.js' // dprint-ignore diff --git a/src/ResultSet/__.ts b/src/client/ResultSet/__.ts similarity index 100% rename from src/ResultSet/__.ts rename to src/client/ResultSet/__.ts diff --git a/src/SelectionSet/SelectionSet.test-d.ts b/src/client/SelectionSet/SelectionSet.test-d.ts similarity index 99% rename from src/SelectionSet/SelectionSet.test-d.ts rename to src/client/SelectionSet/SelectionSet.test-d.ts index 34cd5118f..7fd61ab87 100644 --- a/src/SelectionSet/SelectionSet.test-d.ts +++ b/src/client/SelectionSet/SelectionSet.test-d.ts @@ -1,5 +1,5 @@ import { assertType, expectTypeOf, test } from 'vitest' -import type { Index } from '../../tests/ts/_/schema/generated/Index.js' +import type { Index } from '../../../tests/ts/_/schema/generated/Index.js' import type { SelectionSet } from './__.js' type Q = SelectionSet.Query diff --git a/src/SelectionSet/SelectionSet.ts b/src/client/SelectionSet/SelectionSet.ts similarity index 98% rename from src/SelectionSet/SelectionSet.ts rename to src/client/SelectionSet/SelectionSet.ts index 65870f32e..8e4d462c9 100644 --- a/src/SelectionSet/SelectionSet.ts +++ b/src/client/SelectionSet/SelectionSet.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-types */ -import type { MaybeList, StringNonEmpty, Values } from '../lib/prelude.js' -import type { TSError } from '../lib/TSError.js' +import type { MaybeList, StringNonEmpty, Values } from '../../lib/prelude.js' +import type { TSError } from '../../lib/TSError.js' import type { InputFieldsAllNullable, OmitNullableFields, @@ -9,7 +9,7 @@ import type { Schema, SomeField, SomeFields, -} from '../Schema/__.js' +} from '../../Schema/__.js' export type Query<$Index extends Schema.Index> = $Index['Root']['Query'] extends Schema.Object$2 ? Object<$Index['Root']['Query'], $Index> diff --git a/src/SelectionSet/_.ts b/src/client/SelectionSet/_.ts similarity index 100% rename from src/SelectionSet/_.ts rename to src/client/SelectionSet/_.ts diff --git a/src/SelectionSet/__.ts b/src/client/SelectionSet/__.ts similarity index 100% rename from src/SelectionSet/__.ts rename to src/client/SelectionSet/__.ts diff --git a/src/SelectionSet/__snapshots__/toGraphQLDocumentString.test.ts.snap b/src/client/SelectionSet/__snapshots__/toGraphQLDocumentString.test.ts.snap similarity index 100% rename from src/SelectionSet/__snapshots__/toGraphQLDocumentString.test.ts.snap rename to src/client/SelectionSet/__snapshots__/toGraphQLDocumentString.test.ts.snap diff --git a/src/SelectionSet/toGraphQLDocumentString.test.ts b/src/client/SelectionSet/toGraphQLDocumentString.test.ts similarity index 98% rename from src/SelectionSet/toGraphQLDocumentString.test.ts rename to src/client/SelectionSet/toGraphQLDocumentString.test.ts index 6f652d06b..876a20d1f 100644 --- a/src/SelectionSet/toGraphQLDocumentString.test.ts +++ b/src/client/SelectionSet/toGraphQLDocumentString.test.ts @@ -1,6 +1,6 @@ import { parse, print } from 'graphql' import { describe, expect, test } from 'vitest' -import type { Index } from '../../tests/ts/_/schema/generated/Index.js' +import type { Index } from '../../../tests/ts/_/schema/generated/Index.js' import type { SelectionSet } from './__.js' import { toGraphQLDocumentString } from './toGraphQLDocumentString.js' diff --git a/src/SelectionSet/toGraphQLDocumentString.ts b/src/client/SelectionSet/toGraphQLDocumentString.ts similarity index 100% rename from src/SelectionSet/toGraphQLDocumentString.ts rename to src/client/SelectionSet/toGraphQLDocumentString.ts diff --git a/src/client.test.ts b/src/client/client.test.ts similarity index 96% rename from src/client.test.ts rename to src/client/client.test.ts index edbab104b..cba1f3262 100644 --- a/src/client.test.ts +++ b/src/client/client.test.ts @@ -1,8 +1,8 @@ /* eslint-disable */ import { beforeEach, describe, expect, test } from 'vitest' -import { setupMockServer } from '../tests/raw/__helpers.js' -import type { Index } from '../tests/ts/_/schema/generated/Index.js' -import { $Index as schemaIndex } from '../tests/ts/_/schema/generated/SchemaRuntime.js' +import { setupMockServer } from '../../tests/raw/__helpers.js' +import type { Index } from '../../tests/ts/_/schema/generated/Index.js' +import { $Index as schemaIndex } from '../../tests/ts/_/schema/generated/SchemaRuntime.js' import { create } from './client.js' const ctx = setupMockServer() diff --git a/src/client/client.ts b/src/client/client.ts new file mode 100644 index 000000000..3226f539a --- /dev/null +++ b/src/client/client.ts @@ -0,0 +1,91 @@ +import type { ExcludeUndefined } from 'type-fest/source/required-deep.js' +import request from '../entrypoints/main.js' +import { type RootTypeName } from '../lib/graphql.js' +import type { Exact } from '../lib/prelude.js' +import type { Object$2, Schema } from '../Schema/__.js' +import * as CustomScalars from './customScalars.js' +import type { ResultSet } from './ResultSet/__.js' +import { SelectionSet } from './SelectionSet/__.js' +import type { GraphQLDocumentObject } from './SelectionSet/toGraphQLDocumentString.js' + +// dprint-ignore +export type Client<$SchemaIndex extends Schema.Index> = + & ( + $SchemaIndex['Root']['Query'] extends null + ? unknown + : { + query: <$SelectionSet extends object>(selectionSet: Exact<$SelectionSet, SelectionSet.Query<$SchemaIndex>>) => Promise> + } + ) + & ( + $SchemaIndex['Root']['Mutation'] extends null + ? unknown + : { + mutation: <$SelectionSet extends object>(selectionSet: Exact<$SelectionSet, SelectionSet.Mutation<$SchemaIndex>>) => Promise> + } + ) +// todo +// & ($SchemaIndex['Root']['Subscription'] extends null ? { +// subscription: <$SelectionSet extends SelectionSet.Subscription<$SchemaIndex>>(selectionSet: $SelectionSet) => Promise> +// } +// : unknown) +// + +interface HookInputDocumentEncode { + rootIndex: Object$2 + documentObject: GraphQLDocumentObject +} + +interface Input { + url: URL | string + headers?: HeadersInit + // If there are no custom scalars then this property is useless. Improve types. + schemaIndex: Schema.Index + hooks?: { + documentEncode: ( + input: HookInputDocumentEncode, + fn: (input: HookInputDocumentEncode) => GraphQLDocumentObject, + ) => GraphQLDocumentObject + } +} + +export const create = <$SchemaIndex extends Schema.Index>(input: Input): Client<$SchemaIndex> => { + const parentInput = input + + const runHookable = <$Name extends keyof ExcludeUndefined>( + name: $Name, + input: Parameters[$Name]>[0], + fn: Parameters[$Name]>[1], + ) => { + return parentInput.hooks?.[name](input, fn) ?? fn(input) + } + + const sendDocumentObject = (rootType: RootTypeName) => async (documentObject: GraphQLDocumentObject) => { + const rootIndex = input.schemaIndex.Root[rootType] + if (!rootIndex) throw new Error(`Root type not found: ${rootType}`) + + const documentObjectEncoded = runHookable( + `documentEncode`, + { rootIndex, documentObject }, + ({ rootIndex, documentObject }) => CustomScalars.encode({ index: rootIndex, documentObject }), + ) + const documentString = SelectionSet.toGraphQLDocumentString(documentObjectEncoded) + const result = await request({ + url: new URL(input.url).href, + requestHeaders: input.headers, + document: documentString, + }) + const resultDecoded = CustomScalars.decode(rootIndex, result as object) + return resultDecoded + } + + // @ts-expect-error ignoreme + const client: Client<$SchemaIndex> = { + query: sendDocumentObject(`Query`), + mutation: sendDocumentObject(`Mutation`), + // todo + // subscription: async () => {}, + } + + return client +} diff --git a/src/client.ts b/src/client/customScalars.ts similarity index 61% rename from src/client.ts rename to src/client/customScalars.ts index 09eb3248e..e0276ba61 100644 --- a/src/client.ts +++ b/src/client/customScalars.ts @@ -1,97 +1,11 @@ -import type { ExcludeUndefined } from 'type-fest/source/required-deep.js' -import request from './entrypoints/main.js' -import { type RootTypeName, standardScalarTypeNames } from './lib/graphql.js' -import type { Exact } from './lib/prelude.js' -import type { ResultSet } from './ResultSet/__.js' -import type { Object$2, Schema } from './Schema/__.js' -import { Output } from './Schema/__.js' -import { readMaybeThunk } from './Schema/core/helpers.js' -import { SelectionSet } from './SelectionSet/__.js' +import { standardScalarTypeNames } from '../lib/graphql.js' +import type { Object$2, Schema } from '../Schema/__.js' +import { Output } from '../Schema/__.js' +import { readMaybeThunk } from '../Schema/core/helpers.js' +import type { SelectionSet } from './SelectionSet/__.js' import type { Args } from './SelectionSet/SelectionSet.js' import type { GraphQLDocumentObject } from './SelectionSet/toGraphQLDocumentString.js' -// dprint-ignore -export type Client<$SchemaIndex extends Schema.Index> = - & ( - $SchemaIndex['Root']['Query'] extends null - ? unknown - : { - query: <$SelectionSet extends object>(selectionSet: Exact<$SelectionSet, SelectionSet.Query<$SchemaIndex>>) => Promise> - } - ) - & ( - $SchemaIndex['Root']['Mutation'] extends null - ? unknown - : { - mutation: <$SelectionSet extends object>(selectionSet: Exact<$SelectionSet, SelectionSet.Mutation<$SchemaIndex>>) => Promise> - } - ) -// todo -// & ($SchemaIndex['Root']['Subscription'] extends null ? { -// subscription: <$SelectionSet extends SelectionSet.Subscription<$SchemaIndex>>(selectionSet: $SelectionSet) => Promise> -// } -// : unknown) -// - -interface HookInputDocumentEncode { - rootIndex: Object$2 - documentObject: GraphQLDocumentObject -} - -interface Input { - url: URL | string - headers?: HeadersInit - // If there are no custom scalars then this property is useless. Improve types. - schemaIndex: Schema.Index - hooks?: { - documentEncode: ( - input: HookInputDocumentEncode, - fn: (input: HookInputDocumentEncode) => GraphQLDocumentObject, - ) => GraphQLDocumentObject - } -} - -export const create = <$SchemaIndex extends Schema.Index>(input: Input): Client<$SchemaIndex> => { - const parentInput = input - - const runHookable = <$Name extends keyof ExcludeUndefined>( - name: $Name, - input: Parameters[$Name]>[0], - fn: Parameters[$Name]>[1], - ) => { - return parentInput.hooks?.[name](input, fn) ?? fn(input) - } - - const sendDocumentObject = (rootType: RootTypeName) => async (documentObject: GraphQLDocumentObject) => { - const rootIndex = input.schemaIndex.Root[rootType] - if (!rootIndex) throw new Error(`Root type not found: ${rootType}`) - - const documentObjectEncoded = runHookable( - `documentEncode`, - { rootIndex, documentObject }, - ({ rootIndex, documentObject }) => encodeCustomScalars({ index: rootIndex, documentObject }), - ) - const documentString = SelectionSet.toGraphQLDocumentString(documentObjectEncoded) - const result = await request({ - url: new URL(input.url).href, - requestHeaders: input.headers, - document: documentString, - }) - const resultDecoded = decodeCustomScalars(rootIndex, result as object) - return resultDecoded - } - - // @ts-expect-error ignoreme - const client: Client<$SchemaIndex> = { - query: sendDocumentObject(`Query`), - mutation: sendDocumentObject(`Mutation`), - // todo - // subscription: async () => {}, - } - - return client -} - namespace SSValue { export type Obj = { $?: Args2 @@ -100,9 +14,9 @@ namespace SSValue { export type Arg = boolean | Arg[] | { [key: string]: Arg } } -const encodeCustomScalars = ( +export const encode = ( input: { - index: Object$2 + index: Schema.Object$2 documentObject: SelectionSet.GraphQLDocumentObject }, ): GraphQLDocumentObject => { @@ -153,7 +67,7 @@ const encodeCustomScalarsArgValue = (indexArgMaybeThunk: Schema.Input.Any, argVa throw new Error(`Unsupported arg kind: ${String(indexArg)}`) } -const decodeCustomScalars = (index: Object$2, documentQueryObject: object): object => { +export const decode = (index: Schema.Object$2, documentQueryObject: object): object => { return Object.fromEntries( Object.entries(documentQueryObject).map(([fieldName, v]) => { const indexField = index.fields[fieldName] @@ -197,7 +111,7 @@ const decodeCustomScalarValue = ( assertGraphQLObject(fieldValue) if (typeWithoutNonNull.kind === `Object`) { - return decodeCustomScalars(typeWithoutNonNull, fieldValue) + return decode(typeWithoutNonNull, fieldValue) } if (typeWithoutNonNull.kind === `Interface` || typeWithoutNonNull.kind === `Union`) { @@ -214,16 +128,12 @@ const decodeCustomScalarValue = ( return false }) as undefined | Object$2 if (!ObjectType) throw new Error(`Could not pick object for ${typeWithoutNonNull.kind} selection`) - return decodeCustomScalars(ObjectType, fieldValue) + return decode(ObjectType, fieldValue) } return fieldValue } -type GraphQLObject = { - __typename?: string -} - // eslint-disable-next-line function assertArray(v: unknown): asserts v is unknown[] { if (!Array.isArray(v)) throw new Error(`Expected array. Got: ${String(v)}`) @@ -241,3 +151,7 @@ function assertGraphQLObject(v: unknown): asserts v is GraphQLObject { throw new Error(`Expected string __typename or undefined. Got: ${String(v.__typename)}`) } } + +type GraphQLObject = { + __typename?: string +} diff --git a/src/entrypoints/alpha/client.ts b/src/entrypoints/alpha/client.ts index fe400a163..048d4632e 100644 --- a/src/entrypoints/alpha/client.ts +++ b/src/entrypoints/alpha/client.ts @@ -1 +1 @@ -export * from '../../client.js' +export * from '../../client/client.js'