Skip to content

Commit

Permalink
feat: scalar method (#1206)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Oct 21, 2024
1 parent 7854943 commit 371803e
Show file tree
Hide file tree
Showing 116 changed files with 11,350 additions and 4,339 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"serve:pokemon": "tsx tests/_/services/pokemonManual.ts",
"gen:graffle": "pnpm gen:graffle:tests && pnpm build && cd website && pnpm gen:graffle",
"gen:graffle:tests": "tsx tests/_/schemas/generate.ts && pnpm graffle --project src/extensions/SchemaErrors/tests/fixture",
"graffle": "tsx ./src/cli/generate.ts",
"graffle": "tsx ./src/generator/cli/generate.ts",
"gen:examples": "tsx scripts/generate-examples-derivatives/generate.ts && pnpm format",
"dev": "rm -rf dist && tsc --watch",
"format": "pnpm build:docs && dprint fmt",
Expand Down
2 changes: 1 addition & 1 deletion src/entrypoints/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { create as createSelect, select } from '../layers/5_select/select.js'
export { type Client, create } from '../layers/6_client/client.js'
export { createPrefilled, type InputPrefilled } from '../layers/6_client/prefilled.js'
export { createPrefilled } from '../layers/6_client/clientPrefilled.js'
export { type InputStatic } from '../layers/6_client/Settings/Input.js'
2 changes: 2 additions & 0 deletions src/entrypoints/utilities-for-generated.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export { type Simplify } from 'type-fest'
export { type SchemaDrivenDataMap } from '../extensions/CustomScalars/schemaDrivenDataMap/__.js'
export { type Schema as SchemaIndexBase } from '../generator/generators/Schema.js'
export { SchemaKit } from '../layers/1_Schema/__.js'
export * from '../layers/2_Select/__.js'
export { type ClientContext } from '../layers/6_client/fluent.js'
export type {
ConfigGetOutputError,
HandleOutput,
Expand Down
4 changes: 3 additions & 1 deletion src/extensions/CustomScalars/CustomScalars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ export const CustomScalars = () =>
// documentNode: true,
onRequest: (async ({ pack }) => {
const sddm = pack.input.state.config.schemaMap
const scalars = pack.input.state.scalars
if (!sddm) return pack()

const request = normalizeRequestToNode(pack.input.request)

// We will mutate query. Assign it back to input for it to be carried forward.
pack.input.request.query = request.query

encodeRequestVariables({ sddm, request })
encodeRequestVariables({ sddm, scalars, request })

const { exchange } = await pack()
const { unpack } = await exchange()
Expand All @@ -33,6 +34,7 @@ export const CustomScalars = () =>
sddm,
request,
data: decode.input.result.data,
scalars,
})
}

Expand Down
47 changes: 23 additions & 24 deletions src/extensions/CustomScalars/decode.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { describe, expect } from 'vitest'
import { Date } from '../../../tests/_/fixtures/scalars.js'
import { createResponse, test } from '../../../tests/_/helpers.js'
import { db } from '../../../tests/_/schemas/db.js'
import type { Graffle } from '../../../tests/_/schemas/kitchen-sink/graffle/__.js'
import { Select } from '../../layers/2_Select/__.js'
import { SelectionSetGraphqlMapper } from '../../layers/3_SelectGraphQLMapper/__.js'
import { Grafaid } from '../../lib/grafaid/__.js'

const date0Encoded = db.date0.toISOString()

type TestCase = [
describe: string,
query: Graffle.SelectionSets.Query,
query: Graffle.SelectionSets.Query<{ Date: typeof Date }>,
responseData: object,
expectedData: object,
]
Expand All @@ -22,7 +21,7 @@ const withBatch: TestCaseWith = [
{},
async ([_, query, responseData, expectedData], { fetch, kitchenSinkHttp: kitchenSink }) => {
fetch.mockResolvedValueOnce(createResponse({ data: responseData }))
expect(await kitchenSink.query.$batch(query)).toEqual(expectedData)
expect(await kitchenSink.scalar(Date).query.$batch(query)).toEqual(expectedData)
},
]

Expand All @@ -34,7 +33,7 @@ const withGqlDocument: TestCaseWith = [
const { document } = SelectionSetGraphqlMapper.toGraphQL(
Select.Document.createDocumentNormalizedFromQuerySelection(query as any),
)
expect(await kitchenSink.gql(document).send()).toEqual(expectedData)
expect(await kitchenSink.scalar(Date).gql(document).send()).toEqual(expectedData)
},
]

Expand All @@ -46,47 +45,47 @@ const withGqlString: TestCaseWith = [
const { document } = SelectionSetGraphqlMapper.toGraphQL(
Select.Document.normalizeOrThrow({ query: { foo: query as any } }),
)
expect(await kitchenSink.gql(Grafaid.Document.print(document)).send()).toEqual(expectedData)
expect(await kitchenSink.scalar(Date).gql(Grafaid.Document.print(document)).send()).toEqual(expectedData)
},
]

