@cran/lib.gql.core
Version:
GQL Core Functionality
144 lines (117 loc) • 4.97 kB
text/typescript
import type { Plugin } from "../plugins/Plugin";
import {
MapperKind, type SchemaMapper, getDirective, mapSchema,
} from "@graphql-tools/utils";
import { isDirective, isSchema } from "graphql";
import { parseLiteral } from "./parse";
export type SchemaMapperType<Kind extends MapperKind | null = null> =
SchemaMapperArgs<Kind>[0];
export type SchemaMapperTypes = {
[Kind in MapperKind]: SchemaMapperType<Kind>;
};
export type SchemaMapperArgs<Kind extends MapperKind | null = null> =
Parameters<NonNullable<Kind extends MapperKind
? SchemaMapper[Kind] : SchemaMapper[keyof SchemaMapper]>>;
export type SchemaMapperFunction<Kind extends MapperKind | null = null> = (
directives: Array<Record<string, unknown>>,
...original: SchemaMapperArgs<Kind>
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
) => SchemaMapperType<Kind> | null | undefined | void;
export type SchemaMapperExtended = {
[Kind in MapperKind]: SchemaMapperFunction<Kind>;
};
const Location: { [Key in MapperKind]: Array<string>; } = {
[MapperKind.TYPE] : [ "SCALAR", "OBJECT", "INTERFACE", "UNION", "ENUM", "INPUT_OBJECT", ],
[MapperKind.SCALAR_TYPE] : [ "SCALAR", ],
[MapperKind.ENUM_TYPE] : [ "ENUM", ],
[MapperKind.COMPOSITE_TYPE] : [ "OBJECT", "INTERFACE", "UNION", ],
[MapperKind.OBJECT_TYPE] : [ "OBJECT", ],
[MapperKind.INPUT_OBJECT_TYPE] : [ "INPUT_OBJECT", ],
[MapperKind.ABSTRACT_TYPE] : [ "INTERFACE", "UNION", ],
[MapperKind.UNION_TYPE] : [ "UNION_TYPE", ],
[MapperKind.INTERFACE_TYPE] : [ "INTERFACE", ],
[MapperKind.ROOT_OBJECT] : [ "SCHEMA", ],
[MapperKind.QUERY] : [ "OBJECT", ],
[MapperKind.MUTATION] : [ "OBJECT", ],
[MapperKind.SUBSCRIPTION] : [ "OBJECT", ],
[MapperKind.ENUM_VALUE] : [ "ENUM_VALUE", ],
[MapperKind.FIELD] : [ "FIELD_DEFINITION", "INPUT_FIELD_DEFINITION", ],
[MapperKind.OBJECT_FIELD] : [ "FIELD_DEFINITION", ],
[MapperKind.ROOT_FIELD] : [ "FIELD_DEFINITION", ],
[MapperKind.QUERY_ROOT_FIELD] : [ "FIELD_DEFINITION", ],
[MapperKind.MUTATION_ROOT_FIELD] : [ "FIELD_DEFINITION", ],
[MapperKind.SUBSCRIPTION_ROOT_FIELD]: [ "FIELD_DEFINITION", ],
[MapperKind.INTERFACE_FIELD] : [ "FIELD_DEFINITION", ],
[MapperKind.COMPOSITE_FIELD] : [ "FIELD_DEFINITION", ],
[MapperKind.ARGUMENT] : [ "ARUGMENT_DEFINITION", ],
[MapperKind.INPUT_OBJECT_FIELD] : [ "INPUT_FIELD_DEFINITION", ],
[MapperKind.DIRECTIVE] : [ "DIRECTIVE", ],
};
function gatherLocations ( keys: Array<MapperKind> ) {
const locations = new Set<string>();
for (const key of keys) {
for (const location of Location[key]) {
locations.add(location);
}
}
return ([ ...locations, ]).join("|");
}
export function createDirective (
name: string, args: Record<string, string>,
mapper: Partial<SchemaMapperExtended>
) {
const argList = Object.entries(args)
.map(function mapArg ( [ key, value, ] ) {
return `${key}:${value}`;
}).join(",");
const argStruct = argList ? `(${argList})` : "";
return {
typeDefs: `directive @${name}${argStruct} on ${
gatherLocations(Object.keys(mapper) as Array<MapperKind>)
}`,
transformer ( schema ) {
return mapSchema(schema, wrapSchemaMapper(name, mapper));
},
} as const as Plugin;
}
function wrapSchemaMapper (
name: string, mapper: Partial<SchemaMapperExtended>
) {
const wrapped: Record<string, unknown> = { };
for (const key in mapper) {
wrapped[key] = wrapMapper(
name, mapper[key as keyof SchemaMapperExtended]! as SchemaMapperFunction
);
}
return wrapped as SchemaMapper;
}
function wrapMapper ( name: string, mapperFunction: SchemaMapperFunction ) {
return function mapper ( ...args: SchemaMapperArgs ) {
const [ type, ] = args;
if (isDirective(type)) {
return mapperFunction([ ], ...args) ?? type;
}
const directives = getDirective(
(args as Array<unknown>).find(isSchema)!, type, name
) ?? getAstDirective(type, name);
if (directives?.[0]) {
return mapperFunction(directives, ...args) ?? type;
}
return type;
};
}
function getAstDirective ( type: SchemaMapperType, name: string ) {
if (type.astNode && "directives" in type.astNode && type.astNode.directives) {
return type.astNode.directives.filter(function filterDir ( directive ) {
return directive.name.value === name;
}).map(function mapDir ( directive ) {
return directive.arguments
?.reduce<Record<string, unknown>>(function reduceArg ( acc, arg ) {
const { name: { value: argName, }, value, } = arg;
acc[argName] = parseLiteral("value" in value ? String(value.value) : "null", value);
return acc;
}, { }) ?? { };
});
}
return [ ];
}