Skip to content

Commit

Permalink
feat: lift inputs to operation variables (#1151)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Oct 1, 2024
1 parent 3895cd0 commit ade5a3e
Show file tree
Hide file tree
Showing 170 changed files with 4,040 additions and 3,169 deletions.
2 changes: 1 addition & 1 deletion src/entrypoints/_Print.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { print as document } from '../layers/4_document/print.js'
export { toGraphQLDocument as document } from '../layers/3_SelectionSetGraphqlMapper/nodes/Document.js'
1 change: 0 additions & 1 deletion src/entrypoints/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ export { type WithInput } from '../layers/6_client/Settings/inputIncrementable/i
export * from '../lib/prelude.js'
export * from './__Graffle.js'
export * as Preset from './_Preset.js'
export * as Print from './_Print.js'
2 changes: 1 addition & 1 deletion src/entrypoints/schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from '../layers/1_Schema/__.js'
export { SelectionSet } from '../layers/2_SelectionSet/__.js'
export { Select } from '../layers/2_Select/__.js'
export { ResultSet } from '../layers/3_ResultSet/__.js'
2 changes: 1 addition & 1 deletion src/entrypoints/utilities-for-generated.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { type Simplify } from 'type-fest'
export * from '../layers/2_SelectionSet/__.js'
export * from '../layers/2_Select/__.js'
export type {
ConfigGetOutputError,
ResolveOutputReturnRootField,
Expand Down
2 changes: 1 addition & 1 deletion src/layers/0_functions/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { DocumentTypeDecoration } from '@graphql-typed-document-node/core'
import type { DocumentNode, TypedQueryDocumentNode } from 'graphql'
import type { HasRequiredKeys, IsEmptyObject } from 'type-fest'
import type { SomeData, StandardScalarVariables } from '../../lib/graphql.js'
import type { SomeData, StandardScalarVariables } from '../../lib/graphql-plus/graphql.js'
import type { Negate } from '../../lib/prelude.js'

export type DocumentInput<$Data extends SomeData = SomeData, V = any> =
Expand Down
34 changes: 34 additions & 0 deletions src/layers/1_Schema/Directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Hybrid } from './Hybrid/__.js'

export interface Directive {
name: string
arguments: Record<string, {
name: string
type: Hybrid.Scalar.$Any
}>
}

export const IncludeDirective: Directive = {
name: `include`,
arguments: {
if: {
name: `if`,
type: Hybrid.Scalar.Boolean,
},
},
}

export const SkipDirective: Directive = {
name: `skip`,
arguments: {
if: {
name: `if`,
type: Hybrid.Scalar.Boolean,
},
},
}

export const standardDirectivesByName = {
include: IncludeDirective,
skip: SkipDirective,
}
1 change: 1 addition & 0 deletions src/layers/1_Schema/Hybrid/_.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './typeGroups.js'
export * from './types/Enum.js'
export * from './types/Scalar/__.js'
3 changes: 3 additions & 0 deletions src/layers/1_Schema/Hybrid/typeGroups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { Enum, Scalar } from './_.js'

export type $Any = Enum | Scalar.$Any
2 changes: 1 addition & 1 deletion src/layers/1_Schema/Hybrid/types/Scalar/Scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const Scalars = {
}

// todo this mixes scalars from different schemas
export type Any =
export type $Any =
| String
| Int
| Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Input } from './_.js'
import type { Input } from './__.js'

type InputFields = Record<string, any>

Expand Down
6 changes: 4 additions & 2 deletions src/layers/1_Schema/Input/typeGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { InputObject } from './types/InputObject.js'
import type { List } from './types/List.js'
import type { Nullable } from './types/Nullable.js'

export type Named = Hybrid.Enum | Hybrid.Scalar.Any | InputObject
export type Named = Hybrid.Enum | Hybrid.Scalar.$Any | InputObject

export type Any = List<any> | Nullable<any> | Named
export type Any = AnyExceptNull | Nullable<any>

export type AnyExceptNull = List<any> | Named
8 changes: 7 additions & 1 deletion src/layers/1_Schema/Output/typeGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import type { Nullable } from './types/Nullable.js'
import type { Object$2 } from './types/Object.js'
import type { Union } from './types/Union.js'

export type Named = Interface | Enum | Object$2 | Union<string, [Object$2, ...Object$2[]]> | Hybrid.Scalar.Any
export type ObjectLike = Object$2 | Interface

export const isObjectLike = (type: Any): type is ObjectLike => {
return type.kind === `Object` || type.kind === `Interface` || type.kind === `Union`
}