// dprint-ignore
const testGeneralCases = test.for<TestCase>([
[`nullable null`, { date: true }, { date: null }, { date: null }],
[`nullable value`, { date: true }, { date: date0Encoded }, { date: db.date0 }],
[`non-null`, { dateNonNull: true }, { dateNonNull: date0Encoded }, { dateNonNull: db.date0 }],
[`list`, { dateList: true }, { dateList: [0, 1] }, { dateList: [db.date0, new Date(1)] }],
[`list list`, { dateListList: true }, { dateListList: [[0, 1],[0,1]] }, { dateListList: [[db.date0, new Date(1)],[db.date0, new Date(1)]] }],
[`list non-null`, { dateListNonNull: true }, { dateListNonNull: [0, 1] }, { dateListNonNull: [db.date0, new Date(1)] }],
[`object field`, { dateObject1: { date1: true } }, { dateObject1: { date1: date0Encoded } }, { dateObject1: { date1: db.date0 } }],
[`interface field`, { dateInterface1: { date1: true } }, { dateInterface1: { date1: date0Encoded } }, { dateInterface1: { date1: db.date0 } }],
[`interface inline fragment`, { dateInterface1: { ___on_DateObject1: { date1: true } } }, { dateInterface1: { date1: date0Encoded } }, { dateInterface1: { date1: db.date0 } }],
[`nullable null`, { date: true }, { date: null }, { date: null }],
[`nullable value`, { date: true }, { date: db.date0Encoded }, { date: db.date0 }],
[`non-null`, { dateNonNull: true }, { dateNonNull: db.date0Encoded }, { dateNonNull: db.date0 }],
[`list`, { dateList: true }, { dateList: [0, 1] }, { dateList: [db.date0, db.date1] }],
[`list list`, { dateListList: true }, { dateListList: [[0, 1],[0,1]] }, { dateListList: [[db.date0, db.date1],[db.date0, db.date1]] }],
[`list non-null`, { dateListNonNull: true }, { dateListNonNull: [0, 1] }, { dateListNonNull: [db.date0, db.date1] }],
[`object field`, { dateObject1: { date1: true } }, { dateObject1: { date1: db.date0Encoded } }, { dateObject1: { date1: db.date0 } }],
[`interface field`, { dateInterface1: { date1: true } }, { dateInterface1: { date1: db.date0Encoded } }, { dateInterface1: { date1: db.date0 } }],
[`interface inline fragment`, { dateInterface1: { ___on_DateObject1: { date1: true } } }, { dateInterface1: { date1: db.date0Encoded } }, { dateInterface1: { date1: db.date0 } }],
])

// dprint-ignore
const testAliasCases = test.for<TestCase>([
[`alias`, { date: [`x`, true] }, { x: date0Encoded }, { x: db.date0 }],
[`interface inline fragment alias`, { dateInterface1: { ___on_DateObject1: { date1: [`x`, true] } } }, { dateInterface1: { x: date0Encoded }}, { dateInterface1: { x: db.date0 }}],
[`interface inline fragment nested alias`, { dateInterface1: { ___on_DateObject1: { ___: { date1: [`x`, true] } } } }, { dateInterface1: { x: date0Encoded }}, { dateInterface1: { x: db.date0 }}],
[`inline fragment interface alias`, { ___: { dateInterface1: { ___on_DateObject1: { date1: [`x`, true] } } } }, { dateInterface1: { x: date0Encoded }}, { dateInterface1: { x: db.date0 }}],
[`inline fragment x2 interface alias & nullable value`, { ___: [{ dateInterface1: { ___on_DateObject1: { date1: [`x`, true] } } }, {date: [`y`,true]}] }, { dateInterface1: { x: date0Encoded }, y: date0Encoded }, { dateInterface1: { x: db.date0 }, y: db.date0 }],
[`alias`, { date: [`x`, true] }, { x: db.date0Encoded }, { x: db.date0 }],
[`interface inline fragment alias`, { dateInterface1: { ___on_DateObject1: { date1: [`x`, true] } } }, { dateInterface1: { x: db.date0Encoded }}, { dateInterface1: { x: db.date0 }}],
[`interface inline fragment nested alias`, { dateInterface1: { ___on_DateObject1: { ___: { date1: [`x`, true] } } } }, { dateInterface1: { x: db.date0Encoded }}, { dateInterface1: { x: db.date0 }}],
[`inline fragment interface alias`, { ___: { dateInterface1: { ___on_DateObject1: { date1: [`x`, true] } } } }, { dateInterface1: { x: db.date0Encoded }}, { dateInterface1: { x: db.date0 }}],
[`inline fragment x2 interface alias & nullable value`, { ___: [{ dateInterface1: { ___on_DateObject1: { date1: [`x`, true] } } }, {date: [`y`,true]}] }, { dateInterface1: { x: db.date0Encoded }, y: db.date0Encoded }, { dateInterface1: { x: db.date0 }, y: db.date0 }],
])

