@eddeee888/gcg-typescript-resolver-files
TypeScript icon, indicating that this package has built-in type declarations

0.8.1 • Public • Published

@eddeee888/gcg-typescript-resolver-files

This GraphQL Code Generator plugin creates resolvers given GraphQL schema.

This relies on types generated from @graphql-codegen/typescript and @graphql-codegen/typescript-resolvers plugins.

yarn add -D @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files
yarn add graphql-scalars

Features

  • Generates opinionated folder/file structure with type-safety in mind to help finding query, mutation or object types easily
  • Does NOT overwrite existing resolver logic
  • Detects missing / wrong resolver exports and adds them to resolver files
  • Automatically sets up GraphQL Scalars with types and config from graphql-scalars with ability to opt-out
  • Automatically extracts object type mapper interfaces and types (marked with Mapper suffix) from accompanying .mappers.ts files in each module. For example, if the schema file is /path/to/schema.graphql, the mapper file is /path/to/schema.mappers.ts.

Getting Started

Setup

Files

# src/schema/base/schema.graphql
type Query

# src/schema/user/schema.graphql
extend type Query {
  user(id: ID!): User
}

type User {
  id: ID!
  fullName: String!
}

type Address {
  id: ID!
  address: String!
}
// src/schema/user/schema.mappers.ts

// Exporting the following Mapper interfaces and types is the equivalent of this codegen config:
// ```yml
// mappers:
//   Address: './user/schema.mappers#AddressMapper'
//   User: './user/schema.mappers#UserMapper'
// ```

export { Address as AddressMapper } from 'address-package';

export interface UserMapper {
  id: string;
  firstName: string;
  lastName: string;
}

Codegen Config

// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files';

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema': defineConfig(),
  },
};

export default config;

OR

# codegen.yml
schema: '**/*.graphql'
generates:
  src/schema:
    preset: '@eddeee888/gcg-typescript-resolver-files'
    watchPattern: '**/*.mappers.ts'

Result

Running codegen will generate the following files:

  • src/schema/user/resolvers/Query/user.ts
  • src/schema/user/resolvers/User.ts
  • src/schema/user/resolvers/Address.ts
  • src/schema/resolvers.generated.ts
  • src/schema/typeDefs.generated.ts
  • src/schema/types.generated.ts

Config Options

mode

merged or modules (Default: modules)

How files are collocated. modules detects containing dir of a schema file as "modules", then split resolvers into those modules. merged treats baseOutputDir as the one and only module and generates resolvers.

resolverTypesPath

string (Default: ./types.generated.ts)

Relative path to type file generated by typescript-resolvers plugin.

resolverRelativeTargetDir

string (Default: resolvers)

Relative path to target dir. For config.mode=merged, files will be generated into <baseOutputDir>/<resolverRelativeTargetDir>. For config.mode=modules, files will be generated into <baseOutputDir>/**/<moduleName>/<resolverRelativeTargetDir>.

resolverMainFileMode

merged or modules (Default: merged)

How to generate file/s that put resolvers map together:

  • merged: one file
  • modules: one file per module

resolverMainFile

string (Default: resolvers.generated.ts)

File that puts all generated resolvers together. Relative from baseOutputDir.

resolverGeneration

disabled or recommended or all or object (Default: recommended)

Decides which resolvers to generate:

  • disabled: generates no resolvers. Use this if you want to use your own structures. Note: if custom scalars are detected and used, resolver main files are still generated.
  • recommended: generates the minimal amount of resolvers. Use this if you want a managed experience.
    • no union/interface resolvers are generated because we rely on certain settings in typescript-resolvers that make these not required.
  • all: generates all resolvers. Use this if you want all resolvers to be generated and use the ones you need.

Internally, each string option is turned into an object. This object uses glob pattern to tell which files to be generated based on the file's normalized name (*). For example, recommended option is the equivalent of the following object:

{
  query: '*',
  mutation: '*',
  subscription: '*',
  scalar: '*',
  object: '*',
  union: '',
  interface: '',
}

(*) A normalized name has the following shape: <Module name>.<Top-level type name>.<Field resolver>?

Consider this schema:

# src/schema/pet/schema.graphql
extend type Query {
  pets: [Pet!]!
}

type Cat {
  id: ID!
}
type Dog {
  id: ID!
}
union Pet = Cat | Dog

# src/schema/user/schema.graphql
extend type Mutation {
  createUser(id: ID!): CreateUserResult!
}

type User {
  id: ID!
}

type CreateUserOk {
  result: User!
}
type CreateUserError {
  error: String!
}
union CreateUserResult = CreateUserOk | CreateUserError

Then, the generated files and normalised names are:

  • src/schema/pet/resolvers/Query/pets.ts, affected by query option, normalized name: pet.Query.pets
  • src/schema/pet/resolvers/Cat.ts, affected by object option, normalized name: pet.Cat
  • src/schema/pet/resolvers/Dog.ts, affected by object option, normalized name: pet.Dog
  • src/schema/pet/resolvers/Pet.ts, affected by union option, normalized name: pet.Pet
  • src/schema/user/resolvers/Mutation/createUser.ts, affected by mutation option, normalized name: user.Mutation.createUser
  • src/schema/user/resolvers/User.ts, affected by object option, normalized name: user.User
  • src/schema/user/resolvers/CreateUserOk.ts, affected by object option, normalized name: user.CreateUserOk
  • src/schema/user/resolvers/CreateUserError.ts, affected by object option, normalized name: user.CreateUserError
  • src/schema/user/resolvers/CreateUserResult.ts, affected by union option, normalized name: user.CreateUserResult

Now, let's say we want to disable all union files, types ending with Ok or Error, the config would look like this:

defineConfig({
  resolverGeneration: {
    query: '*',
    mutation: '*',
    subscription: '*',
    scalar: '*',
    object: ['!*.*Ok', '!*.*Error'], // Disables objects ending with `Ok` or `Error` in every module
    union: '', // Empty string disables all file generation of relevant type in every module
    interface: '*',
  },
});

Hint: To see why certain files are skipped, run codegen command with DEBUG turned on:

DEBUG="@eddeee888/gcg-typescript-resolver-files" yarn graphql-codegen

typeDefsFileMode

merged or mergedWhitelisted or modules (Default: merged)

How to generate typeDefs file/s:

  • merged: one file
  • mergedWhitelisted: one file but only contains whitelisted modules
  • modules: one file per module

If config.mode=merged, this config is always merged

typeDefsFilePath

string or false (Default: ./typeDefs.generated.ts)

Where to generate typeDefs files. If value is false or empty string, the file/s are not generated.

If typeDefsFileMode=merged or typeDefsFileMode=mergedWhitelisted, this path is relative from baseOutputDir.

If typeDefsFileMode=modules, this path is relative from each module directory.

add

Record<string, (@graphql-codegen/add).AddPluginConfig> (Default: undefined) (EXPERIMENTAL)

Allows using add plugin on a given file.

Note: Currently only supports resolverTypesPath file i.e. types.generated.ts

Example:

// codegen.ts
{
  generates: {
    'src/schema': defineConfig({
      add: {
        './types.generated.ts': { content: '/* eslint-disable */' },
      },
    })
  }
}

whitelistedModules

Array<string> (Only works with config.mode=modules)

Whitelists modules to generate files and entries in main file. By default all modules are whitelisted. Useful for gradual migrations.

blacklistedModules

Array<string> (Only works with config.mode=modules)

Blacklists modules to avoid generate files and entries in main file. Useful for gradual migrations.

externalResolvers

Record<string, string>

Map of relative or absolute path (prefixed with ~) to external or existing resolvers.

Examples:

  • DateTime: ~graphql-scalars#DateTimeResolver
  • Query.me: '~@org/meResolver#default as meResolver'
  • User: 'otherResolvers#User as UserResolver'.

typesPluginsConfig

(@graphql-codegen/typescript).TypeScriptPluginConfig & (@graphql-codegen/typescript-resolvers).TypeScriptResolversPluginConfig

Takes typescript config and typescript-resolvers config to override the defaults.

Experimental options:

  • namingConvention

mappersFileExtension

string (Default: .mappers.ts)

The files with this extension provides mappers interfaces and types for the schema files in the same module.

mappersSuffix

string (Default: Mapper)

Exported interfaces and types with this suffix from mappersFile in each module are put into the mappers object of @graphql-codegen/typescript-resolvers.

scalarsModule

string or false (Default: graphql-scalars)

Where Scalar implementation and codegen types come from. Use false to implement your own Scalars.

If using an module that is not graphql-scalars, the module must export resolver implementation and codegen type the same way graphql-scalars does e.g.

{
  resolvers: {
    DateTime: DateTimeResolver,
  },
  DateTimeResolver: {
    // ... resolver implementation
    extensions: {
      codegenScalarType: 'Date | string',
    },
  }
}

scalarsOverrides

Record<string, { resolver?: string; type?: string | { input: string; output: string } }> (Default: {})

Overrides scalars' resolver implementation, type or both.

Example:

// codegen.ts
{
  generates: {
    'src/schema': defineConfig({
      scalarsOverrides: {
        DateTime: {
          resolver: './localDateTimeResolver#Resolver',
        }
        Currency: {
          type: 'unknown'
        },
        BigInt: {
          resolver: '@other/scalars#BigIntResolver',
          type: {
            input: 'bigint',
            output: 'number | string'
          }
        }
      }
    })
  }
}

tsConfigFilePath

string (Default: ./tsconfig.json)

Project's TypeScript config, relative from project root. This helps type analysis such as resolving custom module paths.

fixObjectTypeResolvers

smart or disabled (Default: smart) (Experimental)

Statically compares object type's mapper types' field against schema types' fields, creating resolvers if required

emitLegacyCommonJSImports

bool (Default: true)

Determines whether imports emitted should use CommonJS syntax or ESM syntax (with .js file endings)

Config Examples

1. Custom Config

Custom preset config can be set using the presetConfig option:

# codegen.yml
schema: '**/*.graphql'
generates:
  src/schema:
    preset: '@eddeee888/gcg-typescript-resolver-files'
    presetConfig:
      mode: 'modules'
      resolverTypesPath: './types.gen.ts'
      typeDefsFilePath: false
      typesPluginsConfig: # Pass config you'd normally use for `typescript` and `typescript-resolvers` here
        nonOptionalTypename: false
        federation: true

2. Using TypeScript Config File

If you use codegen.ts, you can use the exported defineConfig function to get better TypeScript support:

import type { CodegenConfig } from '@graphql-codegen/cli';
import { defineConfig } from '@eddeee888/gcg-typescript-resolver-files';

const config: CodegenConfig = {
  schema: '**/schema.graphql',
  generates: {
    'src/schema': defineConfig({
      mode: 'modules',
      resolverTypesPath: './types.gen.ts',
      typeDefsFilePath: false,
      typesPluginsConfig: {
        // Pass config you'd normally use for `typescript` and `typescript-resolvers` here
        nonOptionalTypename: false,
        federation: true,
      },
    }),
  },
};
export default config;

Package Sidebar

Install

npm i @eddeee888/gcg-typescript-resolver-files

Weekly Downloads

6,855

Version

0.8.1

License

MIT

Unpacked Size

216 kB

Total Files

152

Last publish

Collaborators

  • eddeee888