Skip to content

Commit

Permalink
feat(extension-system): request result data types (#1295)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Dec 26, 2024
1 parent 0e5918c commit 05a9717
Show file tree
Hide file tree
Showing 22 changed files with 169 additions and 80 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
],
"repository": {
"type": "git",
"url": "https://github.com/graffle-js/graffle.git"
"url": "git+https://github.com/graffle-js/graffle.git"
},
"keywords": [
"graphql",
Expand Down
2 changes: 1 addition & 1 deletion src/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { requestMethodsProperties } from '../documentBuilder/requestMethods/requestMethods.js' // todo
import { requestMethodsProperties } from '../documentBuilder/requestMethods/requestMethods.js'
import type { Anyware } from '../lib/anyware/__.js'
import { proxyGet } from '../lib/prelude.js'
import type { TypeFunction } from '../lib/type-function/__.js'
Expand Down
6 changes: 3 additions & 3 deletions src/client/handleOutput.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { GraphQLError } from 'graphql'
import type { Simplify } from 'type-fest'
import type { SimplifyDeepExcept } from '../documentBuilder/Simplify.js'
import type { Extension } from '../entrypoints/extensionkit.js'
import type { Anyware } from '../lib/anyware/__.js'
import { Errors } from '../lib/errors/__.js'
Expand All @@ -17,6 +16,7 @@ import {
import type { RequestPipelineBase } from '../requestPipeline/RequestPipeline.js'
import type { Context } from '../types/context.js'
import type { GlobalRegistry } from '../types/GlobalRegistry/GlobalRegistry.js'
import type { RequestResult } from '../types/RequestResult.ts/__.js'
import {
type ErrorCategory,
isOutputTraditionalGraphQLOutput,
Expand Down Expand Up @@ -99,7 +99,7 @@ export type HandleOutputGraffleRootField<$Context extends Context, $Data extends
ExcludeNull<
HandleOutput<
$Context,
SimplifyDeepExcept<$Context['scalars']['typesDecoded'], $Data>
RequestResult.Simplify<$Context, $Data>
>
>,
$RootFieldName
Expand All @@ -117,7 +117,7 @@ export type HandleOutput<$Context extends Context, $Data extends SomeObjectData>
$Context,
Envelope<
$Context,
SimplifyDeepExcept<$Context['scalars']['typesDecoded'], $Data>
RequestResult.Simplify<$Context, $Data>
>
>

Expand Down
16 changes: 13 additions & 3 deletions src/client/properties/scalar.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type Context } from '../../types/context.js'
import type { GlobalRegistry } from '../../types/GlobalRegistry/GlobalRegistry.js'
import { Schema } from '../../types/Schema/__.js'
import type { GetDecoded, GetEncoded } from '../../types/Schema/nodes/Scalar/helpers.js'
import { type Client } from '../client.js'
import type { ExtensionChainableRegistry } from '../client.js'
import { createProperties } from '../helpers.js'
Expand Down Expand Up @@ -45,8 +44,8 @@ export type ScalarMethod<
{
[_ in keyof $Context]: _ extends 'scalars' ? {
map: $Context[_]['map'] & { [_ in $Scalar['name']]: $Scalar }
typesEncoded: $Context[_]['typesEncoded'] | GetEncoded<$Scalar>
typesDecoded: $Context[_]['typesDecoded'] | GetDecoded<$Scalar>
typesEncoded: $Context[_]['typesEncoded'] | Schema.Scalar.GetEncoded<$Scalar>
typesDecoded: $Context[_]['typesDecoded'] | Schema.Scalar.GetDecoded<$Scalar>
}
: $Context[_]
},
Expand All @@ -55,6 +54,17 @@ export type ScalarMethod<
>
}

// todo review if really needed for keeping type instance count low v
// We do not use this above to reduce type instance count
export type AddScalar<$Context extends Context, $Scalar extends Schema.Scalar> = {
[_ in keyof $Context]: _ extends 'scalars' ? {
map: $Context[_]['map'] & { [_ in $Scalar['name']]: $Scalar }
typesEncoded: $Context[_]['typesEncoded'] | Schema.Scalar.GetEncoded<$Scalar>
typesDecoded: $Context[_]['typesDecoded'] | Schema.Scalar.GetDecoded<$Scalar>
}
: $Context[_]
}

type Arguments = [Schema.Scalar] | [string, { decode: (value: string) => any; encode: (value: any) => string }]

export const scalarProperties = createProperties((builder, state) => {
Expand Down
12 changes: 2 additions & 10 deletions src/client/properties/use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type UseMethod<
out $Extension_ extends object,
out $ExtensionChainable_ extends ExtensionChainableRegistry,
> = <extension extends Extension>(extension: extension) => Client<
// @ts-expect-error fixme
UseReducer<$Context, extension>,
$Extension_,
// @ts-expect-error
Expand All @@ -23,7 +24,7 @@ export type UseReducer<
$Context extends Context,
$Extension extends Extension,
> =
AddTypeHooks<
Extension.AddTypeHooksFromExtension<
AddTransport<
AddExtension<
$Context,
Expand Down Expand Up @@ -95,15 +96,6 @@ type AddTransport<
$Extension['transport']
>

type AddTypeHooks<
$Context extends Context,
$Extension extends Extension,
> = ConfigManager.UpdateKeyWithAppendMany<
$Context,
'typeHookOnRequestResult',
$Extension['typeHooks']['onRequestResult']
>

type AddExtension<
$Context extends Context,
$Extension extends Extension,
Expand Down
4 changes: 2 additions & 2 deletions src/documentBuilder/InferResult/__.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import type { db } from '../../../tests/_/schemas/db.js'
import type { Schema } from '../../../tests/_/schemas/kitchen-sink/graffle/modules/schema.js'
import type * as SelectionSets from '../../../tests/_/schemas/kitchen-sink/graffle/modules/selection-sets.js'
import { assertEqual } from '../../lib/assert-equal.js'
import type { RequestResult } from '../../types/RequestResult.ts/__.js'
import type { Registry } from '../../types/Schema/nodes/Scalar/helpers.js'
import type { DocumentBuilder } from '../__.js'
import type { InferResult } from './__.js'

type $<$SelectionSet extends SelectionSets.Query> = DocumentBuilder.SimplifyDeep<
type $<$SelectionSet extends SelectionSets.Query> = RequestResult.SimplifyWithEmptyContext<
InferResult.OperationQuery<$SelectionSet, Schema>
>

Expand Down
20 changes: 0 additions & 20 deletions src/documentBuilder/Simplify.test-d.ts

This file was deleted.

18 changes: 0 additions & 18 deletions src/documentBuilder/Simplify.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/documentBuilder/_.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './Simplify.js'
export * from './InferResult/__.js'
1 change: 0 additions & 1 deletion src/entrypoints/utilities-for-generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export type { ConfigGetOutputError, HandleOutput, HandleOutputGraffleRootField }
export { useReducer } from '../client/properties/use.js'
export { type DocumentRunner } from '../documentBuilder/requestMethods/document.js'
export * from '../documentBuilder/Select/__.js'
export { type SimplifyDeep, type SimplifyDeepExcept } from '../documentBuilder/Simplify.js'
export { type AssertExtendsObject, type Exact, type ExactNonEmpty, type UnionExpanded } from '../lib/prelude.js'
export { TypeFunction } from '../lib/type-function/__.js'
export type { ClientTransports } from '../types/context.js'
Expand Down
27 changes: 27 additions & 0 deletions src/extension/TypeHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,29 @@ export interface TypeHooks {
* Note: There is no way to manipulate the whole document.
*/
onRequestDocumentRootType: OnRequestDocumentRootType[]
/**
* Type(s) that show up in request result data.
*
* The purpose of this type is to stop Graffle from
* "simplifying" (aka. "expanding") whatever type(s)
* is/are here.
*
* So for example, if this type were `Date` and type `Date`
* appeared in the request result data, then it would be left-as is.
*
* Multiple types can be specified with a union, for example: `IntrospectionQuery | Date`.
*/
requestResultDataTypes: unknown
}

export interface TypeHooksEmpty extends TypeHooks {
onRequestResult: []
onRequestDocumentRootType: []
requestResultDataTypes: never
}

export namespace States {
export type Empty = TypeHooksEmpty
}

export interface TypeHooksBuilderCallback<$TypeHooks extends TypeHooks> {
Expand All @@ -35,14 +53,23 @@ export interface TypeHooksBuilderCallback<$TypeHooks extends TypeHooks> {

export interface TypeHooksBuilder<$TypeHooks extends TypeHooks = TypeHooksEmpty> {
type: $TypeHooks
requestResultDataTypes: <$RequestResultDataTypes>() => TypeHooksBuilder<
{
requestResultDataTypes: $TypeHooks['requestResultDataTypes'] | $RequestResultDataTypes
onRequestResult: $TypeHooks['onRequestResult']
onRequestDocumentRootType: $TypeHooks['onRequestDocumentRootType']
}
>
onRequestResult: <$OnRequestResult extends OnRequestResult>() => TypeHooksBuilder<
{
requestResultDataTypes: $TypeHooks['requestResultDataTypes']
onRequestResult: [...$TypeHooks['onRequestResult'], $OnRequestResult]
onRequestDocumentRootType: $TypeHooks['onRequestDocumentRootType']
}
>
onRequestDocumentRootType: <$OnRequestDocumentRootType extends OnRequestDocumentRootType>() => TypeHooksBuilder<
{
requestResultDataTypes: $TypeHooks['requestResultDataTypes']
onRequestResult: $TypeHooks['onRequestResult']
onRequestDocumentRootType: [...$TypeHooks['onRequestDocumentRootType'], $OnRequestDocumentRootType]
}
Expand Down
21 changes: 21 additions & 0 deletions src/extension/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { UnknownOrAnyToNever } from '../lib/prelude.js'
import type { Context, ContextTypeLevel } from '../types/context.js'
import type { Extension } from './__.js'

export type AddTypeHooksFromExtension<
$Context extends Context,
$Extension extends Extension,
> = AddExtensionTypeHooks<$Context, $Extension['typeHooks']>

export type AddExtensionTypeHooks<
$Context extends Context,
$ExtensionTypeHooks extends Extension.TypeHooks.TypeHooks,
> =
& Omit<$Context, keyof ContextTypeLevel>
& {
typeHookOnRequestResult: [...$Context['typeHookOnRequestResult'], ...$ExtensionTypeHooks['onRequestResult']]
typeHookOnRequestDocumentRootType: $Context['typeHookOnRequestDocumentRootType']
typeHookRequestResultDataTypes:
| $Context['typeHookRequestResultDataTypes']
| UnknownOrAnyToNever<$ExtensionTypeHooks['requestResultDataTypes']>
}
11 changes: 3 additions & 8 deletions src/extension/extension.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expectTypeOf, test } from 'vitest'
import type { Extension } from './__.js'
import { create } from './extension.js'

describe(`constructor arguments`, () => {
Expand Down Expand Up @@ -67,13 +68,7 @@ test(`type hooks is empty by default`, () => {
return {}
},
})
expectTypeOf(Ex.info.typeHooks).toEqualTypeOf<{
onRequestResult: []
onRequestDocumentRootType: []
}>()
expectTypeOf(Ex.info.typeHooks).toEqualTypeOf<Extension.TypeHooks.States.Empty>()
const ex = Ex()
expectTypeOf(ex.typeHooks).toEqualTypeOf<{
onRequestResult: []
onRequestDocumentRootType: []
}>()
expectTypeOf(ex.typeHooks).toEqualTypeOf<Extension.TypeHooks.States.Empty>()
})
3 changes: 3 additions & 0 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BuilderExtension } from './builder.js'
import type { TypeHooks, TypeHooksEmpty } from './TypeHooks.js'
import type { TypeHooksBuilderCallback } from './TypeHooks.js'

export * from './context.js'
export * as TypeHooks from './TypeHooks.js'

export type ExtensionInputParameters =
Expand All @@ -28,6 +29,7 @@ export interface ExtensionDefinition {
) => Anyware.Overload.Builder
}

// todo: rename to "define"
export const create = <
$Name extends string,
$BuilderExtension extends BuilderExtension | undefined = BuilderExtension | undefined,
Expand Down Expand Up @@ -94,6 +96,7 @@ export const create = <
transport,
// todo: remove this from runtime, its JUST for types.
typeHooks: {
requestResultDataTypes: null,
onRequestDocumentRootType: [],
onRequestResult: [],
},
Expand Down
5 changes: 5 additions & 0 deletions src/extensions/Introspection/Introspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ export const Introspection = create({
const config = createConfig(input)
return config
},
// todo consider this API:
// create: ({ config, typeHooks }) => {
// return {
// typeHooks: typeHooks<{ ... }>,
create: ({ config }) => {
return {
typeHooks: $ => $.requestResultDataTypes<IntrospectionQuery>(),
builder: (builder) =>
builder<BuilderExtension>(({ path, property, client }) => {
if (!(path.length === 0 && property === `introspect`)) return
Expand Down
1 change: 1 addition & 0 deletions src/extensions/SchemaErrors/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface SchemaErrors extends Extension {
typeHooks: {
onRequestDocumentRootType: [OnRequestDocumentRootType_]
onRequestResult: [OnRequestResult_]
requestResultDataTypes: never
}
}

Expand Down
12 changes: 2 additions & 10 deletions src/lib/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,16 +456,6 @@ export type PickRequiredProperties<T extends object> = {

export type Negate<T extends boolean> = T extends true ? false : true

// export type SimplifyExceptError<T extends unknown> = ConditionalSimplify<T, Error>

// export type SimplifyExceptErrorUnion<T> = T extends any ? SimplifyExceptError<T> : never

// export type SimplifyUnion<T> = T extends any ? Simplify<T> : never

// export type SimplifyDeep<T> = ConditionalSimplifyDeep<T, Function | Iterable<unknown> | Date, object>

// export type SimplifyDeepUnion<T> = T extends any ? SimplifyDeep<T> : never

export type RequireProperties<O extends object, K extends keyof O> = Simplify<O & { [P in K]-?: O[P] }>

export const throwNull = <V>(value: V): Exclude<V, null> => {
Expand Down Expand Up @@ -786,3 +776,5 @@ export const identity = <value>(value: value): value => value
export type PartialOrUndefined<T> = {
[K in keyof T]?: T[K] | undefined
}

export type UnknownOrAnyToNever<T> = unknown extends T ? never : T
45 changes: 45 additions & 0 deletions src/types/RequestResult.ts/Simplify.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { AddScalar } from '../../client/properties/scalar.js'
import type { Extension } from '../../extension/__.js'
import { assertEqual } from '../../lib/assert-equal.js'
import type { Context } from '../context.js'
import type { Schema } from '../Schema/__.js'
import type { _SimplifyExcept, Simplify, SimplifyWithEmptyContext } from './Simplify.js'

// dprint-ignore
{
type DateScalar = Schema.Scalar<'Date', Date, string>
type CEmpty = Context.States.Empty
type CExt = Extension.AddExtensionTypeHooks<CEmpty, {
onRequestDocumentRootType: [],
onRequestResult: []
requestResultDataTypes: Text,
}>
type CScalar = AddScalar<Context.States.Empty, DateScalar>
type CExtAndScalar = AddScalar<CExt, DateScalar>

type _1 = Simplify<CEmpty , {x:Date|null}>
// @ts-expect-error
assertEqual<_1 , {x:Date|null}>()
type _2 = Simplify<CExt , {x:Text|null}>
assertEqual<_2 , {x:Text|null}>()
type _3 = Simplify<CScalar , {x:Date|null}>
assertEqual<_3 , {x:Date|null}>()
type _4 = Simplify<CExtAndScalar , {x:Date|Text|null}>
assertEqual<_4 , {x:Date|Text|null}>()



assertEqual<SimplifyWithEmptyContext<{x:1|null}> , {x:1|null}>()
assertEqual<SimplifyWithEmptyContext<null | {x:1}> , null | {x:1}>()
assertEqual<SimplifyWithEmptyContext<null | {x?:1}> , null | {x?:1}>()
assertEqual<SimplifyWithEmptyContext<null | {x?:1|null}> , null | {x?:1|null}>()

assertEqual<_SimplifyExcept<Date, null | Date> , null | Date>()
assertEqual<_SimplifyExcept<Date, {}> , {}>()
assertEqual<_SimplifyExcept<Date, { a: Date }> , { a: Date }>()
assertEqual<_SimplifyExcept<Date, { a: 1 }> , { a: 1 }>()
assertEqual<_SimplifyExcept<Date, { a: { b: Date } }> , { a: { b: Date } }>()
assertEqual<_SimplifyExcept<Date, { a: { b: Date } }> , { a: { b: Date } }>()
assertEqual<_SimplifyExcept<Date, { a: null | { b: Date } }> , { a: null | { b: Date } }>()

}
Loading

0 comments on commit 05a9717

Please sign in to comment.