// dprint-ignore
const testUnionCases = test.for<TestCase>([
[`case 1with __typename`,
{ dateUnion: { __typename: true, ___on_DateObject1: { date1: true } } },
{ dateUnion: { __typename: `DateObject1`, date1: date0Encoded } },
{ dateUnion: { __typename: `DateObject1`, date1: db.date0Encoded } },
{ dateUnion: { __typename: `DateObject1`, date1: db.date0 }}
],
[`case 1 without __typename`,
{ dateUnion: { ___on_DateObject1: { date1: true } } },
{ dateUnion: { date1: date0Encoded } },
{ dateUnion: { date1: db.date0Encoded } },
{ dateUnion: { date1: db.date0 } }
],
[`case 2`,
{ dateUnion: { ___on_DateObject1: { date1: true }, ___on_DateObject2: { date2: true } } },
{ dateUnion: { date2: date0Encoded } },
{ dateUnion: { date2: db.date0Encoded } },
{ dateUnion: { date2: db.date0 } }
],
[`case 2 miss`,
Expand All @@ -103,7 +102,7 @@ describe(`$batch`, () => {
describe(`object field in union`, () => {
testUnionCases(`%s`, async ([_, query, responseData, expectedData], { fetch, kitchenSinkHttp: kitchenSink }) => {
fetch.mockResolvedValueOnce(createResponse({ data: responseData }))
expect(await kitchenSink.query.$batch(query)).toEqual(expectedData)
expect(await kitchenSink.scalar(Date).query.$batch(query)).toEqual(expectedData)
})
})
})
Expand Down
17 changes: 14 additions & 3 deletions src/extensions/CustomScalars/decode.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Kind } from 'graphql'
import { SchemaKit } from '../../entrypoints/schema.js'
import { applyCodec } from '../../layers/1_Schema/Hybrid/types/Scalar/Scalar.js'
import type { RegisteredScalars } from '../../layers/6_client/fluent.js'
import type { Grafaid } from '../../lib/grafaid/__.js'
import { SchemaDrivenDataMap } from './schemaDrivenDataMap/__.js'

/**
* If a document is given then aliases will be decoded as well.
*/
export const decodeResultData = ({ request, data, sddm }: {
export const decodeResultData = ({ request, data, sddm, scalars }: {
/**
* Result data to decode.
*/
Expand All @@ -19,6 +21,10 @@ export const decodeResultData = ({ request, data, sddm }: {
* Request is used to traverse aliases if any were used.
*/
request: Grafaid.RequestAnalyzedDocumentNodeInput
/**
* Registered custom scalars.
*/
scalars: RegisteredScalars
}) => {
const sddmOutputObject = sddm.roots[request.rootType]
if (!sddmOutputObject) return
Expand All @@ -27,15 +33,17 @@ export const decodeResultData = ({ request, data, sddm }: {
data,
sddmOutputObject,
documentPart: request.operation.selectionSet,
scalars,
})
}

const decodeResultData_ = (input: {
data: Grafaid.SomeObjectData | null | undefined
sddmOutputObject: SchemaDrivenDataMap.OutputObject
documentPart: null | Grafaid.Document.SelectionSetNode
scalars: RegisteredScalars
}): void => {
const { data, sddmOutputObject, documentPart } = input
const { data, sddmOutputObject, documentPart, scalars } = input
if (!data) return

for (const [k, v] of Object.entries(data)) {
Expand All @@ -51,14 +59,17 @@ const decodeResultData_ = (input: {

const sddmNode = sddmOutputField.nt

// console.log(sddmNode)
if (SchemaDrivenDataMap.isScalar(sddmNode)) {
data[k] = applyCodec(sddmNode.codec.decode, v)
} else if (SchemaDrivenDataMap.isCustomScalarName(sddmNode)) {
const scalar = SchemaKit.Scalar.lookupCustomScalarOrFallbackToString(scalars, sddmNode)
data[k] = applyCodec(scalar.codec.decode, v)
} else if (SchemaDrivenDataMap.isOutputObject(sddmNode)) {
decodeResultData_({
data: v,
sddmOutputObject: sddmNode,
documentPart: documentField?.selectionSet ?? null,
scalars,
})
} else {
// enums not decoded.
Expand Down
25 changes: 12 additions & 13 deletions src/extensions/CustomScalars/encode.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect } from 'vitest'
import { Date } from '../../../tests/_/fixtures/scalars.js'
import { test } from '../../../tests/_/helpers.js'
import { db } from '../../../tests/_/schemas/db.js'
import type { Graffle } from '../../../tests/_/schemas/kitchen-sink/graffle/__.js'
Expand All @@ -8,31 +9,29 @@ import { Select } from '../../layers/2_Select/__.js'
import { SelectionSetGraphqlMapper } from '../../layers/3_SelectGraphQLMapper/__.js'
import { Grafaid } from '../../lib/grafaid/__.js'

const date0Encoded = db.date0.toISOString()
const date1Encoded = db.date1.toISOString()

type TestCase = [
description: string,
query: Graffle.SelectionSets.Query,
query: Graffle.SelectionSets.Query<{ Date: typeof Date }>,
expectedVariables: object,
]

// todo test variable to a directive.
// dprint-ignore
const testCases = test.for<TestCase>([
[`arg enum` , { stringWithArgEnum: { $: { $ABCEnum: `A` } } } , { ABCEnum: `A` }],
[`arg field` , { dateArg: { $: { date: db.date0 } } } , { date: date0Encoded }],
[`arg field in non-null` , { dateArgNonNull: { $: { date: db.date0 } } } , { date: date0Encoded }],
[`arg field in list` , { dateArgList: { $: { date: [db.date0, db.date1] } } } , { date: [date0Encoded, date1Encoded] }],
[`arg field` , { dateArg: { $: { date: db.date0 } } } , { date: db.date0Encoded }],
[`arg field in non-null` , { dateArgNonNull: { $: { date: db.date0 } } } , { date: db.date0Encoded }],
[`arg field in list` , { dateArgList: { $: { date: [db.date0, db.date1] } } } , { date: [db.date0Encoded, db.date1Encoded] }],
[`arg field in list (null)` , { dateArgList: { $: { date: null } } } , { date: null } ],
[`arg field in non-null list (with list)` , { dateArgNonNullList: { $: { date: [db.date0, db.date1] } } } , { date: [date0Encoded, date1Encoded] }],
[`arg field in non-null list (with null)` , { dateArgNonNullList: { $: { date: [null, db.date0] } } } , { date: [null, date0Encoded] }],
[`arg field in non-null list non-null` , { dateArgNonNullListNonNull: { $: { date: [db.date0, db.date1] } } } , { date: [date0Encoded, date1Encoded] }],
[`input object field` , { dateArgInputObject: { $: { input: { idRequired: ``, dateRequired: db.date0, date: db.date1 } } } } , { input: { idRequired: ``, dateRequired: date0Encoded, date: date1Encoded } }],
[`nested input object field` , { InputObjectNested: { $: { input: { InputObject: { idRequired: ``, dateRequired: db.date0, date: db.date1 }}}}} , { input: { InputObject: { idRequired: ``, dateRequired: date0Encoded, date: date1Encoded } } }],
[`arg field in non-null list (with list)` , { dateArgNonNullList: { $: { date: [db.date0, db.date1] } } } , { date: [db.date0Encoded, db.date1Encoded] }],
[`arg field in non-null list (with null)` , { dateArgNonNullList: { $: { date: [null, db.date0] } } } , { date: [null, db.date0Encoded] }],
[`arg field in non-null list non-null` , { dateArgNonNullListNonNull: { $: { date: [db.date0, db.date1] } } } , { date: [db.date0Encoded, db.date1Encoded] }],
[`input object field` , { dateArgInputObject: { $: { input: { idRequired: ``, dateRequired: db.date0, date: db.date1 } } } } , { input: { idRequired: ``, dateRequired: db.date0Encoded, date: db.date1Encoded } }],
[`nested input object field` , { InputObjectNested: { $: { input: { InputObject: { idRequired: ``, dateRequired: db.date0, date: db.date1 }}}}} , { input: { InputObject: { idRequired: ``, dateRequired: db.date0Encoded, date: db.date1Encoded } } }],
])

testCases(`%s`, async ([_, query, expectedVariables], { kitchenSink }) => {
const g = kitchenSink.use(Spy()).scalar(Date)
const { document, operationsVariables } = SelectionSetGraphqlMapper.toGraphQL(
Select.Document.createDocumentNormalizedFromQuerySelection(query as any),
{
Expand All @@ -41,6 +40,6 @@ testCases(`%s`, async ([_, query, expectedVariables], { kitchenSink }) => {
},
)
const documentString = Grafaid.Document.print(document)
await kitchenSink.use(Spy()).gql(documentString).send(operationsVariables[`$default`])
await g.gql(documentString).send(operationsVariables[`$default`])
expect(Spy.data.pack.input?.request.variables).toEqual(expectedVariables)
})
Loading

0 comments on commit 371803e

Please sign in to comment.