diff --git a/package.json b/package.json index 0e7a3128d..c2329e094 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "homepage": "https://github.com/jasonkuhrt/graffle", "scripts": { "serve:pokemon": "tsx tests/_/services/pokemonManual.ts", - "gen:graffle": "tsx tests/_/schemas/generate.ts && pnpm build && cd website && pnpm gen:graffle", + "gen:graffle": "pnpm gen:graffle:tests && pnpm build && cd website && pnpm gen:graffle", + "gen:graffle:tests": "tsx tests/_/schemas/generate.ts", "graffle": "tsx ./src/cli/generate.ts", "gen:examples": "tsx scripts/generate-examples-derivatives/generate.ts && pnpm format", "dev": "rm -rf dist && tsc --watch", diff --git a/src/cli/generate.ts b/src/cli/generate.ts index e16da9f0d..cc68cf7d3 100755 --- a/src/cli/generate.ts +++ b/src/cli/generate.ts @@ -3,7 +3,7 @@ import { Command } from '@molt/command' import * as Path from 'node:path' import { z } from 'zod' -import { generateFiles } from '../layers/4_generator/files.js' +import { Generator } from '../layers/4_generator/__.js' import { urlParseSafe } from '../lib/prelude.js' const args = Command.create().description(`Generate a type safe GraphQL client.`) @@ -89,7 +89,7 @@ const defaultSchemaUrl = typeof args.defaultSchemaUrl === `string` ? new URL(args.defaultSchemaUrl) : args.defaultSchemaUrl -await generateFiles({ +await Generator.generate({ sourceSchema: url ? { type: `url`, url } : { type: `sdl`, dirPath: Path.dirname(args.schema) }, diff --git a/src/entrypoints/generator.ts b/src/entrypoints/generator.ts index b2301ad04..60fb332b1 100644 --- a/src/entrypoints/generator.ts +++ b/src/entrypoints/generator.ts @@ -1 +1 @@ -export { generateFiles as generate } from '../layers/4_generator/files.js' +export { generate } from '../layers/4_generator/_.js' diff --git a/src/layers/4_generator/_.ts b/src/layers/4_generator/_.ts new file mode 100644 index 000000000..78dc79fd3 --- /dev/null +++ b/src/layers/4_generator/_.ts @@ -0,0 +1,2 @@ +export * from './config.js' +export * from './generate.js' diff --git a/src/layers/4_generator/__.ts b/src/layers/4_generator/__.ts new file mode 100644 index 000000000..c678ab3cf --- /dev/null +++ b/src/layers/4_generator/__.ts @@ -0,0 +1 @@ +export * as Generator from './_.js' diff --git a/src/layers/4_generator/__snapshots__/files.test.ts.snap b/src/layers/4_generator/__snapshots__/generate.test.ts.snap similarity index 99% rename from src/layers/4_generator/__snapshots__/files.test.ts.snap rename to src/layers/4_generator/__snapshots__/generate.test.ts.snap index 0484eab52..88ddf2754 100644 --- a/src/layers/4_generator/__snapshots__/files.test.ts.snap +++ b/src/layers/4_generator/__snapshots__/generate.test.ts.snap @@ -201,8 +201,8 @@ import type * as Schema from './SchemaBuildtime.js' export interface Index { name: Data.Name - RootTypesPresent: ['Query', 'Mutation'] - RootUnion: Schema.Root.Query | Schema.Root.Mutation + RootTypesPresent: ['Mutation', 'Query'] + RootUnion: Schema.Root.Mutation | Schema.Root.Query Root: { Query: Schema.Root.Query Mutation: Schema.Root.Mutation @@ -270,12 +270,12 @@ export interface Index { ErrorTwo: { __typename: 'ErrorTwo' } } rootResultFields: { + Subscription: {} + Mutation: {} Query: { result: 'result' resultNonNull: 'resultNonNull' } - Mutation: {} - Subscription: {} } } } @@ -284,7 +284,7 @@ export interface Index { exports[`schema2 7`] = ` "import type * as $ from '../../../../../../src/entrypoints/schema.js' -import type * as $Scalar from './Scalar.ts' +import type * as $Scalar from './Scalar.js' // ------------------------------------------------------------ // // Root // @@ -898,7 +898,7 @@ export const Query = $.Object$(\`Query\`, { }) export const $Index: Index = { name: Data.Name, - RootTypesPresent: ['Query', 'Mutation'] as const, + RootTypesPresent: ['Mutation', 'Query'] as const, RootUnion: undefined as any, // Type level only. Root: { Query, @@ -967,12 +967,12 @@ export const $Index: Index = { ErrorTwo: { __typename: 'ErrorTwo' }, }, rootResultFields: { + Subscription: {}, + Mutation: {}, Query: { result: 'result' as const, resultNonNull: 'resultNonNull' as const, }, - Mutation: {}, - Subscription: {}, }, }, } diff --git a/src/layers/4_generator/__snapshots__/generator.test.ts.snap b/src/layers/4_generator/__snapshots__/generator.test.ts.snap deleted file mode 100644 index 4f6123f04..000000000 --- a/src/layers/4_generator/__snapshots__/generator.test.ts.snap +++ /dev/null @@ -1,341 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`generates types from GraphQL SDL file 1`] = ` -"import type * as _ from '../../../../src/Schema/__.js' -import type * as $Scalar from './Scalar.ts' - -export namespace $ { - export interface Index { - Root: { - Query: Root.Query - Mutation: null - Subscription: null - } - objects: { - Foo: Object.Foo - Bar: Object.Bar - ObjectNested: Object.ObjectNested - lowerCaseObject: Object.lowerCaseObject - lowerCaseObject2: Object.lowerCaseObject2 - Object1: Object.Object1 - Object1ImplementingInterface: Object.Object1ImplementingInterface - Object2ImplementingInterface: Object.Object2ImplementingInterface - } - unions: { - FooBarUnion: Union.FooBarUnion - lowerCaseUnion: Union.lowerCaseUnion - } - } -} - -// ------------------------------------------------------------ // -// Root // -// ------------------------------------------------------------ // - -export namespace Root { - export type Query = _.Object<'Query', { - date: _.Field<_.Output.Nullable<$Scalar.Date>> - interface: _.Field<_.Output.Nullable> - id: _.Field<_.Output.Nullable<$Scalar.ID>> - idNonNull: _.Field<$Scalar.ID> - string: _.Field<_.Output.Nullable<$Scalar.String>> - stringWithRequiredArg: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - string: $Scalar.String - }> - > - stringWithArgs: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - string: _.Input.Nullable<$Scalar.String> - int: _.Input.Nullable<$Scalar.Int> - float: _.Input.Nullable<$Scalar.Float> - boolean: _.Input.Nullable<$Scalar.Boolean> - id: _.Input.Nullable<$Scalar.ID> - }> - > - stringWithArgEnum: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - ABCEnum: _.Input.Nullable - }> - > - stringWithListArg: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - ints: _.Input.Nullable<_.Input.List<_.Input.Nullable<$Scalar.Int>>> - }> - > - stringWithListArgRequired: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - ints: _.Input.List<_.Input.Nullable<$Scalar.Int>> - }> - > - stringWithArgInputObject: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - input: _.Input.Nullable - }> - > - stringWithArgInputObjectRequired: _.Field< - _.Output.Nullable<$Scalar.String>, - _.Args<{ - input: InputObject.InputObject - }> - > - listListIntNonNull: _.Field<_.Output.List<_.Output.List<$Scalar.Int>>> - listListInt: _.Field< - _.Output.Nullable<_.Output.List<_.Output.Nullable<_.Output.List<_.Output.Nullable<$Scalar.Int>>>>> - > - listInt: _.Field<_.Output.Nullable<_.Output.List<_.Output.Nullable<$Scalar.Int>>>> - listIntNonNull: _.Field<_.Output.List<$Scalar.Int>> - object: _.Field<_.Output.Nullable> - objectNonNull: _.Field - objectNested: _.Field<_.Output.Nullable> - objectWithArgs: _.Field< - _.Output.Nullable, - _.Args<{ - string: _.Input.Nullable<$Scalar.String> - int: _.Input.Nullable<$Scalar.Int> - float: _.Input.Nullable<$Scalar.Float> - boolean: _.Input.Nullable<$Scalar.Boolean> - id: _.Input.Nullable<$Scalar.ID> - }> - > - fooBarUnion: _.Field<_.Output.Nullable> - /** - * Query enum field documentation. - */ - abcEnum: _.Field<_.Output.Nullable> - lowerCaseUnion: _.Field<_.Output.Nullable> - }> -} - -// ------------------------------------------------------------ // -// Enum // -// ------------------------------------------------------------ // - -export namespace Enum { - /** - * Enum documentation. - * - * Members - * "A" - (DEPRECATED: Enum value A is deprecated.) - * "B" - Enum B member documentation. - * "C" - Enum C member documentation. (DEPRECATED: Enum value C is deprecated.) - */ - export type ABCEnum = _.Enum<'ABCEnum', ['A', 'B', 'C']> -} - -// ------------------------------------------------------------ // -// InputObject // -// ------------------------------------------------------------ // - -export namespace InputObject { - export type InputObject = _.InputObject<'InputObject', { - id: _.Input.Nullable<$Scalar.ID> - idRequired: $Scalar.ID - }> -} - -// ------------------------------------------------------------ // -// Interface // -// ------------------------------------------------------------ // - -export namespace Interface { - export type Interface = _.Interface<'Interface', { - id: _.Field<_.Output.Nullable<$Scalar.ID>> - }, [Object.Object1ImplementingInterface, Object.Object2ImplementingInterface]> -} - -// ------------------------------------------------------------ // -// Object // -// ------------------------------------------------------------ // - -export namespace Object { - /** - * Object documentation. - */ - export type Foo = _.Object<'Foo', { - /** - * Field documentation. - * - * @deprecated Field a is deprecated. - */ - id: _.Field<_.Output.Nullable<$Scalar.ID>> - }> - - export type Bar = _.Object<'Bar', { - int: _.Field<_.Output.Nullable<$Scalar.Int>> - }> - - export type ObjectNested = _.Object<'ObjectNested', { - id: _.Field<_.Output.Nullable<$Scalar.ID>> - object: _.Field<_.Output.Nullable> - }> - - export type lowerCaseObject = _.Object<'lowerCaseObject', { - id: _.Field<_.Output.Nullable<$Scalar.ID>> - }> - - export type lowerCaseObject2 = _.Object<'lowerCaseObject2', { - int: _.Field<_.Output.Nullable<$Scalar.Int>> - }> - - export type Object1 = _.Object<'Object1', { - string: _.Field<_.Output.Nullable<$Scalar.String>> - int: _.Field<_.Output.Nullable<$Scalar.Int>> - float: _.Field<_.Output.Nullable<$Scalar.Float>> - boolean: _.Field<_.Output.Nullable<$Scalar.Boolean>> - id: _.Field<_.Output.Nullable<$Scalar.ID>> - }> - - export type Object1ImplementingInterface = _.Object<'Object1ImplementingInterface', { - id: _.Field<_.Output.Nullable<$Scalar.ID>> - int: _.Field<_.Output.Nullable<$Scalar.Int>> - }> - - export type Object2ImplementingInterface = _.Object<'Object2ImplementingInterface', { - id: _.Field<_.Output.Nullable<$Scalar.ID>> - boolean: _.Field<_.Output.Nullable<$Scalar.Boolean>> - }> -} - -// ------------------------------------------------------------ // -// Union // -// ------------------------------------------------------------ // - -export namespace Union { - /** - * Union documentation. - */ - export type FooBarUnion = _.Union<'FooBarUnion', [Object.Foo, Object.Bar]> - - export type lowerCaseUnion = _.Union<'lowerCaseUnion', [Object.lowerCaseObject, Object.lowerCaseObject2]> -} -" -`; - -exports[`generates types from GraphQL SDL file 2`] = ` -"import * as Scalar from '../../../../src/Schema/NamedType/Scalar/Scalar.js' - -declare global { - interface SchemaCustomScalars { - Date: Date - } -} - -export const Date = Scalar.scalar(\`Date\`, Scalar.nativeScalarCodecs.String) -export type Date = typeof Date - -export * from '../../../../src/Schema/NamedType/Scalar/Scalar.js' -" -`; - -exports[`generates types from GraphQL SDL file 3`] = ` -"import * as _ from '../../../../src/Schema/__.js' -import * as $Scalar from './Scalar.js' - -export const ABCEnum = _.Enum(\`ABCEnum\`, [\`A\`, \`B\`, \`C\`]) - -export const Query = _.Object(\`Query\`, { - date: _.Output.field(_.Output.Nullable($Scalar.Date)), - interface: _.Output.field(_.Output.Nullable(() => Interface)), - id: _.Output.field(_.Output.Nullable($Scalar.ID)), - idNonNull: _.Output.field($Scalar.ID), - string: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithRequiredArg: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithArgs: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithArgEnum: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithListArg: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithListArgRequired: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithArgInputObject: _.Output.field(_.Output.Nullable($Scalar.String)), - stringWithArgInputObjectRequired: _.Output.field(_.Output.Nullable($Scalar.String)), - listListIntNonNull: _.Output.field(_.Output.List(_.Output.List($Scalar.Int))), - listListInt: _.Output.field( - _.Output.Nullable(_.Output.List(_.Output.Nullable(_.Output.List(_.Output.Nullable($Scalar.Int))))), - ), - listInt: _.Output.field(_.Output.Nullable(_.Output.List(_.Output.Nullable($Scalar.Int)))), - listIntNonNull: _.Output.field(_.Output.List($Scalar.Int)), - object: _.Output.field(_.Output.Nullable(() => Object1)), - objectNonNull: _.Output.field(() => Object1), - objectNested: _.Output.field(_.Output.Nullable(() => ObjectNested)), - objectWithArgs: _.Output.field(_.Output.Nullable(() => Object1)), - fooBarUnion: _.Output.field(_.Output.Nullable(() => FooBarUnion)), - abcEnum: _.Output.field(_.Output.Nullable(ABCEnum)), - lowerCaseUnion: _.Output.field(_.Output.Nullable(() => lowerCaseUnion)), -}) - -export const Foo = _.Object(\`Foo\`, { - id: _.Output.field(_.Output.Nullable($Scalar.ID)), -}) - -export const Bar = _.Object(\`Bar\`, { - int: _.Output.field(_.Output.Nullable($Scalar.Int)), -}) - -export const ObjectNested = _.Object(\`ObjectNested\`, { - id: _.Output.field(_.Output.Nullable($Scalar.ID)), - object: _.Output.field(_.Output.Nullable(() => Object1)), -}) - -export const lowerCaseObject = _.Object(\`lowerCaseObject\`, { - id: _.Output.field(_.Output.Nullable($Scalar.ID)), -}) - -export const lowerCaseObject2 = _.Object(\`lowerCaseObject2\`, { - int: _.Output.field(_.Output.Nullable($Scalar.Int)), -}) - -export const Object1 = _.Object(\`Object1\`, { - string: _.Output.field(_.Output.Nullable($Scalar.String)), - int: _.Output.field(_.Output.Nullable($Scalar.Int)), - float: _.Output.field(_.Output.Nullable($Scalar.Float)), - boolean: _.Output.field(_.Output.Nullable($Scalar.Boolean)), - id: _.Output.field(_.Output.Nullable($Scalar.ID)), -}) - -export const Object1ImplementingInterface = _.Object(\`Object1ImplementingInterface\`, { - id: _.Output.field(_.Output.Nullable($Scalar.ID)), - int: _.Output.field(_.Output.Nullable($Scalar.Int)), -}) - -export const Object2ImplementingInterface = _.Object(\`Object2ImplementingInterface\`, { - id: _.Output.field(_.Output.Nullable($Scalar.ID)), - boolean: _.Output.field(_.Output.Nullable($Scalar.Boolean)), -}) - -export const FooBarUnion = _.Union(\`FooBarUnion\`, [Foo, Bar]) - -export const lowerCaseUnion = _.Union(\`lowerCaseUnion\`, [lowerCaseObject, lowerCaseObject2]) - -export const Interface = _.Interface(\`Interface\`, { id: _.Output.field(_.Output.Nullable($Scalar.ID)) }, [ - Object1ImplementingInterface, - Object2ImplementingInterface, -]) - -export const $Index = { - Root: { - Query, - Mutation: null, - Subscription: null, - }, - objects: { - Foo, - Bar, - ObjectNested, - lowerCaseObject, - lowerCaseObject2, - Object1, - Object1ImplementingInterface, - Object2ImplementingInterface, - }, - unions: { - FooBarUnion, - lowerCaseUnion, - }, -} -" -`; diff --git a/src/layers/4_generator/code/Client.ts b/src/layers/4_generator/code/Client.ts deleted file mode 100644 index 7b8565677..000000000 --- a/src/layers/4_generator/code/Client.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createModuleGenerator } from '../createCodeGenerator.js' -import { moduleNameSchemaRuntime } from './SchemaRuntime.js' - -export const { generate: generateClient, moduleName: moduleNameClient } = createModuleGenerator( - `Client`, - ({ config, code }) => { - code.push( - `import { createPrefilled } from '${config.libraryPaths.client}'`, - `import { $defaultSchemaUrl, $Index } from './${moduleNameSchemaRuntime}.js'`, - ``, - `export const create = createPrefilled(\`${config.name}\`, $Index, $defaultSchemaUrl)`, - ) - - return code - }, -) diff --git a/src/layers/4_generator/code/__.ts b/src/layers/4_generator/code/__.ts deleted file mode 100644 index 0f6a88164..000000000 --- a/src/layers/4_generator/code/__.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { capitalizeFirstLetter } from '../../../lib/prelude.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { defaultName } from '../generateCode.js' -import { moduleName_ } from './_.js' - -export const defaultNamespace = `Graffle` - -export const { generate: generate__, moduleName: moduleName__ } = createModuleGenerator( - `__`, - ({ config, code }) => { - const namespace = config.name === defaultName ? defaultNamespace : capitalizeFirstLetter(config.name) - code.push( - `export * as ${namespace} from './${moduleName_}.js'`, - ) - return code - }, -) diff --git a/src/layers/4_generator/code/global.ts b/src/layers/4_generator/code/global.ts deleted file mode 100644 index 6ba895233..000000000 --- a/src/layers/4_generator/code/global.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Code } from '../../../lib/Code.js' -import { hasCustomScalars } from '../../../lib/graphql.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { moduleNameData } from './Data.js' -import { moduleNameMethodsDocument } from './MethodsDocument.js' -import { moduleNameMethodsRoot } from './MethodsRoot.js' -import { moduleNameMethodsSelect } from './MethodsSelect.js' -import { moduleNameScalar } from './Scalar.js' -import { moduleNameSchemaIndex } from './SchemaIndex.js' - -export const { moduleName: moduleNameGlobal, generate: generateGlobal } = createModuleGenerator( - `Global`, - ({ config, code }) => { - const StandardScalarNamespace = `StandardScalar` - const needsDefaultCustomScalarImplementation = hasCustomScalars(config.typeMapByKind) - && !config.options.customScalars - - code.push( - `import type * as Data from './${moduleNameData}.js'`, - `import type * as MethodsSelect from './${moduleNameMethodsSelect}.js'`, - `import type * as MethodsDocument from './${moduleNameMethodsDocument}.js'`, - `import type * as MethodsRoot from './${moduleNameMethodsRoot}.js'`, - `import type { Index } from './${moduleNameSchemaIndex}.js'`, - ) - - if (config.typeMapByKind.GraphQLScalarTypeCustom.length > 0) { - code.push(`import type * as Scalar from './${moduleNameScalar}.js'`) - } - code.push(``) - - const defaultSchemaUrlTsDoc = config.defaultSchemaUrl - ? `\n${Code.TSDoc(config.defaultSchemaUrl.href)}` - : `` - - const customScalarsProperties = config.typeMapByKind.GraphQLScalarTypeCustom - .map((_) => { - return `${_.name}: ${ - needsDefaultCustomScalarImplementation ? `${StandardScalarNamespace}.String` : `Scalar.${_.name}` - }` - }).join(`\n`) - - code.push(` - declare global { - export namespace GraffleGlobalTypes { - export interface Schemas { - ${config.name}: { - interfaces: { - MethodsSelect: MethodsSelect.$MethodsSelect - Document: MethodsDocument.BuilderMethodsDocumentFn - Root: MethodsRoot.BuilderMethodsRootFn - } - name: Data.Name - index: Index - customScalars: { - ${customScalarsProperties} - } - featureOptions: { - schemaErrors: ${config.options.errorTypeNamePattern ? `true` : `false`} - }${defaultSchemaUrlTsDoc} - defaultSchemaUrl: ${config.defaultSchemaUrl ? `string` : `null`} - } - } - } - } - `) - - return code - }, -) diff --git a/src/layers/4_generator/config.ts b/src/layers/4_generator/config.ts new file mode 100644 index 000000000..45923f770 --- /dev/null +++ b/src/layers/4_generator/config.ts @@ -0,0 +1,197 @@ +import { buildClientSchema, printSchema } from 'graphql' +import { buildSchema, type GraphQLObjectType, type GraphQLSchema } from 'graphql' +import fs from 'node:fs/promises' +import * as Path from 'node:path' +import { introspectionQuery } from '../../cli/_helpers.js' +import { getTypeMapByKind, type TypeMapByKind } from '../../lib/graphql.js' +import { omitUndefinedKeys } from '../../lib/prelude.js' +import { fileExists } from './helpers/fs.js' + +export interface Input { + sourceSchema: { + type: 'sdl' + /** + * Defaults to the source directory if set, otherwise the current working directory. + */ + dirPath?: string + } | { + type: 'url' + url: URL + } + outputDirPath?: string + name?: string + // code?: Omit + defaultSchemaUrl?: boolean | URL + /** + * Defaults to the current working directory. + */ + sourceDirPath?: string + sourceCustomScalarCodecsFilePath?: string + schemaPath?: string + /** + * Override import paths to graffle package within the generated code. + * Used by Graffle test suite to have generated clients point to source + * code. Probably not useful to you. + */ + libraryPaths?: InputLibraryPaths + errorTypeNamePattern?: RegExp + /** + * Should custom scalars definitions be imported into the generated output? + */ + customScalars?: boolean + format?: boolean + TSDoc?: { + noDocPolicy?: 'message' | 'ignore' + } +} + +interface InputLibraryPaths { + client?: string + schema?: string + scalars?: string + utilitiesForGenerated?: string +} + +export interface Config { + name: string + paths: { + project: { + outputs: { + root: string + modules: string + } + inputs: { + root: string + customScalarCodecs: string + } + } + imports: { + customScalarCodecs: string + grafflePackage: Required + } + } + schema: { + instance: GraphQLSchema + typeMapByKind: TypeMapByKind + error: { + objects: GraphQLObjectType[] + enabled: boolean + } + } + options: { + defaultSchemaUrl: URL | null + format: boolean + errorTypeNamePattern: RegExp | null + customScalars: boolean + TSDoc: { + noDocPolicy: 'message' | 'ignore' + } + } +} + +export const defaultName = `default` + +export const defaultLibraryPaths = { + client: `graffle/client`, + scalars: `graffle/schema/scalars`, + schema: `graffle/schema`, + utilitiesForGenerated: `graffle/utilities-for-generated`, +} + +export const createConfig = async (input: Input): Promise => { + // dprint-ignore + const defaultSchemaUrl = + typeof input.defaultSchemaUrl === `boolean` + ? input.sourceSchema.type === `url` + ? input.sourceSchema.url + : null + : input.defaultSchemaUrl??null + + const formattingEnabled = input.format ?? true + + const inputPathDirRoot = input.sourceDirPath ?? process.cwd() + + const outputDirPathRoot = input.outputDirPath ?? Path.join(process.cwd(), `./graffle`) + + const outputDirPathModules = Path.join(outputDirPathRoot, `/modules`) + + const inputPathCustomScalarCodecs = input.sourceCustomScalarCodecsFilePath + ?? Path.join(inputPathDirRoot, `customScalarCodecs.ts`) + + const customScalarCodecsPathExists = await fileExists(inputPathCustomScalarCodecs) + + const customScalarCodecsImportPath = Path.relative( + outputDirPathModules, + inputPathCustomScalarCodecs.replace(/\.ts$/, `.js`), + ) + + const errorTypeNamePattern = input.errorTypeNamePattern ?? null + + const sourceSchema = await resolveSourceSchema(input) + + const schema = buildSchema(sourceSchema) + const typeMapByKind = getTypeMapByKind(schema) + const errorObjects = errorTypeNamePattern + ? Object.values(typeMapByKind.GraphQLObjectType).filter(_ => _.name.match(errorTypeNamePattern)) + : [] + + // const rootTypes = { + // Query: typeMapByKind.GraphQLRootType.find(_ => _.name === `Query`) ?? null, + // Mutation: typeMapByKind.GraphQLRootType.find(_ => _.name === `Mutation`) ?? null, + // Subscription: typeMapByKind.GraphQLRootType.find(_ => _.name === `Subscription`) ?? null, + // } + + return { + name: input.name ?? defaultName, + paths: { + project: { + outputs: { + root: outputDirPathRoot, + modules: outputDirPathModules, + }, + inputs: { + root: inputPathDirRoot, + customScalarCodecs: inputPathCustomScalarCodecs, + }, + }, + imports: { + customScalarCodecs: customScalarCodecsImportPath, + grafflePackage: { + ...defaultLibraryPaths, + ...omitUndefinedKeys(input.libraryPaths ?? {}), + }, + }, + }, + schema: { + instance: schema, + typeMapByKind, + error: { + enabled: Boolean(errorTypeNamePattern), + objects: errorObjects, + }, + }, + options: { + defaultSchemaUrl, + format: formattingEnabled, + errorTypeNamePattern, + customScalars: customScalarCodecsPathExists, + TSDoc: { + noDocPolicy: input.TSDoc?.noDocPolicy ?? `ignore`, + }, + }, + } +} + +const resolveSourceSchema = async (input: Input) => { + if (input.sourceSchema.type === `sdl`) { + const sourceDirPath = input.sourceSchema.dirPath ?? input.sourceDirPath ?? process.cwd() + const schemaPath = input.schemaPath ?? Path.join(sourceDirPath, `schema.graphql`) + const sdl = await fs.readFile(schemaPath, `utf8`) + return sdl + } else { + const data = await introspectionQuery(input.sourceSchema.url) + const schema = buildClientSchema(data) + const sdl = printSchema(schema) + return sdl + } +} diff --git a/src/layers/4_generator/createCodeGenerator.ts b/src/layers/4_generator/createCodeGenerator.ts deleted file mode 100644 index d1c7ae7fa..000000000 --- a/src/layers/4_generator/createCodeGenerator.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Config } from './generateCode.js' - -interface ModuleGeneratorResult { - moduleName: string - code: string -} - -export type ModuleGenerator = (config: Config) => ModuleGeneratorResult - -export type ModuleGeneratorConstructor = ( - moduleName: string, - codeGenerator: CodeGeneratorImplementation, -) => { moduleName: string; generate: ModuleGenerator } - -export const createModuleGenerator: ModuleGeneratorConstructor = (moduleName, codeGeneratorImplementation) => { - const codeGenerator = createCodeGenerator(codeGeneratorImplementation) - - const generate: ModuleGenerator = (config) => { - const code = codeGenerator({ config }) - return { - code, - moduleName, - } - } - return { moduleName, generate } -} - -export const createCodeGenerator = <$CustomInput extends object = {}>( - codeGeneratorImplementation: CodeGeneratorImplementation<$CustomInput>, -): CodeGenerator<$CustomInput> => { - return (input) => { - const code: LinesOfGeneratedCode = [] - codeGeneratorImplementation({ ...input, code }) - return code.filter(_ => _ !== null).map(_ => _.trim()).join(`\n`) - } -} - -export type CodeGenerator<$CustomInput extends object = {}> = (input: $CustomInput & BaseInput) => Code - -interface BaseInput { - config: Config -} -interface BaseInputInternal extends BaseInput { - code: LinesOfGeneratedCode -} - -export type CodeGeneratorImplementation<$CustomInput extends object = {}> = ( - input: $CustomInput & BaseInputInternal, -) => void - -type Code = string - -type LinesOfGeneratedCode = (Code | null)[] diff --git a/src/layers/4_generator/files.ts b/src/layers/4_generator/files.ts deleted file mode 100644 index 3c9686399..000000000 --- a/src/layers/4_generator/files.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { Formatter } from '@dprint/formatter' -import { buildClientSchema, printSchema } from 'graphql' -import _ from 'json-bigint' -import fs from 'node:fs/promises' -import * as Path from 'node:path' -import { introspectionQuery } from '../../cli/_helpers.js' -import type { OptionsInput } from './generateCode.js' -import { generateCode, type Input as GenerateInput } from './generateCode.js' -import { fileExists } from './prelude.js' - -export interface Input { - outputDirPath?: string - name?: string - code?: Omit - defaultSchemaUrl?: boolean | URL - sourceSchema: { - type: 'sdl' - /** - * Defaults to the source directory if set, otherwise the current working directory. - */ - dirPath?: string - } | { - type: 'url' - url: URL - } - /** - * Defaults to the current working directory. - */ - sourceDirPath?: string - sourceCustomScalarCodecsFilePath?: string - schemaPath?: string - format?: boolean - errorTypeNamePattern?: OptionsInput['errorTypeNamePattern'] - libraryPaths?: { - client?: string - schema?: string - scalars?: string - utilitiesForGenerated?: string - } -} - -const getTypeScriptFormatter = async (): Promise => { - try { - const { createFromBuffer } = await import(`@dprint/formatter`) - const { getPath } = await import(`@dprint/typescript`) - return createFromBuffer(await fs.readFile(getPath())) - } catch (error) { - return undefined - } -} - -const resolveSourceSchema = async (input: Input) => { - if (input.sourceSchema.type === `sdl`) { - const sourceDirPath = input.sourceSchema.dirPath ?? input.sourceDirPath ?? process.cwd() - const schemaPath = input.schemaPath ?? Path.join(sourceDirPath, `schema.graphql`) - const sdl = await fs.readFile(schemaPath, `utf8`) - return sdl - } else { - const data = await introspectionQuery(input.sourceSchema.url) - const schema = buildClientSchema(data) - const sdl = printSchema(schema) - return sdl - } -} - -export const generateFiles = async (input: Input) => { - const sourceDirPath = input.sourceDirPath ?? process.cwd() - const schemaSource = await resolveSourceSchema(input) - const outputDirPath = input.outputDirPath ?? Path.join(process.cwd(), `./graffle`) - const outputModulesDirPath = Path.join(outputDirPath, `/modules`) - // todo support other extensions: .tsx,.js,.mjs,.cjs - const customScalarCodecsFilePath = input.sourceCustomScalarCodecsFilePath - ?? Path.join(sourceDirPath, `customScalarCodecs.ts`) - const customScalarCodecsImportPath = Path.relative( - outputModulesDirPath, - customScalarCodecsFilePath.replace(/\.ts$/, `.js`), - ) - const customScalarCodecsPathExists = await fileExists(customScalarCodecsFilePath) - const typeScriptFormatter = (input.format ?? true) ? await getTypeScriptFormatter() : undefined - // dprint-ignore - const defaultSchemaUrl = - typeof input.defaultSchemaUrl === `boolean` - ? input.sourceSchema.type === `url` - ? input.sourceSchema.url - : undefined - : input.defaultSchemaUrl - - const codes = generateCode({ - defaultSchemaUrl, - libraryPaths: { - ...input.libraryPaths, - }, - name: input.name, - schemaSource, - importPaths: { - customScalarCodecs: customScalarCodecsImportPath, - }, - ...input.code, - options: { - formatter: typeScriptFormatter, - customScalars: customScalarCodecsPathExists, - errorTypeNamePattern: input.errorTypeNamePattern, - }, - }) - - // todo clear directory before generating so that removed or renamed files are cleaned up. - await fs.mkdir(outputDirPath, { recursive: true }) - await fs.mkdir(`${outputDirPath}/modules`, { recursive: true }) - await Promise.all( - codes.map((code) => { - const isIndexModule = code.moduleName.match(/^_+$/) !== null - return fs.writeFile(`${outputDirPath}/${isIndexModule ? `` : `modules/`}${code.moduleName}.ts`, code.code, { - encoding: `utf8`, - }) - }), - ) -} diff --git a/src/layers/4_generator/files.test.ts b/src/layers/4_generator/generate.test.ts similarity index 100% rename from src/layers/4_generator/files.test.ts rename to src/layers/4_generator/generate.test.ts diff --git a/src/layers/4_generator/generate.ts b/src/layers/4_generator/generate.ts new file mode 100644 index 000000000..21200160b --- /dev/null +++ b/src/layers/4_generator/generate.ts @@ -0,0 +1,69 @@ +import _ from 'json-bigint' +import fs from 'node:fs/promises' +import { type Config, createConfig, type Input } from './config.js' +import { ModuleGenerator_ } from './generators/_.js' +import { ModuleGenerator__ } from './generators/__.js' +import { ModuleGeneratorClient } from './generators/Client.js' +import { ModuleGeneratorData } from './generators/Data.js' +import { ModuleGeneratorError } from './generators/Error.js' +import { ModuleGeneratorGlobal } from './generators/global.js' +import { ModuleGeneratorMethodsDocument } from './generators/MethodsDocument.js' +import { ModuleGeneratorMethodsRoot } from './generators/MethodsRoot.js' +import { ModuleGeneratorMethodsSelect } from './generators/MethodsSelect.js' +import { ModuleGeneratorScalar } from './generators/Scalar.js' +import { ModuleGeneratorSchemaBuildtime } from './generators/SchemaBuildtime.js' +import { ModuleGeneratorSchemaIndex } from './generators/SchemaIndex.js' +import { ModuleGeneratorSchemaRuntime } from './generators/SchemaRuntime.js' +import { ModuleGeneratorSelect } from './generators/Select.js' +import { ModuleGeneratorSelectionSets } from './generators/SelectionSets.js' +import type { GeneratedModule } from './helpers/moduleGenerator.js' +import { getTypeScriptFormatterOrPassthrough, passthroughFormatter } from './helpers/typeScriptFormatter.js' + +export const generate = async (input: Input) => { + const config = await createConfig(input) + const generatedModules = await generateCode(config) + + // todo clear directory before generating so that removed or renamed files are cleaned up. + await fs.mkdir(config.paths.project.outputs.root, { recursive: true }) + await fs.mkdir(config.paths.project.outputs.modules, { recursive: true }) + + await Promise.all( + generatedModules.map((generatedModule) => { + const isExportsModule = generatedModule.name.match(/^_+$/) !== null + const filePath = `${config.paths.project.outputs.root}/${ + isExportsModule ? `` : `modules/` + }${generatedModule.name}.ts` + return fs.writeFile(filePath, generatedModule.content, { + encoding: `utf8`, + }) + }), + ) +} + +const generateCode = async (config: Config): Promise => { + const typeScriptFormatter = config.options.format ? await getTypeScriptFormatterOrPassthrough() : passthroughFormatter + + return [ + ModuleGeneratorGlobal, + ModuleGeneratorClient, + ModuleGeneratorData, + ModuleGeneratorError, + ModuleGeneratorScalar, + // Packaging Stuff + ModuleGenerator__, + ModuleGenerator_, + // Schema Stuff + ModuleGeneratorSchemaIndex, + ModuleGeneratorSchemaBuildtime, + ModuleGeneratorSchemaRuntime, + // Interface Stuff + ModuleGeneratorSelectionSets, + ModuleGeneratorSelect, + ModuleGeneratorMethodsSelect, + ModuleGeneratorMethodsRoot, + ModuleGeneratorMethodsDocument, + ].map(generator => generator.generate(config)).map(code => ({ + ...code, + content: typeScriptFormatter.formatText(code.content), + })) +} diff --git a/src/layers/4_generator/generateCode.ts b/src/layers/4_generator/generateCode.ts deleted file mode 100644 index 02ec23646..000000000 --- a/src/layers/4_generator/generateCode.ts +++ /dev/null @@ -1,164 +0,0 @@ -import type { Formatter } from '@dprint/formatter' -import type { GraphQLObjectType, GraphQLSchema } from 'graphql' -import { buildSchema } from 'graphql' -import * as Path from 'node:path' -import type { TypeMapByKind } from '../../lib/graphql.js' -import { getTypeMapByKind } from '../../lib/graphql.js' -import { generate_ } from './code/_.js' -import { generate__ } from './code/__.js' -import { generateClient } from './code/Client.js' -import { generateData } from './code/Data.js' -import { generateError } from './code/Error.js' -import { generateGlobal } from './code/global.js' -import { generateMethodsDocument } from './code/MethodsDocument.js' -import { generateMethodsRoot } from './code/MethodsRoot.js' -import { generateMethodsSelect } from './code/MethodsSelect.js' -import { generateScalar } from './code/Scalar.js' -import { generateSchemaBuildtime } from './code/SchemaBuildtime.js' -import { generateSchemaIndex } from './code/SchemaIndex.js' -import { generateRuntimeSchema } from './code/SchemaRuntime.js' -import { generateSelect } from './code/Select.js' -import { generateSelectionSets } from './code/SelectionSets.js' - -export interface OptionsInput { - name?: string - errorTypeNamePattern?: RegExp - /** - * Should custom scalars definitions be imported into the generated output? - */ - customScalars?: boolean - formatter?: Formatter - TSDoc?: { - noDocPolicy?: 'message' | 'ignore' - } -} - -export interface Input { - name?: string - libraryPaths?: { - client?: string - schema?: string - scalars?: string - utilitiesForGenerated?: string - } - importPaths?: { - customScalarCodecs?: string - } - defaultSchemaUrl?: URL - /** - * The GraphQL SDL source code. - */ - schemaSource: string - options?: OptionsInput -} - -export interface Config { - name: string - schema: GraphQLSchema - typeMapByKind: TypeMapByKind - defaultSchemaUrl: URL | null - rootTypesPresent: GraphQLObjectType[] - rootTypes: { - Query: GraphQLObjectType | null - Mutation: GraphQLObjectType | null - Subscription: GraphQLObjectType | null - } - error: { - objects: GraphQLObjectType[] - enabled: boolean - } - libraryPaths: { - client: string - schema: string - scalars: string - utilitiesForGenerated: string - } - importPaths: { - customScalarCodecs: string - } - options: { - errorTypeNamePattern: RegExp | null - customScalars: boolean - TSDoc: { - noDocPolicy: 'message' | 'ignore' - } - } -} - -export const defaultName = `default` - -export const resolveOptions = (input: Input): Config => { - const errorTypeNamePattern = input.options?.errorTypeNamePattern ?? null - const schema = buildSchema(input.schemaSource) - const typeMapByKind = getTypeMapByKind(schema) - const errorObjects = errorTypeNamePattern - ? Object.values(typeMapByKind.GraphQLObjectType).filter(_ => _.name.match(errorTypeNamePattern)) - : [] - const defaultSchemaUrl = input.defaultSchemaUrl ?? null - const rootTypes = { - Query: typeMapByKind.GraphQLRootType.find(_ => _.name === `Query`) ?? null, - Mutation: typeMapByKind.GraphQLRootType.find(_ => _.name === `Mutation`) ?? null, - Subscription: typeMapByKind.GraphQLRootType.find(_ => _.name === `Subscription`) ?? null, - } - const rootTypesPresent = Object.values(rootTypes).filter(_ => _ !== null) - return { - name: input.name ?? defaultName, - defaultSchemaUrl, - schema, - error: { - enabled: Boolean(errorTypeNamePattern), - objects: errorObjects, - }, - importPaths: { - customScalarCodecs: input.importPaths?.customScalarCodecs ?? Path.join(process.cwd(), `customScalarCodecs.js`), - }, - libraryPaths: { - client: input.libraryPaths?.client ?? `graffle/client`, - scalars: input.libraryPaths?.scalars ?? `graffle/schema/scalars`, - schema: input.libraryPaths?.schema ?? `graffle/schema`, - utilitiesForGenerated: input.libraryPaths?.utilitiesForGenerated ?? `graffle/utilities-for-generated`, - }, - typeMapByKind, - rootTypes, - rootTypesPresent, - options: { - errorTypeNamePattern, - customScalars: input.options?.customScalars ?? false, - TSDoc: { - noDocPolicy: input.options?.TSDoc?.noDocPolicy ?? `ignore`, - }, - }, - } -} - -export const generateCode = (input: Input) => { - const defaultDprintConfig = { - quoteStyle: `preferSingle`, - semiColons: `asi`, - } - const format = (source: string) => - input.options?.formatter?.formatText(`memory.ts`, source, defaultDprintConfig) ?? source - - const config = resolveOptions(input) - - return [ - generate__, - generate_, - generateData, - generateClient, - generateGlobal, - generateError, - generateSelectionSets, - generateSchemaIndex, - generateScalar, - generateSchemaBuildtime, - generateRuntimeSchema, - generateSelect, - generateMethodsRoot, - generateMethodsSelect, - generateMethodsDocument, - ].map(_ => _(config)).map(code => ({ - ...code, - code: format(code.code), - })) -} diff --git a/src/layers/4_generator/generators/Client.ts b/src/layers/4_generator/generators/Client.ts new file mode 100644 index 000000000..a828698b3 --- /dev/null +++ b/src/layers/4_generator/generators/Client.ts @@ -0,0 +1,16 @@ +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGeneratorSchemaRuntime } from './SchemaRuntime.js' + +export const ModuleGeneratorClient = createModuleGenerator( + `Client`, + ({ config, code }) => { + code.push( + `import { createPrefilled } from '${config.paths.imports.grafflePackage.client}'`, + `import { $defaultSchemaUrl, $Index } from './${ModuleGeneratorSchemaRuntime.name}.js'`, + ``, + `export const create = createPrefilled(\`${config.name}\`, $Index, $defaultSchemaUrl)`, + ) + + return code + }, +) diff --git a/src/layers/4_generator/code/Data.ts b/src/layers/4_generator/generators/Data.ts similarity index 52% rename from src/layers/4_generator/code/Data.ts rename to src/layers/4_generator/generators/Data.ts index b1e966ad1..3a1578890 100644 --- a/src/layers/4_generator/code/Data.ts +++ b/src/layers/4_generator/generators/Data.ts @@ -1,6 +1,6 @@ -import { createModuleGenerator } from '../createCodeGenerator.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' -export const { generate: generateData, moduleName: moduleNameData } = createModuleGenerator( +export const ModuleGeneratorData = createModuleGenerator( `Data`, ({ config, code }) => { code.push( diff --git a/src/layers/4_generator/code/Error.ts b/src/layers/4_generator/generators/Error.ts similarity index 68% rename from src/layers/4_generator/code/Error.ts rename to src/layers/4_generator/generators/Error.ts index 22ca91546..f3b355a12 100644 --- a/src/layers/4_generator/code/Error.ts +++ b/src/layers/4_generator/generators/Error.ts @@ -1,6 +1,6 @@ -import { createModuleGenerator } from '../createCodeGenerator.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' -export const { generate: generateError, moduleName: moduleNameError } = createModuleGenerator( +export const ModuleGeneratorError = createModuleGenerator( `Error`, ({ config, code }) => { code.push( @@ -10,8 +10,8 @@ export const { generate: generateError, moduleName: moduleNameError } = createMo code.push(` const ErrorObjectsTypeNameSelectedEnum = { - ${config.error.objects.map(_ => `${_.name}: { __typename: '${_.name}' }`).join(`,\n`)} - } as ${config.error.objects.length > 0 ? `const` : `Record`} + ${config.schema.error.objects.map(_ => `${_.name}: { __typename: '${_.name}' }`).join(`,\n`)} + } as ${config.schema.error.objects.length > 0 ? `const` : `Record`} const ErrorObjectsTypeNameSelected = Object.values(ErrorObjectsTypeNameSelectedEnum) diff --git a/src/layers/4_generator/code/MethodsDocument.ts b/src/layers/4_generator/generators/MethodsDocument.ts similarity index 54% rename from src/layers/4_generator/code/MethodsDocument.ts rename to src/layers/4_generator/generators/MethodsDocument.ts index 3f4ee4fef..7fceae4cf 100644 --- a/src/layers/4_generator/code/MethodsDocument.ts +++ b/src/layers/4_generator/generators/MethodsDocument.ts @@ -1,22 +1,22 @@ // todo remove use of Utils.Aug when schema errors not in use // todo jsdoc import { hasMutation, hasQuery } from '../../../lib/graphql.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { moduleNameSchemaIndex } from './SchemaIndex.js' -import { moduleNameSelectionSets } from './SelectionSets.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGeneratorSchemaIndex } from './SchemaIndex.js' +import { ModuleGeneratorSelectionSets } from './SelectionSets.js' -export const { generate: generateMethodsDocument, moduleName: moduleNameMethodsDocument } = createModuleGenerator( +export const ModuleGeneratorMethodsDocument = createModuleGenerator( `MethodsDocument`, ({ config, code }) => { - code.push(`import type * as SelectionSets from './${moduleNameSelectionSets}.js'`) - code.push(`import type * as Utilities from '${config.libraryPaths.utilitiesForGenerated}'`) - code.push(`import type { Index } from './${moduleNameSchemaIndex}.js'`) + code.push(`import type * as SelectionSets from './${ModuleGeneratorSelectionSets.name}.js'`) + code.push(`import type * as Utilities from '${config.paths.imports.grafflePackage.utilitiesForGenerated}'`) + code.push(`import type { Index } from './${ModuleGeneratorSchemaIndex.name}.js'`) code.push(``) code.push( `interface DocumentInput {`, - hasQuery(config.typeMapByKind) ? `query?: Record` : null, - hasMutation(config.typeMapByKind) ? `mutation?: Record` : null, + hasQuery(config.schema.typeMapByKind) ? `query?: Record` : null, + hasMutation(config.schema.typeMapByKind) ? `mutation?: Record` : null, `}`, ) code.push(``) diff --git a/src/layers/4_generator/code/MethodsRoot.ts b/src/layers/4_generator/generators/MethodsRoot.ts similarity index 73% rename from src/layers/4_generator/code/MethodsRoot.ts rename to src/layers/4_generator/generators/MethodsRoot.ts index 8f625064d..2934a6278 100644 --- a/src/layers/4_generator/code/MethodsRoot.ts +++ b/src/layers/4_generator/generators/MethodsRoot.ts @@ -1,23 +1,24 @@ // todo remove use of Utils.Aug when schema errors not in use import { getNamedType, type GraphQLObjectType, isScalarType } from 'graphql' import { isAllArgsNullable, RootTypeNameToOperationName } from '../../../lib/graphql.js' -import { createCodeGenerator, createModuleGenerator } from '../createCodeGenerator.js' -import { renderDocumentation, renderName } from '../helpers.js' -import { moduleNameSchemaIndex } from './SchemaIndex.js' -import { moduleNameSelectionSets } from './SelectionSets.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { createModuleGeneratorRunner } from '../helpers/moduleGeneratorRunner.js' +import { renderDocumentation, renderName } from '../helpers/render.js' +import { ModuleGeneratorSchemaIndex } from './SchemaIndex.js' +import { ModuleGeneratorSelectionSets } from './SelectionSets.js' -export const { moduleName: moduleNameMethodsRoot, generate: generateMethodsRoot } = createModuleGenerator( +export const ModuleGeneratorMethodsRoot = createModuleGenerator( `MethodsRoot`, ({ config, code }) => { - code.push(`import type * as Utils from '${config.libraryPaths.utilitiesForGenerated}';`) - code.push(`import type { ResultSet } from '${config.libraryPaths.schema}';`) - code.push(`import type { Index } from './${moduleNameSchemaIndex}.js'`) - code.push(`import type * as SelectionSet from './${moduleNameSelectionSets}.js'`) + code.push(`import type * as Utils from '${config.paths.imports.grafflePackage.utilitiesForGenerated}';`) + code.push(`import type { ResultSet } from '${config.paths.imports.grafflePackage.schema}';`) + code.push(`import type { Index } from './${ModuleGeneratorSchemaIndex.name}.js'`) + code.push(`import type * as SelectionSet from './${ModuleGeneratorSelectionSets.name}.js'`) code.push(``) code.push(``) - config.rootTypesPresent.forEach(node => { + config.schema.typeMapByKind.GraphQLRootType.forEach(node => { code.push(renderRootType({ config, node })) code.push(``) }) @@ -25,7 +26,7 @@ export const { moduleName: moduleNameMethodsRoot, generate: generateMethodsRoot code.push(` export interface BuilderMethodsRoot<$Config extends Utils.Config> { ${ - config.typeMapByKind.GraphQLRootType.map(node => { + config.schema.typeMapByKind.GraphQLRootType.map(node => { const operationName = RootTypeNameToOperationName[node.name as keyof typeof RootTypeNameToOperationName] return `${operationName}: ${node.name}Methods<$Config>` }).join(`\n`) @@ -45,7 +46,7 @@ export const { moduleName: moduleNameMethodsRoot, generate: generateMethodsRoot }, ) -const renderRootType = createCodeGenerator<{ node: GraphQLObjectType }>(({ node, config, code }) => { +const renderRootType = createModuleGeneratorRunner<{ node: GraphQLObjectType }>(({ node, config, code }) => { const fieldMethods = renderFieldMethods({ config, node }) code.push(` @@ -76,7 +77,7 @@ const renderRootType = createCodeGenerator<{ node: GraphQLObjectType }>(({ node, }`) }) -const renderFieldMethods = createCodeGenerator<{ node: GraphQLObjectType }>(({ node, config, code }) => { +const renderFieldMethods = createModuleGeneratorRunner<{ node: GraphQLObjectType }>(({ node, config, code }) => { for (const field of Object.values(node.getFields())) { const doc = renderDocumentation(config, field) code.push(doc) diff --git a/src/layers/4_generator/code/MethodsSelect.ts b/src/layers/4_generator/generators/MethodsSelect.ts similarity index 63% rename from src/layers/4_generator/code/MethodsSelect.ts rename to src/layers/4_generator/generators/MethodsSelect.ts index 5adfaf9d5..8c57bf1a5 100644 --- a/src/layers/4_generator/code/MethodsSelect.ts +++ b/src/layers/4_generator/generators/MethodsSelect.ts @@ -1,21 +1,21 @@ // todo jsdoc import { getNodeNameAndKind, isRootType } from '../../../lib/graphql.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { renderName, title1 } from '../helpers.js' -import { moduleNameSelectionSets } from './SelectionSets.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { renderName, title1 } from '../helpers/render.js' +import { ModuleGeneratorSelectionSets } from './SelectionSets.js' -export const { generate: generateMethodsSelect, moduleName: moduleNameMethodsSelect } = createModuleGenerator( +export const ModuleGeneratorMethodsSelect = createModuleGenerator( `MethodsSelect`, ({ config, code }) => { - code.push(`import type * as $SelectionSets from './${moduleNameSelectionSets}.js'`) - code.push(`import type * as $Utilities from '${config.libraryPaths.utilitiesForGenerated}'`) + code.push(`import type * as $SelectionSets from './${ModuleGeneratorSelectionSets.name}.js'`) + code.push(`import type * as $Utilities from '${config.paths.imports.grafflePackage.utilitiesForGenerated}'`) code.push(``) const graphqlTypeGroups = [ - config.typeMapByKind.GraphQLRootType, - config.typeMapByKind.GraphQLObjectType, - config.typeMapByKind.GraphQLUnionType, - config.typeMapByKind.GraphQLInterfaceType, + config.schema.typeMapByKind.GraphQLRootType, + config.schema.typeMapByKind.GraphQLObjectType, + config.schema.typeMapByKind.GraphQLUnionType, + config.schema.typeMapByKind.GraphQLInterfaceType, ].filter(_ => _.length > 0) code.push(title1(`Select Methods Interface`)) diff --git a/src/layers/4_generator/code/Scalar.ts b/src/layers/4_generator/generators/Scalar.ts similarity index 64% rename from src/layers/4_generator/code/Scalar.ts rename to src/layers/4_generator/generators/Scalar.ts index 996b67df2..724aa0d26 100644 --- a/src/layers/4_generator/code/Scalar.ts +++ b/src/layers/4_generator/generators/Scalar.ts @@ -1,36 +1,36 @@ import { hasCustomScalars } from '../../../lib/graphql.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { typeTitle2 } from '../helpers.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { typeTitle2 } from '../helpers/render.js' -export const { generate: generateScalar, moduleName: moduleNameScalar } = createModuleGenerator( +export const ModuleGeneratorScalar = createModuleGenerator( `Scalar`, ({ config, code }) => { // todo test case for when this is true - const needsDefaultCustomScalarImplementation = hasCustomScalars(config.typeMapByKind) + const needsDefaultCustomScalarImplementation = hasCustomScalars(config.schema.typeMapByKind) && !config.options.customScalars const CustomScalarsNamespace = `CustomScalars` const StandardScalarNamespace = `StandardScalar` if (needsDefaultCustomScalarImplementation) { - code.push(`import * as ${StandardScalarNamespace} from '${config.libraryPaths.scalars}'`) + code.push(`import * as ${StandardScalarNamespace} from '${config.paths.imports.grafflePackage.scalars}'`) } - if (hasCustomScalars(config.typeMapByKind)) { - code.push(`import type { Schema } from '${config.libraryPaths.schema}'`) - code.push(`import * as ${CustomScalarsNamespace} from '${config.importPaths.customScalarCodecs}'`) + if (hasCustomScalars(config.schema.typeMapByKind)) { + code.push(`import type { Schema } from '${config.paths.imports.grafflePackage.schema}'`) + code.push(`import * as ${CustomScalarsNamespace} from '${config.paths.imports.customScalarCodecs}'`) code.push(``) } - code.push(`export * from '${config.libraryPaths.scalars}'`) + code.push(`export * from '${config.paths.imports.grafflePackage.scalars}'`) if (config.options.customScalars) { - code.push(`export * from '${config.importPaths.customScalarCodecs}'`) - const names = config.typeMapByKind.GraphQLScalarTypeCustom.map((scalar) => scalar.name) - code.push(`export { ${names.join(`, `)} } from '${config.importPaths.customScalarCodecs}'`) + code.push(`export * from '${config.paths.imports.customScalarCodecs}'`) + const names = config.schema.typeMapByKind.GraphQLScalarTypeCustom.map((scalar) => scalar.name) + code.push(`export { ${names.join(`, `)} } from '${config.paths.imports.customScalarCodecs}'`) } code.push(``) - for (const scalar of config.typeMapByKind.GraphQLScalarTypeCustom) { + for (const scalar of config.schema.typeMapByKind.GraphQLScalarTypeCustom) { code.push(typeTitle2(`custom scalar type`)(scalar)) code.push(``) code.push(`export type ${scalar.name} = typeof ${CustomScalarsNamespace}.${scalar.name}`) @@ -49,7 +49,7 @@ export const { generate: generateScalar, moduleName: moduleNameScalar } = create `WARNING: Custom scalars detected in the schema, but you have not created a custom scalars module to import implementations from.`, ) code.push( - config.typeMapByKind.GraphQLScalarTypeCustom + config.schema.typeMapByKind.GraphQLScalarTypeCustom .flatMap((_) => { return [ `export const ${_.name} = ${StandardScalarNamespace}.String`, diff --git a/src/layers/4_generator/code/SchemaBuildtime.ts b/src/layers/4_generator/generators/SchemaBuildtime.ts similarity index 93% rename from src/layers/4_generator/code/SchemaBuildtime.ts rename to src/layers/4_generator/generators/SchemaBuildtime.ts index ac376eb9c..7775594ec 100644 --- a/src/layers/4_generator/code/SchemaBuildtime.ts +++ b/src/layers/4_generator/generators/SchemaBuildtime.ts @@ -24,9 +24,10 @@ import { RootTypeName, } from '../../../lib/graphql.js' import { entries, values } from '../../../lib/prelude.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { type Config } from '../generateCode.js' -import { getDocumentation, getInterfaceImplementors } from '../helpers.js' +import type { Config } from '../config.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { getDocumentation, getInterfaceImplementors } from '../helpers/render.js' +import { ModuleGeneratorScalar } from './Scalar.js' const namespaceNames = { GraphQLEnumType: `Enum`, @@ -145,7 +146,7 @@ const concreteRenderers = defineConcreteRenderers({ return Code.TSDocWithBlock(doc, source) }, GraphQLInterfaceType: (config, node) => { - const implementors = getInterfaceImplementors(config.typeMapByKind, node) + const implementors = getInterfaceImplementors(config.schema.typeMapByKind, node) return Code.TSDocWithBlock( getDocumentation(config, node), Code.export$(Code.type( @@ -266,14 +267,14 @@ const renderArg = (config: Config, arg: GraphQLArgument) => { // high level -export const { generate: generateSchemaBuildtime, moduleName: moduleNameSchemaBuildtime } = createModuleGenerator( +export const ModuleGeneratorSchemaBuildtime = createModuleGenerator( `SchemaBuildtime`, ({ config, code }) => { - code.push(`import type * as $ from '${config.libraryPaths.schema}'`) - code.push(`import type * as $Scalar from './Scalar.ts'`) + code.push(`import type * as $ from '${config.paths.imports.grafflePackage.schema}'`) + code.push(`import type * as $Scalar from './${ModuleGeneratorScalar.name}.js'`) code.push(`\n\n`) - for (const [name, types] of entries(config.typeMapByKind)) { + for (const [name, types] of entries(config.schema.typeMapByKind)) { if (name === `GraphQLScalarType`) continue if (name === `GraphQLScalarTypeCustom`) continue if (name === `GraphQLScalarTypeStandard`) continue diff --git a/src/layers/4_generator/code/SchemaIndex.ts b/src/layers/4_generator/generators/SchemaIndex.ts similarity index 56% rename from src/layers/4_generator/code/SchemaIndex.ts rename to src/layers/4_generator/generators/SchemaIndex.ts index a8b5512bf..9b0e45c8d 100644 --- a/src/layers/4_generator/code/SchemaIndex.ts +++ b/src/layers/4_generator/generators/SchemaIndex.ts @@ -1,42 +1,42 @@ import { getNamedType, isUnionType } from 'graphql' import { Code } from '../../../lib/Code.js' import { hasMutation, hasQuery, hasSubscription } from '../../../lib/graphql.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import { moduleNameData } from './Data.js' -import { moduleNameMethodsRoot } from './MethodsRoot.js' -import { moduleNameSchemaBuildtime } from './SchemaBuildtime.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGeneratorData } from './Data.js' +import { ModuleGeneratorMethodsRoot } from './MethodsRoot.js' +import { ModuleGeneratorSchemaBuildtime } from './SchemaBuildtime.js' -export const { generate: generateSchemaIndex, moduleName: moduleNameSchemaIndex } = createModuleGenerator( +export const ModuleGeneratorSchemaIndex = createModuleGenerator( `SchemaIndex`, ({ config, code }) => { const SchemaBuildtimeNamespace = `Schema` const MethodsRootNamespace = `MethodsRoot` code.push(`/* eslint-disable */`) - code.push(`import type * as Data from './${moduleNameData}.js'`) - code.push(`import type * as ${SchemaBuildtimeNamespace} from './${moduleNameSchemaBuildtime}.js'`) - code.push(`import type * as ${MethodsRootNamespace} from './${moduleNameMethodsRoot}.js'`) + code.push(`import type * as Data from './${ModuleGeneratorData.name}.js'`) + code.push(`import type * as ${SchemaBuildtimeNamespace} from './${ModuleGeneratorSchemaBuildtime.name}.js'`) + code.push(`import type * as ${MethodsRootNamespace} from './${ModuleGeneratorMethodsRoot.name}.js'`) code.push(``) const rootTypesPresence = { - Query: hasQuery(config.typeMapByKind), - Mutation: hasMutation(config.typeMapByKind), - Subscription: hasSubscription(config.typeMapByKind), + Query: hasQuery(config.schema.typeMapByKind), + Mutation: hasMutation(config.schema.typeMapByKind), + Subscription: hasSubscription(config.schema.typeMapByKind), } - const root = config.typeMapByKind.GraphQLRootType.map(_ => + const root = config.schema.typeMapByKind.GraphQLRootType.map(_ => [_.name, `${SchemaBuildtimeNamespace}.Root.${_.name}`] as const ) - const objects = config.typeMapByKind.GraphQLObjectType.map(_ => + const objects = config.schema.typeMapByKind.GraphQLObjectType.map(_ => [_.name, `${SchemaBuildtimeNamespace}.Object.${_.name}`] as const ) - const unions = config.typeMapByKind.GraphQLUnionType.map(_ => + const unions = config.schema.typeMapByKind.GraphQLUnionType.map(_ => [_.name, `${SchemaBuildtimeNamespace}.Union.${_.name}`] as const ) - const interfaces = config.typeMapByKind.GraphQLInterfaceType.map( + const interfaces = config.schema.typeMapByKind.GraphQLInterfaceType.map( _ => [_.name, `${SchemaBuildtimeNamespace}.Interface.${_.name}`] as const, ) - const enums = config.typeMapByKind.GraphQLEnumType.map( + const enums = config.schema.typeMapByKind.GraphQLEnumType.map( _ => [_.name, `${SchemaBuildtimeNamespace}.Enum.${_.name}`] as const, ) @@ -45,8 +45,11 @@ export const { generate: generateSchemaIndex, moduleName: moduleNameSchemaIndex `Index`, Code.objectFrom({ name: `Data.Name`, - RootTypesPresent: `[${config.rootTypesPresent.map((_) => Code.quote(_.name)).join(`, `)}]`, - RootUnion: config.rootTypesPresent.map(_ => `${SchemaBuildtimeNamespace}.Root.${_.name}`).join(`|`), + RootTypesPresent: `[${ + config.schema.typeMapByKind.GraphQLRootType.map((_) => Code.quote(_.name)).join(`, `) + }]`, + RootUnion: config.schema.typeMapByKind.GraphQLRootType.map(_ => `${SchemaBuildtimeNamespace}.Root.${_.name}`) + .join(`|`), Root: { type: Code.objectFrom({ Query: rootTypesPresence.Query ? `${SchemaBuildtimeNamespace}.Root.Query` : null, @@ -68,20 +71,21 @@ export const { generate: generateSchemaIndex, moduleName: moduleNameSchemaIndex // Objects that match this pattern name: /.../ error: Code.objectFrom({ objects: Code.objectFromEntries( - config.error.objects.map(_ => [_.name, `${SchemaBuildtimeNamespace}.Object.${_.name}`]), + config.schema.error.objects.map(_ => [_.name, `${SchemaBuildtimeNamespace}.Object.${_.name}`]), ), objectsTypename: Code.objectFromEntries( - config.error.objects.map(_ => [_.name, `{ __typename: "${_.name}" }`]), + config.schema.error.objects.map(_ => [_.name, `{ __typename: "${_.name}" }`]), ), rootResultFields: `{ - ${ - Object.entries(config.rootTypes).map(([rootTypeName, rootType]) => { - if (!rootType) return `${rootTypeName}: {}` - + ${!hasQuery(config.schema.typeMapByKind) ? `Query: {}` : ``} + ${!hasMutation(config.schema.typeMapByKind) ? `Mutation: {}` : ``} + ${!hasSubscription(config.schema.typeMapByKind) ? `Subscription: {}` : ``} + ${ + Object.values(config.schema.typeMapByKind.GraphQLRootType).map((rootType) => { const resultFields = Object.values(rootType.getFields()).filter((field) => { const type = getNamedType(field.type) return isUnionType(type) - && type.getTypes().some(_ => config.error.objects.some(__ => __.name === _.name)) + && type.getTypes().some(_ => config.schema.error.objects.some(__ => __.name === _.name)) }).map((field) => field.name) return `${rootType.name}: {\n${resultFields.map(_ => `${_}: "${_}"`).join(`,\n`)} }` diff --git a/src/layers/4_generator/code/SchemaRuntime.ts b/src/layers/4_generator/generators/SchemaRuntime.ts similarity index 72% rename from src/layers/4_generator/code/SchemaRuntime.ts rename to src/layers/4_generator/generators/SchemaRuntime.ts index 69d3b97d9..217afb811 100644 --- a/src/layers/4_generator/code/SchemaRuntime.ts +++ b/src/layers/4_generator/generators/SchemaRuntime.ts @@ -33,38 +33,38 @@ import { isAllArgsNullable, isAllInputObjectFieldsNullable, } from '../../../lib/graphql.js' -import { createModuleGenerator } from '../createCodeGenerator.js' -import type { Config } from '../generateCode.js' -import { moduleNameData } from './Data.js' -import { moduleNameScalar } from './Scalar.js' -import { moduleNameSchemaIndex } from './SchemaIndex.js' +import type { Config } from '../config.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGeneratorData } from './Data.js' +import { ModuleGeneratorScalar } from './Scalar.js' +import { ModuleGeneratorSchemaIndex } from './SchemaIndex.js' -export const { generate: generateRuntimeSchema, moduleName: moduleNameSchemaRuntime } = createModuleGenerator( +export const ModuleGeneratorSchemaRuntime = createModuleGenerator( `SchemaRuntime`, ({ config, code }) => { code.push(`/* eslint-disable */\n`) code.push( ` - import * as $ from '${config.libraryPaths.schema}' - import * as Data from './${moduleNameData}.js' - import * as $Scalar from './${moduleNameScalar}.js' - import type { Index } from './${moduleNameSchemaIndex}.js' - `, + import * as $ from '${config.paths.imports.grafflePackage.schema}' + import * as Data from './${ModuleGeneratorData.name}.js' + import * as $Scalar from './${ModuleGeneratorScalar.name}.js' + import type { Index } from './${ModuleGeneratorSchemaIndex.name}.js' + `, ) code.push( `export const $defaultSchemaUrl = ${ - config.defaultSchemaUrl ? `new URL("${config.defaultSchemaUrl.href}")` : `undefined` + config.options.defaultSchemaUrl ? `new URL("${config.options.defaultSchemaUrl.href}")` : `undefined` }`, ) code.push( - config.typeMapByKind.GraphQLEnumType.map(type => enum$(config, type)).join(`\n`), - config.typeMapByKind.GraphQLInputObjectType.map(type => inputObject(config, type)).join(`\n`), - config.typeMapByKind.GraphQLObjectType.map(type => object(config, type)).join(`\n`), - config.typeMapByKind.GraphQLUnionType.map(type => union(config, type)).join(`\n`), - config.typeMapByKind.GraphQLInterfaceType.map(type => interface$(config, type)).join(`\n`), - config.typeMapByKind.GraphQLRootType.map(type => object(config, type)).join(`\n`), + config.schema.typeMapByKind.GraphQLEnumType.map(type => enum$(config, type)).join(`\n`), + config.schema.typeMapByKind.GraphQLInputObjectType.map(type => inputObject(config, type)).join(`\n`), + config.schema.typeMapByKind.GraphQLObjectType.map(type => object(config, type)).join(`\n`), + config.schema.typeMapByKind.GraphQLUnionType.map(type => union(config, type)).join(`\n`), + config.schema.typeMapByKind.GraphQLInterfaceType.map(type => interface$(config, type)).join(`\n`), + config.schema.typeMapByKind.GraphQLRootType.map(type => object(config, type)).join(`\n`), ) code.push( @@ -77,16 +77,16 @@ export const { generate: generateRuntimeSchema, moduleName: moduleNameSchemaRunt const index = (config: Config) => { const rootTypesPresence = { - Query: hasQuery(config.typeMapByKind), - Mutation: hasMutation(config.typeMapByKind), - Subscription: hasSubscription(config.typeMapByKind), + Query: hasQuery(config.schema.typeMapByKind), + Mutation: hasMutation(config.schema.typeMapByKind), + Subscription: hasSubscription(config.schema.typeMapByKind), } // todo input objects for decode/encode input object fields - const unions = config.typeMapByKind.GraphQLUnionType.map(type => type.name).join(`,\n`) - const objects = config.typeMapByKind.GraphQLObjectType.map(type => type.name).join(`,\n`) - const interfaces = config.typeMapByKind.GraphQLInterfaceType.map(type => type.name).join(`,\n`) - const roots = config.typeMapByKind.GraphQLRootType.map(type => type.name).join(`,\n`) - const enums = config.typeMapByKind.GraphQLEnumType.map(type => type.name).join(`,\n`) + const unions = config.schema.typeMapByKind.GraphQLUnionType.map(type => type.name).join(`,\n`) + const objects = config.schema.typeMapByKind.GraphQLObjectType.map(type => type.name).join(`,\n`) + const interfaces = config.schema.typeMapByKind.GraphQLInterfaceType.map(type => type.name).join(`,\n`) + const roots = config.schema.typeMapByKind.GraphQLRootType.map(type => type.name).join(`,\n`) + const enums = config.schema.typeMapByKind.GraphQLEnumType.map(type => type.name).join(`,\n`) const allTypes = [ roots, unions, @@ -99,7 +99,7 @@ const index = (config: Config) => { export const $Index: Index = { name: Data.Name, RootTypesPresent: [${ - Object.entries(rootTypesPresence).filter(([_, present]) => present).map(([_]) => Code.quote(_)).join(`, `) + config.schema.typeMapByKind.GraphQLRootType.map((_) => Code.quote(_.name)).join(`, `) }] as const, RootUnion: undefined as any, // Type level only. Root: { @@ -121,20 +121,21 @@ const index = (config: Config) => { }, error: { objects: { - ${config.error.objects.map(type => type.name).join(`,\n`)} + ${config.schema.error.objects.map(type => type.name).join(`,\n`)} }, objectsTypename: { - ${config.error.objects.map(_ => `${_.name}: { __typename: "${_.name}" }`).join(`,\n`)} + ${config.schema.error.objects.map(_ => `${_.name}: { __typename: "${_.name}" }`).join(`,\n`)} }, rootResultFields: { + ${!hasQuery(config.schema.typeMapByKind) ? `Query: {},` : ``} + ${!hasMutation(config.schema.typeMapByKind) ? `Mutation: {},` : ``} + ${!hasSubscription(config.schema.typeMapByKind) ? `Subscription: {},` : ``} ${ - Object.entries(config.rootTypes).map(([rootTypeName, rootType]) => { - if (!rootType) return `${rootTypeName}: {}` - + Object.values(config.schema.typeMapByKind.GraphQLRootType).map((rootType) => { const resultFields = Object.values(rootType.getFields()).filter((field) => { const type = getNamedType(field.type) return isUnionType(type) - && type.getTypes().some(_ => config.error.objects.some(__ => __.name === _.name)) + && type.getTypes().some(_ => config.schema.error.objects.some(__ => __.name === _.name)) }).map((field) => field.name) return `${rootType.name}: {\n${resultFields.map(_ => `${_}: "${_}" as const`).join(`,\n`)} }` @@ -159,7 +160,7 @@ const union = (_config: Config, type: GraphQLUnionType) => { const interface$ = (config: Config, type: GraphQLInterfaceType) => { // todo probably need thunks here - const implementors = config.typeMapByKind.GraphQLObjectType.filter(_ => + const implementors = config.schema.typeMapByKind.GraphQLObjectType.filter(_ => _.getInterfaces().filter(_ => _.name === type.name).length > 0 ).map(_ => _.name).join(`,`) const fields = Object.values(type.getFields()).map((field) => { diff --git a/src/layers/4_generator/code/Select.ts b/src/layers/4_generator/generators/Select.ts similarity index 53% rename from src/layers/4_generator/code/Select.ts rename to src/layers/4_generator/generators/Select.ts index cbd321340..bcb20ec7f 100644 --- a/src/layers/4_generator/code/Select.ts +++ b/src/layers/4_generator/generators/Select.ts @@ -1,21 +1,21 @@ // todo jsdoc -import { createModuleGenerator } from '../createCodeGenerator.js' -import { renderName, title1, typeTitle } from '../helpers.js' -import { moduleNameData } from './Data.js' -import { moduleNameSchemaIndex } from './SchemaIndex.js' -import { moduleNameSelectionSets } from './SelectionSets.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { renderName, title1, typeTitle } from '../helpers/render.js' +import { ModuleGeneratorData } from './Data.js' +import { ModuleGeneratorSchemaIndex } from './SchemaIndex.js' +import { ModuleGeneratorSelectionSets } from './SelectionSets.js' -export const { generate: generateSelect, moduleName: moduleNameSelect } = createModuleGenerator( +export const ModuleGeneratorSelect = createModuleGenerator( `Select`, ({ config, code }) => { - code.push(`import * as Data from './${moduleNameData}.js'`) - code.push(`import type { Index } from './${moduleNameSchemaIndex}.js'`) - code.push(`import type { ResultSet } from '${config.libraryPaths.schema}'`) - code.push(`import type * as SelectionSets from './${moduleNameSelectionSets}.js'`) + code.push(`import * as Data from './${ModuleGeneratorData.name}.js'`) + code.push(`import type { Index } from './${ModuleGeneratorSchemaIndex.name}.js'`) + code.push(`import type { ResultSet } from '${config.paths.imports.grafflePackage.schema}'`) + code.push(`import type * as SelectionSets from './${ModuleGeneratorSelectionSets.name}.js'`) code.push(``) code.push(title1(`Runtime`)) - code.push(`import { createSelect } from '${config.libraryPaths.client}'`) + code.push(`import { createSelect } from '${config.paths.imports.grafflePackage.client}'`) code.push(`export const Select = createSelect(Data.Name)`) code.push(``) @@ -26,7 +26,7 @@ export const { generate: generateSelect, moduleName: moduleNameSelect } = create code.push(typeTitle(config, `Root`)) - code.push(...config.typeMapByKind.GraphQLRootType.map((type) => { + code.push(...config.schema.typeMapByKind.GraphQLRootType.map((type) => { return `export type ${type.name}<$SelectionSet extends SelectionSets.${ renderName(type) }> = ResultSet.Root<$SelectionSet, Index, '${type.name}'>` @@ -35,7 +35,7 @@ export const { generate: generateSelect, moduleName: moduleNameSelect } = create code.push(typeTitle(config, `Object`)) // TODO propagate descriptions to JSDoc - code.push(...config.typeMapByKind.GraphQLObjectType.map((type) => { + code.push(...config.schema.typeMapByKind.GraphQLObjectType.map((type) => { return `export type ${type.name}<$SelectionSet extends SelectionSets.${ renderName(type) }> = ResultSet.Object$<$SelectionSet, Index, Index['allTypes']['${type.name}']>` @@ -43,7 +43,7 @@ export const { generate: generateSelect, moduleName: moduleNameSelect } = create code.push(typeTitle(config, `Union`)) - code.push(...config.typeMapByKind.GraphQLUnionType.map((type) => { + code.push(...config.schema.typeMapByKind.GraphQLUnionType.map((type) => { return `export type ${type.name}<$SelectionSet extends SelectionSets.${ renderName(type) }> = ResultSet.Union<$SelectionSet, Index, Index['allTypes']['${type.name}']>` @@ -51,7 +51,7 @@ export const { generate: generateSelect, moduleName: moduleNameSelect } = create code.push(typeTitle(config, `Interface`)) - code.push(...config.typeMapByKind.GraphQLInterfaceType.map((type) => { + code.push(...config.schema.typeMapByKind.GraphQLInterfaceType.map((type) => { return `export type ${type.name}<$SelectionSet extends SelectionSets.${ renderName(type) }> = ResultSet.Interface<$SelectionSet, Index, Index['allTypes']['${type.name}']>` diff --git a/src/layers/4_generator/code/SelectionSets.ts b/src/layers/4_generator/generators/SelectionSets.ts similarity index 87% rename from src/layers/4_generator/code/SelectionSets.ts rename to src/layers/4_generator/generators/SelectionSets.ts index 2a2efc4c7..11b11c9a9 100644 --- a/src/layers/4_generator/code/SelectionSets.ts +++ b/src/layers/4_generator/generators/SelectionSets.ts @@ -31,7 +31,8 @@ import { } from '../../../lib/graphql.js' import type { StandardScalarTypeNames } from '../../../lib/graphql.js' import { SelectionSet } from '../../2_SelectionSet/__.js' -import { createCodeGenerator, createModuleGenerator } from '../createCodeGenerator.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { createModuleGeneratorRunner } from '../helpers/moduleGeneratorRunner.js' import { getDocumentation, getInterfaceImplementors, @@ -39,28 +40,28 @@ import { renderName, title1, typeTitle2SelectionSet, -} from '../helpers.js' -import { moduleNameScalar } from './Scalar.js' +} from '../helpers/render.js' +import { ModuleGeneratorScalar } from './Scalar.js' -export const { moduleName: moduleNameSelectionSets, generate: generateSelectionSets } = createModuleGenerator( +export const ModuleGeneratorSelectionSets = createModuleGenerator( `SelectionSets`, ({ config, code }) => { code.push(``) - code.push(`import type { SelectionSet as $SelectionSet } from '${config.libraryPaths.schema}'`) - code.push(`import type * as $Utilities from '${config.libraryPaths.utilitiesForGenerated}'`) - if (hasCustomScalars(config.typeMapByKind)) { - code.push(`import type * as $Scalar from './${moduleNameScalar}.js'`) + code.push(`import type { SelectionSet as $SelectionSet } from '${config.paths.imports.grafflePackage.schema}'`) + code.push(`import type * as $Utilities from '${config.paths.imports.grafflePackage.utilitiesForGenerated}'`) + if (hasCustomScalars(config.schema.typeMapByKind)) { + code.push(`import type * as $Scalar from './${ModuleGeneratorScalar.name}.js'`) } code.push(``) const typesToRender = [ - config.typeMapByKind.GraphQLRootType, - config.typeMapByKind.GraphQLEnumType, - config.typeMapByKind.GraphQLInputObjectType, - config.typeMapByKind.GraphQLInterfaceType, - config.typeMapByKind.GraphQLObjectType, - config.typeMapByKind.GraphQLUnionType, + config.schema.typeMapByKind.GraphQLRootType, + config.schema.typeMapByKind.GraphQLEnumType, + config.schema.typeMapByKind.GraphQLInputObjectType, + config.schema.typeMapByKind.GraphQLInterfaceType, + config.schema.typeMapByKind.GraphQLObjectType, + config.schema.typeMapByKind.GraphQLUnionType, ].filter(_ => _.length > 0) typesToRender.forEach((nodes) => { @@ -90,7 +91,7 @@ export const { moduleName: moduleNameSelectionSets, generate: generateSelectionS }, ) -const renderRefDefs = createCodeGenerator<{ nodes: GraphQLNamedType[] }>( +const renderRefDefs = createModuleGeneratorRunner<{ nodes: GraphQLNamedType[] }>( ({ nodes, code }) => { const refDefsRendered = nodes.map(node => `export type _${renderName(node)} = ${renderName(node)}`).join(`\n`) const namespaceRendered = ` @@ -103,7 +104,7 @@ const renderRefDefs = createCodeGenerator<{ nodes: GraphQLNamedType[] }>( }, ) -const renderUnion = createCodeGenerator<{ node: GraphQLUnionType }>( +const renderUnion = createModuleGeneratorRunner<{ node: GraphQLUnionType }>( ({ config, node, code }) => { const doc = renderDocumentation(config, node) code.push(doc) @@ -126,7 +127,7 @@ const renderUnion = createCodeGenerator<{ node: GraphQLUnionType }>( }, ) -const renderEnum = createCodeGenerator<{ node: GraphQLEnumType }>( +const renderEnum = createModuleGeneratorRunner<{ node: GraphQLEnumType }>( ({ config, node, code }) => { const doc = renderDocumentation(config, node) code.push(doc) @@ -136,7 +137,7 @@ const renderEnum = createCodeGenerator<{ node: GraphQLEnumType }>( }, ) -const renderInputObject = createCodeGenerator<{ node: GraphQLInputObjectType }>( +const renderInputObject = createModuleGeneratorRunner<{ node: GraphQLInputObjectType }>( ({ config, node, code }) => { const doc = renderDocumentation(config, node) code.push(doc) @@ -150,13 +151,13 @@ const renderInputObject = createCodeGenerator<{ node: GraphQLInputObjectType }>( }, ) -const renderInterface = createCodeGenerator<{ node: GraphQLInterfaceType }>( +const renderInterface = createModuleGeneratorRunner<{ node: GraphQLInterfaceType }>( ({ config, node, code }) => { const fields = Object.values(node.getFields()) const fieldsRendered = fields.map(field => { return Helpers.outputField(field.name, `${renderName(node)}.${renderName(field)}`) }).join(`\n`) - const implementorTypes = getInterfaceImplementors(config.typeMapByKind, node) + const implementorTypes = getInterfaceImplementors(config.schema.typeMapByKind, node) const onTypesRendered = implementorTypes.map(type => Helpers.outputField(`${SelectionSet.On.prefix}${type.name}`, renderName(type)) ).join( @@ -194,7 +195,7 @@ const renderInterface = createCodeGenerator<{ node: GraphQLInterfaceType }>( }, ) -const renderObject = createCodeGenerator<{ node: GraphQLObjectType }>( +const renderObject = createModuleGeneratorRunner<{ node: GraphQLObjectType }>( ({ config, node, code }) => { const fields = Object.values(node.getFields()) @@ -253,7 +254,7 @@ const kindRenderLookup = { GraphQLUnionType: renderUnion, } -const renderField = createCodeGenerator<{ field: GraphQLField }>( +const renderField = createModuleGeneratorRunner<{ field: GraphQLField }>( ({ config, field, code }) => { const namedType = getNamedType(field.type) const nameRendered = renderName(field) @@ -314,7 +315,7 @@ const renderField = createCodeGenerator<{ field: GraphQLField }>( }, ) -const renderArgsFields = createCodeGenerator<{ field: GraphQLField }>(({ config, field, code }) => { +const renderArgsFields = createModuleGeneratorRunner<{ field: GraphQLField }>(({ config, field, code }) => { const argFieldsRendered = field.args.map(arg => renderArgLike({ config, arg })).join(`\n`) if (argFieldsRendered) { code.push(`{\n${argFieldsRendered}\n}`) @@ -322,7 +323,7 @@ const renderArgsFields = createCodeGenerator<{ field: GraphQLField }>( } }) -const renderFieldArgs = createCodeGenerator<{ field: GraphQLField; argFieldsRendered: string }>( +const renderFieldArgs = createModuleGeneratorRunner<{ field: GraphQLField; argFieldsRendered: string }>( ({ field, code, argFieldsRendered }) => { const argsAnalysis = analyzeArgsNullability(field.args) @@ -346,7 +347,7 @@ const renderFieldArgs = createCodeGenerator<{ field: GraphQLField; arg }, ) -const renderArgLike = createCodeGenerator<{ arg: GraphQLArgument | GraphQLInputField }>( +const renderArgLike = createModuleGeneratorRunner<{ arg: GraphQLArgument | GraphQLInputField }>( ({ config, arg, code }) => { const typeRendered = renderArgType(arg.type) const doc = getDocumentation(config, arg) diff --git a/src/layers/4_generator/code/_.ts b/src/layers/4_generator/generators/_.ts similarity index 50% rename from src/layers/4_generator/code/_.ts rename to src/layers/4_generator/generators/_.ts index d967045a0..71addd32d 100644 --- a/src/layers/4_generator/code/_.ts +++ b/src/layers/4_generator/generators/_.ts @@ -1,9 +1,9 @@ -import { createModuleGenerator } from '../createCodeGenerator.js' -import { moduleNameClient } from './Client.js' -import { moduleNameError } from './Error.js' -import { moduleNameSelect } from './Select.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGeneratorClient } from './Client.js' +import { ModuleGeneratorError } from './Error.js' +import { ModuleGeneratorSelect } from './Select.js' -export const { generate: generate_, moduleName: moduleName_ } = createModuleGenerator( +export const ModuleGenerator_ = createModuleGenerator( `_`, ({ code }) => { code.push( @@ -13,9 +13,9 @@ export const { generate: generate_, moduleName: moduleName_ } = createModuleGene `// setups where this still indeed does help.`, `import './modules/Global.js'`, ``, - `export { Select } from './modules/${moduleNameSelect}.js'`, - `export { isError } from './modules/${moduleNameError}.js'`, - `export { create } from './modules/${moduleNameClient}.js'`, + `export { Select } from './modules/${ModuleGeneratorSelect.name}.js'`, + `export { isError } from './modules/${ModuleGeneratorError.name}.js'`, + `export { create } from './modules/${ModuleGeneratorClient.name}.js'`, ) return code diff --git a/src/layers/4_generator/generators/__.ts b/src/layers/4_generator/generators/__.ts new file mode 100644 index 000000000..b1b15756b --- /dev/null +++ b/src/layers/4_generator/generators/__.ts @@ -0,0 +1,17 @@ +import { capitalizeFirstLetter } from '../../../lib/prelude.js' +import { defaultName } from '../config.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGenerator_ } from './_.js' + +export const defaultNamespace = `Graffle` + +export const ModuleGenerator__ = createModuleGenerator( + `__`, + ({ config, code }) => { + const namespace = config.name === defaultName ? defaultNamespace : capitalizeFirstLetter(config.name) + code.push( + `export * as ${namespace} from './${ModuleGenerator_.name}.js'`, + ) + return code + }, +) diff --git a/src/layers/4_generator/generators/global.ts b/src/layers/4_generator/generators/global.ts new file mode 100644 index 000000000..3c82619bd --- /dev/null +++ b/src/layers/4_generator/generators/global.ts @@ -0,0 +1,69 @@ +import { Code } from '../../../lib/Code.js' +import { hasCustomScalars } from '../../../lib/graphql.js' +import { createModuleGenerator } from '../helpers/moduleGenerator.js' +import { ModuleGeneratorData } from './Data.js' +import { ModuleGeneratorMethodsDocument } from './MethodsDocument.js' +import { ModuleGeneratorMethodsRoot } from './MethodsRoot.js' +import { ModuleGeneratorMethodsSelect } from './MethodsSelect.js' +import { ModuleGeneratorScalar } from './Scalar.js' +import { ModuleGeneratorSchemaIndex } from './SchemaIndex.js' + +export const ModuleGeneratorGlobal = createModuleGenerator( + `Global`, + ({ config, code }) => { + const StandardScalarNamespace = `StandardScalar` + const needsDefaultCustomScalarImplementation = hasCustomScalars(config.schema.typeMapByKind) + && !config.options.customScalars + + code.push( + `import type * as Data from './${ModuleGeneratorData.name}.js'`, + `import type * as MethodsSelect from './${ModuleGeneratorMethodsSelect.name}.js'`, + `import type * as MethodsDocument from './${ModuleGeneratorMethodsDocument.name}.js'`, + `import type * as MethodsRoot from './${ModuleGeneratorMethodsRoot.name}.js'`, + `import type { Index } from './${ModuleGeneratorSchemaIndex.name}.js'`, + ) + + if (config.schema.typeMapByKind.GraphQLScalarTypeCustom.length > 0) { + code.push(`import type * as Scalar from './${ModuleGeneratorScalar.name}.js'`) + } + code.push(``) + + const defaultSchemaUrlTsDoc = config.options.defaultSchemaUrl + ? `\n${Code.TSDoc(config.options.defaultSchemaUrl.href)}` + : `` + + const customScalarsProperties = config.schema.typeMapByKind.GraphQLScalarTypeCustom + .map((_) => { + return `${_.name}: ${ + needsDefaultCustomScalarImplementation ? `${StandardScalarNamespace}.String` : `Scalar.${_.name}` + }` + }).join(`\n`) + + code.push(` + declare global { + export namespace GraffleGlobalTypes { + export interface Schemas { + ${config.name}: { + interfaces: { + MethodsSelect: MethodsSelect.$MethodsSelect + Document: MethodsDocument.BuilderMethodsDocumentFn + Root: MethodsRoot.BuilderMethodsRootFn + } + name: Data.Name + index: Index + customScalars: { + ${customScalarsProperties} + } + featureOptions: { + schemaErrors: ${config.options.errorTypeNamePattern ? `true` : `false`} + }${defaultSchemaUrlTsDoc} + defaultSchemaUrl: ${config.options.defaultSchemaUrl ? `string` : `null`} + } + } + } + } + `) + + return code + }, +) diff --git a/src/layers/4_generator/prelude.ts b/src/layers/4_generator/helpers/fs.ts similarity index 84% rename from src/layers/4_generator/prelude.ts rename to src/layers/4_generator/helpers/fs.ts index 0f895429d..518886565 100644 --- a/src/layers/4_generator/prelude.ts +++ b/src/layers/4_generator/helpers/fs.ts @@ -1,5 +1,5 @@ import fs from 'node:fs/promises' -import { errorFromMaybeError } from '../../lib/prelude.js' +import { errorFromMaybeError } from '../../../lib/prelude.js' export const fileExists = async (path: string) => { return Boolean( diff --git a/src/layers/4_generator/helpers/moduleGenerator.ts b/src/layers/4_generator/helpers/moduleGenerator.ts new file mode 100644 index 000000000..6d87aa142 --- /dev/null +++ b/src/layers/4_generator/helpers/moduleGenerator.ts @@ -0,0 +1,43 @@ +import { + createModuleGeneratorRunner, + type ModuleGeneratorRunner, + type ModuleGeneratorRunnerImplementation, +} from './moduleGeneratorRunner.js' + +export type FactoryModuleGenerator = <$Name extends string>( + /** + * The name of the file that will be written to disk. + */ + name: $Name, + runnerImplementation: ModuleGeneratorRunnerImplementation, +) => ModuleGenerator<$Name> + +export interface ModuleGenerator<$Name extends string = string> { + /** + * The name of the file that will be written to disk. + */ + name: $Name + generate: ModuleGeneratorRunner +} + +export interface GeneratedModule<$Name extends string = string> { + name: $Name + content: string +} + +export const createModuleGenerator: FactoryModuleGenerator = (name, runnerImplementation) => { + const runner = createModuleGeneratorRunner(runnerImplementation) + + const generate: ModuleGeneratorRunner = (config) => { + const content = runner({ config }) + return { + content, + name, + } + } + + return { + name, + generate, + } +} diff --git a/src/layers/4_generator/helpers/moduleGeneratorRunner.ts b/src/layers/4_generator/helpers/moduleGeneratorRunner.ts new file mode 100644 index 000000000..ff95d3521 --- /dev/null +++ b/src/layers/4_generator/helpers/moduleGeneratorRunner.ts @@ -0,0 +1,34 @@ +import type { Config } from '../config.js' +import type { GeneratedModule } from './moduleGenerator.js' + +type FactoryModuleGeneratorRunner = <$CustomInput extends object = {}>( + runnerImplementation: ModuleGeneratorRunnerImplementation<$CustomInput>, +) => CodeGenerator<$CustomInput> + +export type ModuleGeneratorRunner = (config: Config) => GeneratedModule + +export type CodeGenerator<$CustomInput extends object = {}> = (input: $CustomInput & BaseInput) => Code + +export type ModuleGeneratorRunnerImplementation<$CustomInput extends object = {}> = ( + input: $CustomInput & BaseInputInternal, +) => void + +interface BaseInputInternal extends BaseInput { + code: LinesOfGeneratedCode +} + +interface BaseInput { + config: Config +} + +type LinesOfGeneratedCode = (Code | null)[] + +type Code = string + +export const createModuleGeneratorRunner: FactoryModuleGeneratorRunner = (runnerImplementation) => { + return (input) => { + const code: LinesOfGeneratedCode = [] + runnerImplementation({ ...input, code }) + return code.filter(_ => _ !== null).map(_ => _.trim()).join(`\n`) + } +} diff --git a/src/layers/4_generator/helpers.ts b/src/layers/4_generator/helpers/render.ts similarity index 92% rename from src/layers/4_generator/helpers.ts rename to src/layers/4_generator/helpers/render.ts index d84cd4ac5..966d1fb9a 100644 --- a/src/layers/4_generator/helpers.ts +++ b/src/layers/4_generator/helpers/render.ts @@ -1,15 +1,16 @@ import type { GraphQLInterfaceType } from 'graphql' import { type GraphQLEnumValue, type GraphQLField, type GraphQLNamedType, isEnumType } from 'graphql' -import { Code } from '../../lib/Code.js' +import { Code } from '../../../lib/Code.js' import { type Describable, getNodeDisplayName, getNodeNameAndKind, isDeprecatableNode, type TypeMapByKind, -} from '../../lib/graphql.js' -import { borderThickFullWidth, borderThinFullWidth, centerTo } from '../../lib/text.js' -import type { Config } from './generateCode.js' + type TypeMapKind, +} from '../../../lib/graphql.js' +import { borderThickFullWidth, borderThinFullWidth, centerTo } from '../../../lib/text.js' +import type { Config } from '../config.js' export const title1 = (title: string) => { const titleDecorated = ` @@ -55,10 +56,8 @@ export const typeTitle2 = (category: string) => (node: GraphQLNamedType) => { export const typeTitle2SelectionSet = typeTitle2(`GRAPHQL SELECTION SET`) -export const typeTitle = (config: Config, typeName: string) => { - // @ts-expect-error ignoreme - - const hasItems = config.typeMapByKind[`GraphQL${typeName}Type`]?.length > 0 +export const typeTitle = (config: Config, typeName: TypeMapKind) => { + const hasItems = config.schema.typeMapByKind[`GraphQL${typeName}Type`].length > 0 const title = `${typeName} Types` const titleDecorated = `// ${title}\n// ${`-`.repeat(title.length)}\n` if (hasItems) { diff --git a/src/layers/4_generator/helpers/typeScriptFormatter.ts b/src/layers/4_generator/helpers/typeScriptFormatter.ts new file mode 100644 index 000000000..0aa26a437 --- /dev/null +++ b/src/layers/4_generator/helpers/typeScriptFormatter.ts @@ -0,0 +1,42 @@ +import fs from 'node:fs/promises' + +export interface Formatter { + formatText(content: string, customFormatterConfig?: object): string +} + +export const passthroughFormatter: Formatter = { + formatText: (content) => content, +} + +export const getTypeScriptFormatterOrPassthrough = async (): Promise => { + const formatter = await getTypeScriptFormatter() + return formatter ?? passthroughFormatter +} + +/** + * Attempt to get a TypeScript formatter using dynamic imports. If none succeed then returns null. + * + * This allows users to bring their own formatters (within an allow list of what we try to dynamically import). + */ +export const getTypeScriptFormatter = async (): Promise => { + try { + const { createFromBuffer } = await import(`@dprint/formatter`) + const { getPath } = await import(`@dprint/typescript`) + const formatter = createFromBuffer(await fs.readFile(getPath())) + const defaultDprintConfig = { + quoteStyle: `preferSingle`, + semiColons: `asi`, + } + return { + formatText: (content, customFormatterConfig) => { + const config = { + ...defaultDprintConfig, + ...customFormatterConfig, + } + return formatter.formatText(`memory.ts`, content, config) + }, + } + } catch (error) { + return null + } +} diff --git a/src/lib/graphql.ts b/src/lib/graphql.ts index 9ff825942..6cca3a2d0 100644 --- a/src/lib/graphql.ts +++ b/src/lib/graphql.ts @@ -63,6 +63,10 @@ export const StandardScalarTypeTypeScriptMapping = { TypeScriptPrimitiveTypeNames > +export type TypeNamedKind = `Enum` | `InputObject` | `Interface` | `Object` | `Scalar` | `Union` + +export type TypeMapKind = TypeNamedKind | `Root` + export const RootTypeName = { Query: `Query`, Mutation: `Mutation`, diff --git a/src/lib/prelude.ts b/src/lib/prelude.ts index 0d4d5a07f..714abe050 100644 --- a/src/lib/prelude.ts +++ b/src/lib/prelude.ts @@ -571,3 +571,11 @@ export const getOptionalNullablePropertyOrThrow = < if (value === undefined || value === null) throw new Error(`Key not found: ${String(key)}`) return value as ExcludeNullAndUndefined<$Record[$Key]> } + +export const omitUndefinedKeys = (obj: T): OmitUndefinedKeys => { + return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== undefined)) as any +} + +type OmitUndefinedKeys = { + [K in keyof T as undefined extends T[K] ? K : never]: T[K] +} diff --git a/tests/_/schemas/generate.ts b/tests/_/schemas/generate.ts index 5d7e73dfc..ae2dbc739 100644 --- a/tests/_/schemas/generate.ts +++ b/tests/_/schemas/generate.ts @@ -2,14 +2,13 @@ import { pascalCase } from 'es-toolkit' import { printSchema } from 'graphql' import fs from 'node:fs/promises' import { dirname, join } from 'node:path' -import { generateFiles } from '../../../src/layers/4_generator/files.js' -import type { OptionsInput } from '../../../src/layers/4_generator/generateCode.js' +import { Generator } from '../../../src/layers/4_generator/__.js' const generate = async ( input: { dirName: string name?: boolean - options?: OptionsInput + generatorInput?: Omit defaultSchemaUrl?: URL }, ) => { @@ -24,22 +23,20 @@ const generate = async ( await fs.writeFile(outputSchemaPath, printSchema(schema)) - await generateFiles({ + await Generator.generate({ sourceSchema: { type: `sdl` }, sourceDirPath, defaultSchemaUrl: input.defaultSchemaUrl, sourceCustomScalarCodecsFilePath: join(`./tests/_/customScalarCodecs.ts`), outputDirPath, - code: { - libraryPaths: { - client: `../../../../../../src/entrypoints/client.js`, - schema: `../../../../../../src/entrypoints/schema.js`, - scalars: `../../../../../../src/layers/1_Schema/Hybrid/types/Scalar/Scalar.js`, - utilitiesForGenerated: `../../../../../../src/entrypoints/utilities-for-generated.js`, - }, + libraryPaths: { + client: `../../../../../../src/entrypoints/client.js`, + schema: `../../../../../../src/entrypoints/schema.js`, + scalars: `../../../../../../src/layers/1_Schema/Hybrid/types/Scalar/Scalar.js`, + utilitiesForGenerated: `../../../../../../src/entrypoints/utilities-for-generated.js`, }, name, - ...input.options, + ...input.generatorInput, }) console.log(`generated at`, sourceDirPath) } @@ -60,5 +57,5 @@ await generate({ await generate({ dirName: `kitchen-sink`, name: false, - options: { errorTypeNamePattern: /^Error.+/ }, + generatorInput: { errorTypeNamePattern: /^Error.+/ }, }) diff --git a/tests/_/schemas/kitchen-sink/graffle/modules/MethodsRoot.ts b/tests/_/schemas/kitchen-sink/graffle/modules/MethodsRoot.ts index 517163a27..5d6b5a19b 100644 --- a/tests/_/schemas/kitchen-sink/graffle/modules/MethodsRoot.ts +++ b/tests/_/schemas/kitchen-sink/graffle/modules/MethodsRoot.ts @@ -3,6 +3,45 @@ import type * as Utils from '../../../../../../src/entrypoints/utilities-for-gen import type { Index } from './SchemaIndex.js' import type * as SelectionSet from './SelectionSets.js' +export interface MutationMethods<$Config extends Utils.Config> { + // todo Use a static type here? + $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation>) => Promise< + Utils.ResolveOutputReturnRootType< + $Config, + Index, + ResultSet.Mutation< + Utils.AddTypenameToSelectedRootTypeResultFields<$Config, Index, 'Mutation', $SelectionSet>, + Index + > + > + > + // todo Use a static type here? + __typename: () => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + '__typename', + 'Mutation' + > + > + id: () => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + 'id', + ResultSet.Field + > + > + idNonNull: () => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + 'idNonNull', + ResultSet.Field + > + > +} + export interface QueryMethods<$Config extends Utils.Config> { // todo Use a static type here? $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Query>) => Promise< @@ -461,45 +500,6 @@ export interface QueryMethods<$Config extends Utils.Config> { > } -export interface MutationMethods<$Config extends Utils.Config> { - // todo Use a static type here? - $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation>) => Promise< - Utils.ResolveOutputReturnRootType< - $Config, - Index, - ResultSet.Mutation< - Utils.AddTypenameToSelectedRootTypeResultFields<$Config, Index, 'Mutation', $SelectionSet>, - Index - > - > - > - // todo Use a static type here? - __typename: () => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - '__typename', - 'Mutation' - > - > - id: () => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - 'id', - ResultSet.Field - > - > - idNonNull: () => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - 'idNonNull', - ResultSet.Field - > - > -} - export interface BuilderMethodsRoot<$Config extends Utils.Config> { mutation: MutationMethods<$Config> query: QueryMethods<$Config> diff --git a/tests/_/schemas/kitchen-sink/graffle/modules/SchemaBuildtime.ts b/tests/_/schemas/kitchen-sink/graffle/modules/SchemaBuildtime.ts index 21f8aafa5..5dd06fd9c 100644 --- a/tests/_/schemas/kitchen-sink/graffle/modules/SchemaBuildtime.ts +++ b/tests/_/schemas/kitchen-sink/graffle/modules/SchemaBuildtime.ts @@ -1,5 +1,5 @@ import type * as $ from '../../../../../../src/entrypoints/schema.js' -import type * as $Scalar from './Scalar.ts' +import type * as $Scalar from './Scalar.js' // ------------------------------------------------------------ // // Root // diff --git a/tests/_/schemas/kitchen-sink/graffle/modules/SchemaIndex.ts b/tests/_/schemas/kitchen-sink/graffle/modules/SchemaIndex.ts index 7504bf8a8..55a63eba6 100644 --- a/tests/_/schemas/kitchen-sink/graffle/modules/SchemaIndex.ts +++ b/tests/_/schemas/kitchen-sink/graffle/modules/SchemaIndex.ts @@ -5,8 +5,8 @@ import type * as Schema from './SchemaBuildtime.js' export interface Index { name: Data.Name - RootTypesPresent: ['Query', 'Mutation'] - RootUnion: Schema.Root.Query | Schema.Root.Mutation + RootTypesPresent: ['Mutation', 'Query'] + RootUnion: Schema.Root.Mutation | Schema.Root.Query Root: { Query: Schema.Root.Query Mutation: Schema.Root.Mutation @@ -74,12 +74,12 @@ export interface Index { ErrorTwo: { __typename: 'ErrorTwo' } } rootResultFields: { + Subscription: {} + Mutation: {} Query: { result: 'result' resultNonNull: 'resultNonNull' } - Mutation: {} - Subscription: {} } } } diff --git a/tests/_/schemas/kitchen-sink/graffle/modules/SchemaRuntime.ts b/tests/_/schemas/kitchen-sink/graffle/modules/SchemaRuntime.ts index f7f5852a4..aa318297f 100644 --- a/tests/_/schemas/kitchen-sink/graffle/modules/SchemaRuntime.ts +++ b/tests/_/schemas/kitchen-sink/graffle/modules/SchemaRuntime.ts @@ -259,7 +259,7 @@ export const Query = $.Object$(`Query`, { }) export const $Index: Index = { name: Data.Name, - RootTypesPresent: ['Query', 'Mutation'] as const, + RootTypesPresent: ['Mutation', 'Query'] as const, RootUnion: undefined as any, // Type level only. Root: { Query, @@ -328,12 +328,12 @@ export const $Index: Index = { ErrorTwo: { __typename: 'ErrorTwo' }, }, rootResultFields: { + Subscription: {}, + Mutation: {}, Query: { result: 'result' as const, resultNonNull: 'resultNonNull' as const, }, - Mutation: {}, - Subscription: {}, }, }, } diff --git a/tests/_/schemas/mutation-only/graffle/modules/SchemaBuildtime.ts b/tests/_/schemas/mutation-only/graffle/modules/SchemaBuildtime.ts index 172af1d93..18fe8924b 100644 --- a/tests/_/schemas/mutation-only/graffle/modules/SchemaBuildtime.ts +++ b/tests/_/schemas/mutation-only/graffle/modules/SchemaBuildtime.ts @@ -1,5 +1,5 @@ import type * as $ from '../../../../../../src/entrypoints/schema.js' -import type * as $Scalar from './Scalar.ts' +import type * as $Scalar from './Scalar.js' // ------------------------------------------------------------ // // Root // diff --git a/tests/_/schemas/mutation-only/graffle/modules/SchemaIndex.ts b/tests/_/schemas/mutation-only/graffle/modules/SchemaIndex.ts index 4dd598858..c602051d3 100644 --- a/tests/_/schemas/mutation-only/graffle/modules/SchemaIndex.ts +++ b/tests/_/schemas/mutation-only/graffle/modules/SchemaIndex.ts @@ -23,8 +23,9 @@ export interface Index { objectsTypename: {} rootResultFields: { Query: {} - Mutation: {} + Subscription: {} + Mutation: {} } } } diff --git a/tests/_/schemas/mutation-only/graffle/modules/SchemaRuntime.ts b/tests/_/schemas/mutation-only/graffle/modules/SchemaRuntime.ts index 3658bf355..138bd50bd 100644 --- a/tests/_/schemas/mutation-only/graffle/modules/SchemaRuntime.ts +++ b/tests/_/schemas/mutation-only/graffle/modules/SchemaRuntime.ts @@ -30,8 +30,9 @@ export const $Index: Index = { objectsTypename: {}, rootResultFields: { Query: {}, - Mutation: {}, + Subscription: {}, + Mutation: {}, }, }, } diff --git a/tests/_/schemas/pokemon/graffle/modules/MethodsRoot.ts b/tests/_/schemas/pokemon/graffle/modules/MethodsRoot.ts index 9ff56bbe9..19ee5aaf5 100644 --- a/tests/_/schemas/pokemon/graffle/modules/MethodsRoot.ts +++ b/tests/_/schemas/pokemon/graffle/modules/MethodsRoot.ts @@ -3,6 +3,37 @@ import type * as Utils from '../../../../../../src/entrypoints/utilities-for-gen import type { Index } from './SchemaIndex.js' import type * as SelectionSet from './SelectionSets.js' +export interface MutationMethods<$Config extends Utils.Config> { + // todo Use a static type here? + $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation>) => Promise< + Utils.ResolveOutputReturnRootType< + $Config, + Index, + ResultSet.Mutation< + Utils.AddTypenameToSelectedRootTypeResultFields<$Config, Index, 'Mutation', $SelectionSet>, + Index + > + > + > + // todo Use a static type here? + __typename: () => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + '__typename', + 'Mutation' + > + > + addPokemon: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation.addPokemon>) => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + 'addPokemon', + ResultSet.Field<$SelectionSet, Index['Root']['Mutation']['fields']['addPokemon'], Index> + > + > +} + export interface QueryMethods<$Config extends Utils.Config> { // todo Use a static type here? $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Query>) => Promise< @@ -74,37 +105,6 @@ export interface QueryMethods<$Config extends Utils.Config> { > } -export interface MutationMethods<$Config extends Utils.Config> { - // todo Use a static type here? - $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation>) => Promise< - Utils.ResolveOutputReturnRootType< - $Config, - Index, - ResultSet.Mutation< - Utils.AddTypenameToSelectedRootTypeResultFields<$Config, Index, 'Mutation', $SelectionSet>, - Index - > - > - > - // todo Use a static type here? - __typename: () => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - '__typename', - 'Mutation' - > - > - addPokemon: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation.addPokemon>) => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - 'addPokemon', - ResultSet.Field<$SelectionSet, Index['Root']['Mutation']['fields']['addPokemon'], Index> - > - > -} - export interface BuilderMethodsRoot<$Config extends Utils.Config> { mutation: MutationMethods<$Config> query: QueryMethods<$Config> diff --git a/tests/_/schemas/pokemon/graffle/modules/SchemaIndex.ts b/tests/_/schemas/pokemon/graffle/modules/SchemaIndex.ts index 1b038ea9c..abf5120a3 100644 --- a/tests/_/schemas/pokemon/graffle/modules/SchemaIndex.ts +++ b/tests/_/schemas/pokemon/graffle/modules/SchemaIndex.ts @@ -5,8 +5,8 @@ import type * as Schema from './SchemaBuildtime.js' export interface Index { name: Data.Name - RootTypesPresent: ['Query', 'Mutation'] - RootUnion: Schema.Root.Query | Schema.Root.Mutation + RootTypesPresent: ['Mutation', 'Query'] + RootUnion: Schema.Root.Mutation | Schema.Root.Query Root: { Query: Schema.Root.Query Mutation: Schema.Root.Mutation @@ -35,9 +35,9 @@ export interface Index { objects: {} objectsTypename: {} rootResultFields: { - Query: {} - Mutation: {} Subscription: {} + Mutation: {} + Query: {} } } } diff --git a/tests/_/schemas/pokemon/graffle/modules/SchemaRuntime.ts b/tests/_/schemas/pokemon/graffle/modules/SchemaRuntime.ts index 9604f3393..9cb64e60c 100644 --- a/tests/_/schemas/pokemon/graffle/modules/SchemaRuntime.ts +++ b/tests/_/schemas/pokemon/graffle/modules/SchemaRuntime.ts @@ -109,7 +109,7 @@ export const Query = $.Object$(`Query`, { }) export const $Index: Index = { name: Data.Name, - RootTypesPresent: ['Query', 'Mutation'] as const, + RootTypesPresent: ['Mutation', 'Query'] as const, RootUnion: undefined as any, // Type level only. Root: { Query, @@ -139,9 +139,9 @@ export const $Index: Index = { objects: {}, objectsTypename: {}, rootResultFields: { - Query: {}, - Mutation: {}, Subscription: {}, + Mutation: {}, + Query: {}, }, }, } diff --git a/tests/_/schemas/query-only/graffle/modules/SchemaBuildtime.ts b/tests/_/schemas/query-only/graffle/modules/SchemaBuildtime.ts index 1055f9053..0f67346db 100644 --- a/tests/_/schemas/query-only/graffle/modules/SchemaBuildtime.ts +++ b/tests/_/schemas/query-only/graffle/modules/SchemaBuildtime.ts @@ -1,5 +1,5 @@ import type * as $ from '../../../../../../src/entrypoints/schema.js' -import type * as $Scalar from './Scalar.ts' +import type * as $Scalar from './Scalar.js' // ------------------------------------------------------------ // // Root // diff --git a/tests/_/schemas/query-only/graffle/modules/SchemaIndex.ts b/tests/_/schemas/query-only/graffle/modules/SchemaIndex.ts index 94aae1080..0a4e5e282 100644 --- a/tests/_/schemas/query-only/graffle/modules/SchemaIndex.ts +++ b/tests/_/schemas/query-only/graffle/modules/SchemaIndex.ts @@ -22,9 +22,9 @@ export interface Index { objects: {} objectsTypename: {} rootResultFields: { - Query: {} Mutation: {} Subscription: {} + Query: {} } } } diff --git a/tests/_/schemas/query-only/graffle/modules/SchemaRuntime.ts b/tests/_/schemas/query-only/graffle/modules/SchemaRuntime.ts index 3d997f293..3d2ffce19 100644 --- a/tests/_/schemas/query-only/graffle/modules/SchemaRuntime.ts +++ b/tests/_/schemas/query-only/graffle/modules/SchemaRuntime.ts @@ -29,9 +29,9 @@ export const $Index: Index = { objects: {}, objectsTypename: {}, rootResultFields: { - Query: {}, Mutation: {}, Subscription: {}, + Query: {}, }, }, } diff --git a/website/graffle/modules/SchemaBuildtime.ts b/website/graffle/modules/SchemaBuildtime.ts index 51b520885..40ecabe10 100644 --- a/website/graffle/modules/SchemaBuildtime.ts +++ b/website/graffle/modules/SchemaBuildtime.ts @@ -1,5 +1,5 @@ import type * as $ from 'graffle/schema' -import type * as $Scalar from './Scalar.ts' +import type * as $Scalar from './Scalar.js' // ------------------------------------------------------------ // // Root // diff --git a/website/graffle/modules/SchemaIndex.ts b/website/graffle/modules/SchemaIndex.ts index 8ea2c77ea..27a37a819 100644 --- a/website/graffle/modules/SchemaIndex.ts +++ b/website/graffle/modules/SchemaIndex.ts @@ -33,9 +33,9 @@ export interface Index { objects: {} objectsTypename: {} rootResultFields: { - Query: {} Mutation: {} Subscription: {} + Query: {} } } } diff --git a/website/graffle/modules/SchemaRuntime.ts b/website/graffle/modules/SchemaRuntime.ts index 9a1fcde4c..67575ee06 100644 --- a/website/graffle/modules/SchemaRuntime.ts +++ b/website/graffle/modules/SchemaRuntime.ts @@ -135,9 +135,9 @@ export const $Index: Index = { objects: {}, objectsTypename: {}, rootResultFields: { - Query: {}, Mutation: {}, Subscription: {}, + Query: {}, }, }, } diff --git a/website/pokemon/modules/MethodsRoot.ts b/website/pokemon/modules/MethodsRoot.ts index c5b1cbdcc..54eea0525 100644 --- a/website/pokemon/modules/MethodsRoot.ts +++ b/website/pokemon/modules/MethodsRoot.ts @@ -3,6 +3,37 @@ import type * as Utils from 'graffle/utilities-for-generated' import type { Index } from './SchemaIndex.js' import type * as SelectionSet from './SelectionSets.js' +export interface MutationMethods<$Config extends Utils.Config> { + // todo Use a static type here? + $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation>) => Promise< + Utils.ResolveOutputReturnRootType< + $Config, + Index, + ResultSet.Mutation< + Utils.AddTypenameToSelectedRootTypeResultFields<$Config, Index, 'Mutation', $SelectionSet>, + Index + > + > + > + // todo Use a static type here? + __typename: () => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + '__typename', + 'Mutation' + > + > + addPokemon: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation.addPokemon>) => Promise< + Utils.ResolveOutputReturnRootField< + $Config, + Index, + 'addPokemon', + ResultSet.Field<$SelectionSet, Index['Root']['Mutation']['fields']['addPokemon'], Index> + > + > +} + export interface QueryMethods<$Config extends Utils.Config> { // todo Use a static type here? $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Query>) => Promise< @@ -74,37 +105,6 @@ export interface QueryMethods<$Config extends Utils.Config> { > } -export interface MutationMethods<$Config extends Utils.Config> { - // todo Use a static type here? - $batch: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation>) => Promise< - Utils.ResolveOutputReturnRootType< - $Config, - Index, - ResultSet.Mutation< - Utils.AddTypenameToSelectedRootTypeResultFields<$Config, Index, 'Mutation', $SelectionSet>, - Index - > - > - > - // todo Use a static type here? - __typename: () => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - '__typename', - 'Mutation' - > - > - addPokemon: <$SelectionSet>(selectionSet: Utils.Exact<$SelectionSet, SelectionSet.Mutation.addPokemon>) => Promise< - Utils.ResolveOutputReturnRootField< - $Config, - Index, - 'addPokemon', - ResultSet.Field<$SelectionSet, Index['Root']['Mutation']['fields']['addPokemon'], Index> - > - > -} - export interface BuilderMethodsRoot<$Config extends Utils.Config> { mutation: MutationMethods<$Config> query: QueryMethods<$Config> diff --git a/website/pokemon/modules/SchemaBuildtime.ts b/website/pokemon/modules/SchemaBuildtime.ts index 354f2a5f6..1af8986c8 100644 --- a/website/pokemon/modules/SchemaBuildtime.ts +++ b/website/pokemon/modules/SchemaBuildtime.ts @@ -1,5 +1,5 @@ import type * as $ from 'graffle/schema' -import type * as $Scalar from './Scalar.ts' +import type * as $Scalar from './Scalar.js' // ------------------------------------------------------------ // // Root // diff --git a/website/pokemon/modules/SchemaIndex.ts b/website/pokemon/modules/SchemaIndex.ts index 1b038ea9c..abf5120a3 100644 --- a/website/pokemon/modules/SchemaIndex.ts +++ b/website/pokemon/modules/SchemaIndex.ts @@ -5,8 +5,8 @@ import type * as Schema from './SchemaBuildtime.js' export interface Index { name: Data.Name - RootTypesPresent: ['Query', 'Mutation'] - RootUnion: Schema.Root.Query | Schema.Root.Mutation + RootTypesPresent: ['Mutation', 'Query'] + RootUnion: Schema.Root.Mutation | Schema.Root.Query Root: { Query: Schema.Root.Query Mutation: Schema.Root.Mutation @@ -35,9 +35,9 @@ export interface Index { objects: {} objectsTypename: {} rootResultFields: { - Query: {} - Mutation: {} Subscription: {} + Mutation: {} + Query: {} } } } diff --git a/website/pokemon/modules/SchemaRuntime.ts b/website/pokemon/modules/SchemaRuntime.ts index 1e456d7b6..a72bc676f 100644 --- a/website/pokemon/modules/SchemaRuntime.ts +++ b/website/pokemon/modules/SchemaRuntime.ts @@ -109,7 +109,7 @@ export const Query = $.Object$(`Query`, { }) export const $Index: Index = { name: Data.Name, - RootTypesPresent: ['Query', 'Mutation'] as const, + RootTypesPresent: ['Mutation', 'Query'] as const, RootUnion: undefined as any, // Type level only. Root: { Query, @@ -139,9 +139,9 @@ export const $Index: Index = { objects: {}, objectsTypename: {}, rootResultFields: { - Query: {}, - Mutation: {}, Subscription: {}, + Mutation: {}, + Query: {}, }, }, }