Skip to content

Commit

Permalink
refactor: client dir
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Apr 14, 2024
1 parent 893ad01 commit b9888e2
Show file tree
Hide file tree
Showing 14 changed files with 120 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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<Index>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* 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,
PickNullableFields,
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>
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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'

Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions src/client.test.ts → src/client/client.test.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
91 changes: 91 additions & 0 deletions src/client/client.ts
Original file line number Diff line number Diff line change
@@ -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<ResultSet.Query<$SelectionSet, $SchemaIndex>>
}
)
& (
$SchemaIndex['Root']['Mutation'] extends null
? unknown
: {
mutation: <$SelectionSet extends object>(selectionSet: Exact<$SelectionSet, SelectionSet.Mutation<$SchemaIndex>>) => Promise<ResultSet.Mutation<$SelectionSet,$SchemaIndex>>
}
)
// todo
// & ($SchemaIndex['Root']['Subscription'] extends null ? {
// subscription: <$SelectionSet extends SelectionSet.Subscription<$SchemaIndex>>(selectionSet: $SelectionSet) => Promise<ResultSet.Subscription<$SelectionSet,$SchemaIndex>>
// }
// : 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<Input['hooks']>>(
name: $Name,
input: Parameters<ExcludeUndefined<Input['hooks']>[$Name]>[0],
fn: Parameters<ExcludeUndefined<Input['hooks']>[$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
}
114 changes: 14 additions & 100 deletions src/client.ts → src/client/customScalars.ts
Original file line number Diff line number Diff line change
@@ -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<ResultSet.Query<$SelectionSet, $SchemaIndex>>
}
)
& (
$SchemaIndex['Root']['Mutation'] extends null
? unknown
: {
mutation: <$SelectionSet extends object>(selectionSet: Exact<$SelectionSet, SelectionSet.Mutation<$SchemaIndex>>) => Promise<ResultSet.Mutation<$SelectionSet,$SchemaIndex>>
}
)
// todo
// & ($SchemaIndex['Root']['Subscription'] extends null ? {
// subscription: <$SelectionSet extends SelectionSet.Subscription<$SchemaIndex>>(selectionSet: $SelectionSet) => Promise<ResultSet.Subscription<$SelectionSet,$SchemaIndex>>
// }
// : 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<Input['hooks']>>(
name: $Name,
input: Parameters<ExcludeUndefined<Input['hooks']>[$Name]>[0],
fn: Parameters<ExcludeUndefined<Input['hooks']>[$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
Expand All @@ -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 => {
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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`) {
Expand All @@ -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)}`)
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion src/entrypoints/alpha/client.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from '../../client.js'
export * from '../../client/client.js'

0 comments on commit b9888e2

Please sign in to comment.