diff --git a/src/layers/3_SelectionSet/__snapshots__/encode.test.ts.snap b/src/layers/3_SelectionSet/__snapshots__/encode.test.ts.snap index 7d06d9cb2..2a03673a2 100644 --- a/src/layers/3_SelectionSet/__snapshots__/encode.test.ts.snap +++ b/src/layers/3_SelectionSet/__snapshots__/encode.test.ts.snap @@ -669,6 +669,180 @@ exports[`args > Query 4`] = ` " `; +exports[`args > custom scalars > Query > arg field 1`] = ` +" +{ + "dateArg": { + "$": { + "date": "1970-01-01T00:00:00.000Z" + } + } +} +-------------- +{ + dateArg(date: "1970-01-01T00:00:00.000Z") +} +" +`; + +exports[`args > custom scalars > Query > arg field in list (null) 1`] = ` +" +{ + "dateArgList": { + "$": { + "date": null + } + } +} +-------------- +{ + dateArgList(date: null) +} +" +`; + +exports[`args > custom scalars > Query > arg field in list 1`] = ` +" +{ + "dateArgList": { + "$": { + "date": [ + "1970-01-01T00:00:00.000Z", + "1970-01-01T00:00:00.001Z" + ] + } + } +} +-------------- +{ + dateArgList(date: ["1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.001Z"]) +} +" +`; + +exports[`args > custom scalars > Query > arg field in non-null 1`] = ` +" +{ + "dateArgNonNull": { + "$": { + "date": "1970-01-01T00:00:00.000Z" + } + } +} +-------------- +{ + dateArgNonNull(date: "1970-01-01T00:00:00.000Z") +} +" +`; + +exports[`args > custom scalars > Query > arg field in non-null list (with list) 1`] = ` +" +{ + "dateArgNonNullList": { + "$": { + "date": [ + "1970-01-01T00:00:00.000Z", + "1970-01-01T00:00:00.001Z" + ] + } + } +} +-------------- +{ + dateArgNonNullList( + date: ["1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.001Z"] + ) +} +" +`; + +exports[`args > custom scalars > Query > arg field in non-null list (with null) 1`] = ` +" +{ + "dateArgNonNullList": { + "$": { + "date": [ + null, + "1970-01-01T00:00:00.000Z" + ] + } + } +} +-------------- +{ + dateArgNonNullList(date: [null, "1970-01-01T00:00:00.000Z"]) +} +" +`; + +exports[`args > custom scalars > Query > arg field in non-null list non-null 1`] = ` +" +{ + "dateArgNonNullListNonNull": { + "$": { + "date": [ + "1970-01-01T00:00:00.000Z", + "1970-01-01T00:00:00.001Z" + ] + } + } +} +-------------- +{ + dateArgNonNullListNonNull( + date: ["1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.001Z"] + ) +} +" +`; + +exports[`args > custom scalars > Query > input object field 1`] = ` +" +{ + "dateArgInputObject": { + "$": { + "input": { + "idRequired": "", + "dateRequired": "1970-01-01T00:00:00.000Z", + "date": "1970-01-01T00:00:00.001Z" + } + } + } +} +-------------- +{ + dateArgInputObject( + input: {idRequired: "", dateRequired: "1970-01-01T00:00:00.000Z", date: "1970-01-01T00:00:00.001Z"} + ) +} +" +`; + +exports[`args > custom scalars > Query > nested input object field 1`] = ` +" +{ + "InputObjectNested": { + "$": { + "input": { + "InputObject": { + "idRequired": "", + "dateRequired": "1970-01-01T00:00:00.000Z", + "date": "1970-01-01T00:00:00.001Z" + } + } + } + } +} +-------------- +{ + InputObjectNested( + input: {InputObject: {idRequired: "", dateRequired: "1970-01-01T00:00:00.000Z", date: "1970-01-01T00:00:00.001Z"}} + ) +} +" +`; + exports[`enum > Query 1`] = ` " { diff --git a/src/layers/3_SelectionSet/encode.test.ts b/src/layers/3_SelectionSet/encode.test.ts index ffabbc168..91831f09f 100644 --- a/src/layers/3_SelectionSet/encode.test.ts +++ b/src/layers/3_SelectionSet/encode.test.ts @@ -1,5 +1,6 @@ import { parse, print } from 'graphql' import { describe, expect, test } from 'vitest' +import { db } from '../../../tests/_/db.js' import type { Index } from '../../../tests/_/schema/generated/Index.js' import { $Index as schemaIndex } from '../../../tests/_/schema/generated/SchemaRuntime.js' import type { SelectionSet } from './__.js' @@ -24,121 +25,139 @@ const prepareResult = (ss: Q) => { return beforeAfter } +const testArgs = [ + `Query`, + ( + ...args: [SelectionSet.Object] | [ + description: string, + ss: SelectionSet.Object, + ] + ) => { + if (args.length === 1) { + const [ss] = args + expect(prepareResult(ss)).toMatchSnapshot() + return + } else { + const [description, ss] = args + expect(prepareResult(ss)).toMatchSnapshot(description) + } + }, +] as const + describe(`enum`, () => { test.each([ - s({ result: { $: { case: `Object1` }, __typename: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ result: { $: { case: `Object1` }, __typename: true } })], + ])(...testArgs) }) describe(`union`, () => { test.each([ - s({ unionFooBar: { __typename: true } }), - s({ unionFooBar: { onBar: { int: true } } }), - s({ unionFooBar: { onBar: { $skip: true, int: true } } }), + [s({ unionFooBar: { __typename: true } })], + [s({ unionFooBar: { onBar: { int: true } } })], + [s({ unionFooBar: { onBar: { $skip: true, int: true } } })], // s({ unionFooBar: { onBar: {} } }), // todo should be static type error - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + ])(...testArgs) }) describe(`alias`, () => { test.each([ - s({ id_as_x: true }), - s({ id_as_x: true, id_as_id2: true }), - s({ id_as_x: { $skip: true } }), - s({ object_as_x: { $skip: true, id: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ id_as_x: true })], + [s({ id_as_x: true, id_as_id2: true })], + [s({ id_as_x: { $skip: true } })], + [s({ object_as_x: { $skip: true, id: true } })], + ])(...testArgs) }) describe(`args`, () => { test.each([ - s({ stringWithArgs: { $: { boolean: true, float: 1 } } }), - s({ stringWithArgs: { $: {} } }), + [s({ stringWithArgs: { $: { boolean: true, float: 1 } } })], + [s({ stringWithArgs: { $: {} } })], // s({ objectWithArgs: { $: { id: `` } } }), // todo should be static error // s({ objectWithArgs: { $: {} } }), // todo should be static error - s({ objectWithArgs: { $: { id: `` }, id: true } }), - s({ objectWithArgs: { $: {}, id: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ objectWithArgs: { $: { id: `` }, id: true } })], + [s({ objectWithArgs: { $: {}, id: true } })], + ])(...testArgs) }) describe(`$include`, () => { test.each([ - s({ object: { $include: true, id: true } }), - s({ object: { $include: false, id: true } }), - s({ object: { $include: undefined, id: true } }), - s({ object: { $include: { if: true }, id: true } }), - s({ object: { $include: { if: false }, id: true } }), - s({ object: { $include: { if: undefined }, id: true } }), - s({ object: { $include: {}, id: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ object: { $include: true, id: true } })], + [s({ object: { $include: false, id: true } })], + [s({ object: { $include: undefined, id: true } })], + [s({ object: { $include: { if: true }, id: true } })], + [s({ object: { $include: { if: false }, id: true } })], + [s({ object: { $include: { if: undefined }, id: true } })], + [s({ object: { $include: {}, id: true } })], + ])(...testArgs) }) describe(`$skip`, () => { test.each([ - s({ object: { $skip: true, id: true } }), - s({ object: { $skip: false, id: true } }), - s({ object: { $skip: undefined, id: true } }), - s({ object: { $skip: { if: true }, id: true } }), - s({ object: { $skip: { if: false }, id: true } }), - s({ object: { $skip: { if: undefined }, id: true } }), - s({ object: { $skip: {}, id: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ object: { $skip: true, id: true } })], + [s({ object: { $skip: false, id: true } })], + [s({ object: { $skip: undefined, id: true } })], + [s({ object: { $skip: { if: true }, id: true } })], + [s({ object: { $skip: { if: false }, id: true } })], + [s({ object: { $skip: { if: undefined }, id: true } })], + [s({ object: { $skip: {}, id: true } })], + ])(...testArgs) }) describe(`$defer`, () => { test.each([ - s({ object: { $defer: true, id: true } }), - s({ object: { $defer: false, id: true } }), - s({ object: { $defer: undefined, id: true } }), - s({ object: { $defer: { if: true }, id: true } }), - s({ object: { $defer: { if: false }, id: true } }), - s({ object: { $defer: { if: undefined }, id: true } }), - s({ object: { $defer: {}, id: true } }), - s({ object: { $defer: { label: `foobar` }, id: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ object: { $defer: true, id: true } })], + [s({ object: { $defer: false, id: true } })], + [s({ object: { $defer: undefined, id: true } })], + [s({ object: { $defer: { if: true }, id: true } })], + [s({ object: { $defer: { if: false }, id: true } })], + [s({ object: { $defer: { if: undefined }, id: true } })], + [s({ object: { $defer: {}, id: true } })], + [s({ object: { $defer: { label: `foobar` }, id: true } })], + ])(...testArgs) }) describe(`$stream`, () => { test.each([ - s({ object: { $stream: true, id: true } }), - s({ object: { $stream: false, id: true } }), - s({ object: { $stream: undefined, id: true } }), - s({ object: { $stream: { if: true }, id: true } }), - s({ object: { $stream: { if: false }, id: true } }), - s({ object: { $stream: { if: undefined }, id: true } }), - s({ object: { $stream: {}, id: true } }), - s({ object: { $stream: { label: `foobar` }, id: true } }), - s({ object: { $stream: { initialCount: 5 }, id: true } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() - }) + [s({ object: { $stream: true, id: true } })], + [s({ object: { $stream: false, id: true } })], + [s({ object: { $stream: undefined, id: true } })], + [s({ object: { $stream: { if: true }, id: true } })], + [s({ object: { $stream: { if: false }, id: true } })], + [s({ object: { $stream: { if: undefined }, id: true } })], + [s({ object: { $stream: {}, id: true } })], + [s({ object: { $stream: { label: `foobar` }, id: true } })], + [s({ object: { $stream: { initialCount: 5 }, id: true } })], + ])(...testArgs) }) describe(`other`, () => { test.each([ - s({ __typename: true }), - s({ string: true }), - s({ string: 1 }), + [s({ __typename: true })], + [s({ string: true })], + [s({ string: 1 })], // s({ string: false }), // todo should be static error - s({ id: true, string: false }), - s({ id: true, string: 0 }), - s({ id: true, string: undefined }), - s({ object: { id: true } }), - s({ objectNested: { object: { string: true, id: true, int: false } } }), - s({ objectNested: { object: { string: true, id: true, int: { $skip: true } } } }), - ])(`Query`, (ss) => { - expect(prepareResult(ss)).toMatchSnapshot() + [s({ id: true, string: false })], + [s({ id: true, string: 0 })], + [s({ id: true, string: undefined })], + [s({ object: { id: true } })], + [s({ objectNested: { object: { string: true, id: true, int: false } } })], + [s({ objectNested: { object: { string: true, id: true, int: { $skip: true } } } })], + ])(...testArgs) +}) + +describe(`args`, () => { + // dprint-ignore + describe(`custom scalars`, () => { + test.each([ + [`arg field`, s({ dateArg: { $: { date: db.date0 } } })], + [`arg field in non-null`,s({ dateArgNonNull: { $: { date: db.date0 } } })], + [`arg field in list`,s({ dateArgList: { $: { date: [db.date0, new Date(1)] } } })], + [`arg field in list (null)`,s({ dateArgList: { $: { date: null } } })], + [`arg field in non-null list (with list)`,s({ dateArgNonNullList: { $: { date: [db.date0, new Date(1)] } } })], + [`arg field in non-null list (with null)`,s({ dateArgNonNullList: { $: { date: [null, db.date0] } } })], + [`arg field in non-null list non-null`,s({ dateArgNonNullListNonNull: { $: { date: [db.date0, new Date(1)] } } })], + [`input object field`,s({ dateArgInputObject: { $: { input: { idRequired: ``, dateRequired: db.date0, date: new Date(1) } } } })], + [`nested input object field`,s({ InputObjectNested: { $: { input: { InputObject: { idRequired: ``, dateRequired: db.date0, date: new Date(1) } } } } })] + ] as const)(...testArgs) }) }) diff --git a/src/layers/3_SelectionSet/encode.ts b/src/layers/3_SelectionSet/encode.ts index 72fbb8c12..6bfaaeebf 100644 --- a/src/layers/3_SelectionSet/encode.ts +++ b/src/layers/3_SelectionSet/encode.ts @@ -1,5 +1,5 @@ import { RootTypeName } from '../../lib/graphql.js' -import { lowerCaseFirstLetter } from '../../lib/prelude.js' +import { assertArray, assertObject, lowerCaseFirstLetter } from '../../lib/prelude.js' import { Schema } from '../1_Schema/__.js' import { readMaybeThunk } from '../1_Schema/core/helpers.js' import type { ReturnModeType } from '../5_client/Config.js' @@ -26,9 +26,9 @@ type SpecialFields = { $?: Args } -type Args = { [k: string]: Args_ } +type Args = { [k: string]: ArgValue } -type Args_ = string | boolean | null | number | Args +type ArgValue = string | boolean | null | number | Args export type DocumentObject = Record @@ -46,6 +46,13 @@ export interface Context { schemaIndex: Schema.Index config: { returnMode: ReturnModeType + // typeHooks: { + // /** + // * Control encoding for custom scalars + // * found in inputs. + // */ + // customScalar: (v: Schema.Scalar.Scalar) => Schema.Scalar.StandardScalarRuntimeTypes + // } } } @@ -85,32 +92,64 @@ const resolveDirectives = (fieldValue: FieldValue) => { return directives } -const resolveArgs = (schemaField: Schema.SomeField, ss: Indicator | SS) => { - if (isIndicator(ss)) return `` +const resolveArgValue = ( + context: Context, + schemaArgTypeMaybeThunk: Schema.Input.Any, + argValue: ArgValue, +): string => { + if (argValue === null) return String(null) // todo could check if index agrees is nullable. - const { $ } = ss + const schemaArgType = readMaybeThunk(schemaArgTypeMaybeThunk) - let args = `` - if ($ !== undefined) { - const schemaArgs = schemaField.args - if (!schemaArgs) throw new Error(`Field has no args`) - - const entries = Object.entries($) - args = entries.length === 0 ? `` : `(${ - entries.map(([argName, v]) => { - const schemaArg = schemaArgs.fields[argName] as Schema.Input.Any | undefined // eslint-disable-line - if (!schemaArg) throw new Error(`Arg ${argName} not found in schema field`) - if (schemaArg.kind === `Enum`) { - return `${argName}: ${String(v)}` - } else { - // todo if enum, do not quote, requires schema index - return `${argName}: ${JSON.stringify(v)}` - } - }).join(`, `) - })` + switch (schemaArgType.kind) { + case `nullable`: + return resolveArgValue(context, schemaArgType.type, argValue) + case `list`: { + assertArray(argValue) + const value = argValue.map(_ => resolveArgValue(context, schemaArgType.type, _ as ArgValue)) + return `[${value.join(`, `)}]` + } + case `InputObject`: { + assertObject(argValue) + const entries = Object.entries(argValue).map(([argName, argValue]) => { + const schemaArgField = schemaArgType.fields[argName] as Schema.Input.Field | undefined + if (!schemaArgField) throw new Error(`Arg not found: ${argName}`) + return [argName, resolveArgValue(context, schemaArgField.type, argValue)] + }) + return `{ ${entries.map(([k, v]) => `${k!}: ${v!}`).join(`, `)} }` + } + case `Enum`: { + return String(argValue) + } + case `Scalar`: { + // @ts-expect-error fixme + return JSON.stringify(schemaArgType.codec.encode(argValue)) + } + default: + throw new Error(`Unsupported arg kind: ${JSON.stringify(schemaArgType)}`) } +} - return args +const resolveArgs = (context: Context, schemaField: Schema.SomeField, ss: Indicator | SS) => { + if (isIndicator(ss)) return `` + + const { $ } = ss + if ($ === undefined) return `` + + const schemaArgs = schemaField.args + if (!schemaArgs) throw new Error(`Field has no args`) + + const argEntries = Object.entries($) + if (argEntries.length === 0) return `` + + return `(${ + argEntries.map(([argFieldName, v]) => { + const schemaArgField = schemaArgs.fields[argFieldName] as Schema.Input.Any | undefined // eslint-disable-line + if (!schemaArgField) throw new Error(`Arg field ${argFieldName} not found in schema.`) + const valueEncoded = resolveArgValue(context, schemaArgField, v) + return `${argFieldName}: ${valueEncoded}` + }).join(`, `) + })` } const pruneNonSelections = (ss: SS) => { const entries = Object.entries(ss) @@ -129,7 +168,7 @@ const resolveFieldValue = ( const entries = Object.entries(fieldValue) const directives = resolveDirectives(fieldValue) - const args = resolveArgs(schemaField, fieldValue) + const args = resolveArgs(context, schemaField, fieldValue) const selects = entries.filter(_ => isSelectFieldName(_[0])) if (selects.length === 0) { diff --git a/src/layers/5_client/customScalars.ts b/src/layers/4_ResultSet/customScalars.ts similarity index 51% rename from src/layers/5_client/customScalars.ts rename to src/layers/4_ResultSet/customScalars.ts index e90b7e4ee..782835b27 100644 --- a/src/layers/5_client/customScalars.ts +++ b/src/layers/4_ResultSet/customScalars.ts @@ -4,84 +4,9 @@ import { assertArray, mapValues } from '../../lib/prelude.js' import type { Object$2, Schema } from '../1_Schema/__.js' import { Output } from '../1_Schema/__.js' import { readMaybeThunk } from '../1_Schema/core/helpers.js' -import type { SelectionSet } from '../3_SelectionSet/__.js' -import type { GraphQLObjectSelection } from '../3_SelectionSet/encode.js' -import type { Args } from '../3_SelectionSet/types.js' import type { GraphQLObject } from '../4_ResultSet/runtime.js' import { assertGraphQLObject } from '../4_ResultSet/runtime.js' -namespace SSValue { - export type Obj = { - $?: Args2 - } - export type Args2 = Record - export type Arg = boolean | Arg[] | { [key: string]: Arg } -} - -export const encode = ( - input: { - index: Schema.Object$2 - documentObject: SelectionSet.Print.GraphQLObjectSelection - }, -): GraphQLObjectSelection => { - return Object.fromEntries( - Object.entries(input.documentObject).map(([fieldName, fieldValue]) => { - if (typeof fieldValue === `object`) { - if (`$` in fieldValue) { - const field = input.index.fields[fieldName] - if (!field?.args) throw new Error(`Field has no args: ${fieldName}`) - if (!field) throw new Error(`Field not found: ${fieldName}`) // eslint-disable-line - // @ts-expect-error fixme - fieldValue.$ = encodeCustomScalarsArgs(field.args, fieldValue.$) - return [fieldName, fieldValue] - } - // todo test nested inputs case - return [fieldName, encode({ index: input.index, documentObject: fieldValue })] - } - return [fieldName, fieldValue] - }), - ) -} - -const encodeCustomScalarsArgs = (indexArgs: Args, valueArgs: SSValue.Args2): object => { - return Object.fromEntries( - Object.entries(valueArgs).map(([argName, argValue]) => { - // @ts-expect-error fixme - const indexArg = indexArgs.fields[argName] // eslint-disable-line - if (!indexArg) throw new Error(`Arg not found: ${argName}`) - return [argName, encodeCustomScalarsArgValue(indexArg, argValue)] - }), - ) -} - -const encodeCustomScalarsArgValue = (indexArgMaybeThunk: Schema.Input.Any, argValue: null | SSValue.Arg): any => { - if (argValue === null) return null // todo could check if index agrees is nullable. - const indexArg = readMaybeThunk(indexArgMaybeThunk) - switch (indexArg.kind) { - case `nullable`: - return encodeCustomScalarsArgValue(indexArg.type, argValue) - case `list`: { - if (!Array.isArray(argValue)) throw new Error(`Expected array. Got: ${String(argValue)}`) - return argValue.map(_ => encodeCustomScalarsArgValue(indexArg.type, _)) - } - case `InputObject`: { - // dprint-ignore - if (typeof argValue !== `object` || Array.isArray(argValue)) throw new Error(`Expected object. Got: ${String(argValue)}`) - const fields = Object.fromEntries(Object.entries(indexArg.fields).map(([k, v]) => [k, v.type])) // eslint-disable-line - return encodeCustomScalarsArgs({ fields }, argValue) - } - case `Enum`: { - return argValue - } - case `Scalar`: { - // @ts-expect-error fixme - return indexArg.codec.encode(argValue) - } - default: - throw new Error(`Unsupported arg kind: ${JSON.stringify(indexArg)}`) - } -} - export const decode = <$Data extends ExecutionResult['data']>(index: Schema.Object$2, data: $Data): $Data => { if (!data) return data return mapValues(data, (v, fieldName) => { diff --git a/src/layers/5_client/client.customScalar.test.ts b/src/layers/5_client/client.customScalar.test.ts index 88c20d402..f3db0aeb7 100644 --- a/src/layers/5_client/client.customScalar.test.ts +++ b/src/layers/5_client/client.customScalar.test.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -import { beforeEach, describe, expect, test } from 'vitest' +import { describe, expect, test } from 'vitest' import { db } from '../../../tests/_/db.js' import { $Index as schemaIndex } from '../../../tests/_/schema/generated/SchemaRuntime.js' import { setupMockServer } from '../../../tests/legacy/__helpers.js' @@ -71,75 +71,3 @@ describe(`output`, () => { }) }) }) - -describe(`input`, () => { - // needed to avoid runtime error but data ignored because we only test input below. - beforeEach(() => { - ctx.res({ body: { data: {} } }) - }) - const clientExpected = (expectedDocument: (document: any) => void) => { - const client = create({ - schema: ctx.url, - schemaIndex, - hooks: { - documentEncode: (input, run) => { - const result = run(input) - expectedDocument(result['query']) - return result - }, - }, - }) - return client - } - - test(`arg field`, async () => { - const client = clientExpected((doc) => expect(doc.dateArg.$.date).toEqual(date0Encoded)) - await client.query.$batch({ dateArg: { $: { date: db.date0 } } }) - }) - test('arg field in non-null', async () => { - const client = clientExpected((doc) => expect(doc.dateArgNonNull.$.date).toEqual(date0Encoded)) - await client.query.$batch({ dateArgNonNull: { $: { date: db.date0 } } }) - }) - test('arg field in list', async () => { - const client = clientExpected((doc) => expect(doc.dateArgList.$.date).toEqual([date0Encoded, date1Encoded])) - await client.query.$batch({ dateArgList: { $: { date: [db.date0, new Date(1)] } } }) - }) - test('arg field in list (null)', async () => { - const client = clientExpected((doc) => expect(doc.dateArgList.$.date).toEqual(null)) - await client.query.$batch({ dateArgList: { $: { date: null } } }) - }) - test('arg field in non-null list (with list)', async () => { - const client = clientExpected((doc) => expect(doc.dateArgNonNullList.$.date).toEqual([date0Encoded, date1Encoded])) - await client.query.$batch({ dateArgNonNullList: { $: { date: [db.date0, new Date(1)] } } }) - }) - test('arg field in non-null list (with null)', async () => { - const client = clientExpected((doc) => expect(doc.dateArgNonNullList.$.date).toEqual([null, date0Encoded])) - await client.query.$batch({ dateArgNonNullList: { $: { date: [null, db.date0] } } }) - }) - test('arg field in non-null list non-null', async () => { - const client = clientExpected((doc) => - expect(doc.dateArgNonNullListNonNull.$.date).toEqual([date0Encoded, date1Encoded]) - ) - await client.query.$batch({ dateArgNonNullListNonNull: { $: { date: [db.date0, new Date(1)] } } }) - }) - test(`input object field`, async () => { - const client = clientExpected((doc) => { - expect(doc.dateArgInputObject.$.input.dateRequired).toEqual(date0Encoded) - expect(doc.dateArgInputObject.$.input.date).toEqual(date1Encoded) - }) - await client.query.$batch({ - dateArgInputObject: { $: { input: { idRequired: '', dateRequired: db.date0, date: new Date(1) } } }, - }) - }) - test(`nested input object field`, async () => { - const client = clientExpected((doc) => { - expect(doc.InputObjectNested.$.input.InputObject.dateRequired).toEqual(date0Encoded) - expect(doc.InputObjectNested.$.input.InputObject.date).toEqual(date1Encoded) - }) - await client.query.$batch({ - InputObjectNested: { - $: { input: { InputObject: { idRequired: '', dateRequired: db.date0, date: new Date(1) } } }, - }, - }) - }) -}) diff --git a/src/layers/5_client/client.ts b/src/layers/5_client/client.ts index 3e5fa8eae..6777e2767 100644 --- a/src/layers/5_client/client.ts +++ b/src/layers/5_client/client.ts @@ -1,6 +1,5 @@ import type { ExecutionResult } from 'graphql' import { type DocumentNode, execute, graphql, type GraphQLSchema } from 'graphql' -import type { ExcludeUndefined } from 'type-fest/source/required-deep.js' import request from '../../entrypoints/main.js' import { Errors } from '../../lib/errors/__.js' import { type RootTypeName, rootTypeNameToOperationName, type Variables } from '../../lib/graphql.js' @@ -11,6 +10,7 @@ import { readMaybeThunk } from '../1_Schema/core/helpers.js' import type { GlobalRegistry } from '../2_generator/globalRegistry.js' import { SelectionSet } from '../3_SelectionSet/__.js' import type { Context, DocumentObject, GraphQLObjectSelection } from '../3_SelectionSet/encode.js' +import * as CustomScalars from '../4_ResultSet/customScalars.js' import type { ApplyInputDefaults, Config, @@ -18,7 +18,6 @@ import type { ReturnModeTypeBase, ReturnModeTypeSuccessData, } from './Config.js' -import * as CustomScalars from './customScalars.js' import type { DocumentFn } from './document.js' import { toDocumentString } from './document.js' import type { GetRootTypeMethods } from './RootTypeMethods.js' @@ -74,7 +73,7 @@ export const create = <$Input extends Input>( GlobalRegistry.GetSchemaIndexOptionally<$Input['name']>, ApplyInputDefaults<{ returnMode: $Input['returnMode'] }> > => { - const parentInput = input + // const parentInput = input /** * @remarks Without generation the type of returnMode can be `ReturnModeTypeBase` which leads * TS to think some errors below are invalid checks because of a non-present member. @@ -89,15 +88,15 @@ export const create = <$Input extends 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 runHookable = <$Name extends keyof ExcludeUndefined>( + // name: $Name, + // input: Parameters[$Name]>[0], + // fn: Parameters[$Name]>[1], + // ) => { + // return parentInput.hooks?.[name](input, fn) ?? fn(input) + // } - const executeDocumentString = async ( + const executeGraphQLDocument = async ( { document, variables, operationName }: { document: string | DocumentNode variables?: Variables @@ -144,23 +143,19 @@ export const create = <$Input extends Input>( const rootIndex = input.schemaIndex.Root[rootTypeName] if (!rootIndex) throw new Error(`Root type not found: ${rootTypeName}`) - // todo one encoding pass - const selectionEncoded = runHookable( - `documentEncode`, - // todo rename to rootObject - { rootIndex, documentObject: selection }, - ({ rootIndex, documentObject }) => CustomScalars.encode({ index: rootIndex, documentObject }), - ) + // todo turn inputs into variables const documentString = SelectionSet.Print.rootTypeSelectionSet( encodeContext, rootIndex, // @ts-expect-error fixme - selectionEncoded[rootTypeNameToOperationName[rootTypeName]], + selection[rootTypeNameToOperationName[rootTypeName]], ) - // console.log(documentString) // todo variables - const result = await executeDocumentString({ document: documentString }) - // if (result.errors && (result.errors.length > 0)) throw new AggregateError(result.errors) + const result = await executeGraphQLDocument({ document: documentString }) + // todo optimize + // 1. Generate a map of possible custom scalar paths (tree structure) + // 2. When traversing the result, skip keys that are not in the map + // todo rename Result.decode const dataDecoded = CustomScalars.decode(rootIndex, result.data) return { ...result, data: dataDecoded } } @@ -298,7 +293,7 @@ export const create = <$Input extends Input>( // @ts-expect-error ignoreme const client: Client = { raw: async (document: string | DocumentNode, variables?: Variables, operationName?: string) => { - return await executeDocumentString({ document, variables, operationName }) + return await executeGraphQLDocument({ document, variables, operationName }) }, document: (documentObject: DocumentObject) => { const run = async (operationName: string) => { @@ -319,7 +314,7 @@ export const create = <$Input extends Input>( // todo this does not support custom scalars const documentString = toDocumentString(encodeContext, documentObject) - const result = await executeDocumentString({ + const result = await executeGraphQLDocument({ document: documentString, operationName, // todo variables