export type Named = Interface | Enum | Object$2 | Union<string, [Object$2, ...Object$2[]]> | Hybrid.Scalar.$Any

export type Unnamed = List<any> | Nullable<any>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
import type { Args } from './Args.js'
import type { MaybeThunk } from './core/helpers.js'
import type { Hybrid } from './Hybrid/__.js'
import type { Output } from './Output/__.js'
import type { MaybeThunk } from '../../core/helpers.js'
import type { Hybrid } from '../../Hybrid/__.js'
import type { Args } from '../../Input/Args.js'
import type { Output } from '../__.js'

export type Field<$Type extends Output.Any, $Args extends Args<any> | null> = {
export type Field<$Name extends string, $Type extends Output.Any, $Args extends Args<any> | null> = {
// todo when generating schema keep track of the unwrapped type too to avoid IDE runtime cost to calcualte it
// typeUnwrapped: $NamedType
type: $Type
name: $Name
args: $Args
}

export const field = <$Type extends Output.Any, $Args extends null | Args<any> = null>(
export const field = <$Name extends string, $Type extends Output.Any, $Args extends null | Args<any> = null>(
name: $Name,
type: MaybeThunk<$Type>,
args: $Args = null as $Args,
): Field<$Type, $Args> => {
): Field<$Name, $Type, $Args> => {
return {
// At type level "type" is not a thunk
type: type as any,
name,
args,
}
}

type FieldType =
| Hybrid.Enum
| Hybrid.Scalar.Any
| Hybrid.Scalar.$Any
// | Output.__typename
| Output.List<any>
| Output.Nullable<any>
| Output.Object$2<string, any>
| Output.Union<string, [any, ...any[]]>
| Output.Interface<string, Record<string, Field<any, Args<any> | null>>, [any, ...any[]]>
| Output.Interface<string, Record<string, Field<string, any, Args<any> | null>>, [any, ...any[]]>

// todo test non null interface fields
export type SomeField = Field<FieldType, Args<any> | null>
export type SomeField = Field<string, FieldType, Args<any> | null>

export type SomeFields<$Keys extends SomeKey = SomeKey> = Record<$Keys, SomeField>

Expand Down
25 changes: 10 additions & 15 deletions src/layers/1_Schema/Output/types/Object.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { RootTypeNameMutation, RootTypeNameQuery, RootTypeNameSubscription } from '../../../../lib/graphql.js'
import type { Field, SomeFields } from '../../Field.js'
import { field } from '../../Field.js'
import type { Hybrid } from '../../Hybrid/__.js'
import type { UnwrapToNamed } from '../typeGroups.js'
import type {
RootTypeNameMutation,
RootTypeNameQuery,
RootTypeNameSubscription,
} from '../../../../lib/graphql-plus/graphql.js'
import { __typename } from './__typename.js'
import type { Field, SomeFields } from './Field.js'
import { field } from './Field.js'

export interface ObjectQuery<
$Fields extends SomeFields = SomeFields,
Expand All @@ -25,29 +27,22 @@ export interface Object$2<
> {
kind: 'Object'
fields: {
__typename: Field<__typename<$Name>, null>
__typename: Field<'__typename', __typename<$Name>, null>
} & $Fields
}

// Naming this "Object" breaks Vitest: https://github.com/vitest-dev/vitest/issues/5463
export const Object$ = <$Name extends string, $Fields extends Record<keyof $Fields, Field<any, any>>>(
export const Object$ = <$Name extends string, $Fields extends Record<keyof $Fields, Field<any, any, any>>>(
name: $Name,
fields: $Fields,
// eslint-disable-next-line
// @ts-ignore infinite depth issue
): Object$2<$Name, $Fields> => ({
kind: `Object`,
fields: {
__typename: field(__typename(name)),
__typename: field(`__typename`, __typename(name)),
...fields,
},
})

export { Object$ as Object }

// dprint-ignore
export type PickScalarFields<$Object extends Object$2> = {
[
$Key in keyof $Object['fields'] as UnwrapToNamed<$Object['fields'][$Key]['type']> extends Hybrid.Scalar.Any | __typename ? $Key : never
]: $Object['fields'][$Key]
}
8 changes: 5 additions & 3 deletions src/layers/1_Schema/_.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
export * from './Args.js'
export { type RootTypeName } from './core/helpers.js'
export { readMaybeThunk, type RootTypeName } from './core/helpers.js'
export * from './core/Index.js'
export * from './core/Named/__.js'
export * from './Field.js'
export * as Directives from './Directives.js'
export * from './Hybrid/__.js'
export * from './Hybrid/types/Enum.js'
export * from './Hybrid/types/Scalar/__.js'
export * from './Input/__.js'
export * from './Input/Args.js'
export * from './Input/types/InputObject.js'
export * from './Output/__.js'
export * from './Output/types/__typename.js'
export * from './Output/types/Field.js'
export * from './Output/types/Interface.js'
export { Object$, type Object$2 } from './Output/types/Object.js'
export * from './Output/types/Union.js'
3 changes: 2 additions & 1 deletion src/layers/1_Schema/core/Index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { RootTypeName } from '../../../lib/graphql.js'
import type { RootTypeName } from '../../../lib/graphql-plus/graphql.js'
import type { GlobalRegistry } from '../../4_generator/globalRegistry.js'
import type { Hybrid } from '../Hybrid/__.js'
import type { Output } from '../Output/__.js'

// todo move this type to the generator module
/**
* A generic schema index type. Any particular schema index will be a subtype of this, with
* additional specificity such as on objects where here `Record` is used.
Expand Down
115 changes: 115 additions & 0 deletions src/layers/2_Select/$parseSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { SelectionSet } from './_.js'
import { Arguments, Directive, Indicator, InlineFragment, SelectAlias, SelectScalarsWildcard } from './_.js'

export type ParsedSelection =
| {
type: 'Arguments'
arguments: Record<string, any>
}
| {
/**
* When undefined is passed to a directive.
*/
type: 'DirectiveNoop'
}
| {
type: 'Directive'
name: string
argumentsInput: unknown
arguments: Record<string, any>
}
| {
type: 'InlineFragment'
typeCondition: string | null
selectionSets: SelectionSet.AnySelectionSet[]
}
| {
type: 'ScalarsWildcard'
}
| {
type: 'SelectionSet'
name: string
selectionSet: SelectionSet.AnySelectionSet
}
| {
type: 'Indicator'
name: string
select: boolean
}
| {
type: 'Alias'
name: string
aliases: SelectAlias.SelectAliasMultiple
}

export const parseSelection = (key: string, value: any): ParsedSelection => {
if (key === Arguments.key) {
return {
type: `Arguments`,
arguments: value,
}
}

const directiveName = Directive.parseKey(key)
if (directiveName) {
// @ts-expect-error fixme
const directiveDef = Directive.definitionsByName[directiveName]
if (!directiveDef) {
throw new Error(`Unknown directive ${key}.`)
}
if (value === undefined) {
return {
type: `DirectiveNoop`,
}
}
return {
type: `Directive`,
name: directiveName,
argumentsInput: value,
arguments: directiveDef.normalizeArguments(value),
}
}

if (key === SelectScalarsWildcard.key) {
return {
type: `ScalarsWildcard`,
}
}

if (Indicator.isIndicator(value)) {
return {
type: `Indicator`,
name: key,
select: Indicator.isPositiveIndicator(value),
}
}

const inlineFragment = InlineFragment.parseKey(key)
if (inlineFragment) {
const selectionSets = InlineFragment.normalizeInlineFragment(value)
return {
type: `InlineFragment`,
typeCondition: inlineFragment.typeCondition,
selectionSets,
}
}

// Parse alias after inline fragment because both can have array values but the keys are distinctive
if (SelectAlias.isSelectAlias(value)) {
return {
type: `Alias`,
name: key,
aliases: SelectAlias.normalizeSelectAlias(value),
}
}

if (typeof value === `object` && value !== null) {
return {
type: `SelectionSet`,
name: key,
selectionSet: value,
}
}

throw new Error(`Unknown selection at key ${key}.`)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Include } from './_include.js'
import { Skip } from './_skip.js'
import type { Schema } from '../../1_Schema/__.js'
import type { Include, Skip } from './_.js'

export interface DirectiveDefinition {
export interface Definition {
name: string
create: (...args: any[]) => DirectiveLike
type: Schema.Directives.Directive
normalizeArguments: (args: any) => Record<string, any>
}

export interface DirectiveLike {
name: string
args: object
arguments: Record<string, any>
}

/**
Expand All @@ -21,8 +22,3 @@ export namespace $Groups {
export interface Fields extends Include.Field, Skip.Field {}
}
}

export const fieldToDef = {
$include: Include,
$skip: Skip,
} satisfies Record<keyof $Fields, DirectiveDefinition>
4 changes: 4 additions & 0 deletions src/layers/2_Select/Directive/_.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './$types.js'
export * from './helpers.js'
export * as Include from './include.js'
export * as Skip from './skip.js'
File renamed without changes.
Loading

0 comments on commit ade5a3e

Please sign in to comment.