@pothos/core
Version:
Pothos (formerly GiraphQL) is a plugin based schema builder for creating code-first GraphQL schemas in typescript
210 lines (175 loc) • 5.75 kB
text/typescript
import {
type DirectiveNode,
type FieldNode,
type GraphQLDirective,
type GraphQLField,
getArgumentValues,
} from 'graphql';
import { PothosSchemaError, PothosValidationError } from '../errors';
import { InputListRef } from '../refs/input-list';
import { ListRef } from '../refs/list';
import {
type InputType,
type InputTypeParam,
type MaybePromise,
type OutputType,
type PartialResolveInfo,
type PothosOutputFieldConfig,
type SchemaTypes,
type TypeParam,
typeBrandKey,
} from '../types';
export * from './base64';
export * from './context-cache';
export * from './enums';
export * from './input';
export * from './params';
export * from './sort-classes';
export function assertNever(value: never): never {
throw new TypeError(`Unexpected value: ${value}`);
}
export function assertArray(value: unknown): value is unknown[] {
if (!Array.isArray(value)) {
throw new PothosValidationError('List resolvers must return arrays');
}
return true;
}
export function isThenable(value: unknown): value is PromiseLike<unknown> {
return !!(
value &&
(typeof value === 'object' || typeof value === 'function') &&
typeof (value as Record<string, unknown>).then === 'function'
);
}
export function verifyRef(ref: unknown) {
if (ref === undefined) {
throw new PothosSchemaError(`Received undefined as a type ref.
This is often caused by a circular import
If this ref is imported from a file that re-exports it (like index.ts)
you may be able to resolve this by importing it directly from the file that defines it.
`);
}
}
export function verifyInterfaces(interfaces: unknown) {
if (!interfaces || typeof interfaces === 'function') {
return;
}
if (!Array.isArray(interfaces)) {
throw new PothosSchemaError('interfaces must be an array or function');
}
for (const iface of interfaces) {
if (iface === undefined) {
throw new PothosSchemaError(`Received undefined in list of interfaces.
This is often caused by a circular import
If this ref is imported from a file that re-exports it (like index.ts)
you may be able to resolve this by importing it directly from the file that defines it.
Alternatively you can define interfaces with a function that will be lazily evaluated,
which may resolver issues with circular dependencies:
Example:
builder.objectType('MyObject', {
interface: () => [Interface1, Interface2],
...
});
`);
}
}
}
export function brandWithType<Types extends SchemaTypes>(val: unknown, type: OutputType<Types>) {
if (typeof val !== 'object' || val === null) {
return;
}
Object.defineProperty(val, typeBrandKey, {
enumerable: false,
value: type,
});
}
export function getTypeBrand(val: unknown) {
if (typeof val === 'object' && val !== null && typeBrandKey in val) {
return (val as { [typeBrandKey]: OutputType<SchemaTypes> })[typeBrandKey];
}
return null;
}
export function unwrapListParam<Types extends SchemaTypes>(
param: InputTypeParam<Types> | TypeParam<Types>,
): InputType<Types> | OutputType<Types> {
if (Array.isArray(param)) {
return unwrapListParam(param[0]);
}
if (param instanceof ListRef || param instanceof InputListRef) {
return unwrapListParam(param.listType as TypeParam<Types>);
}
return param;
}
export function unwrapOutputListParam<Types extends SchemaTypes>(
param: TypeParam<Types>,
): OutputType<Types> {
if (Array.isArray(param)) {
return unwrapOutputListParam(param[0]);
}
if (param instanceof ListRef) {
return unwrapOutputListParam(param.listType as TypeParam<Types>);
}
return param;
}
export function unwrapInputListParam<Types extends SchemaTypes>(
param: InputTypeParam<Types>,
): InputType<Types> {
if (Array.isArray(param)) {
return unwrapInputListParam(param[0]);
}
if (param instanceof InputListRef) {
return unwrapInputListParam(param.listType as InputTypeParam<Types>);
}
return param;
}
/**
* Helper for allowing plugins to fulfill the return of the `next` resolver, without paying the cost of the
* Promise if not required.
*/
export function completeValue<T, R>(
valOrPromise: PromiseLike<T> | T,
onSuccess: (completedVal: T) => PromiseLike<R> | R,
onError?: (errVal: unknown) => PromiseLike<R> | R,
): Promise<Awaited<R>> | Awaited<R> {
if (isThenable(valOrPromise)) {
return Promise.resolve(valOrPromise).then(onSuccess, onError) as Promise<Awaited<R>>;
}
// No need to handle onError, this should just be a try/catch inside the `onSuccess` block
const result = onSuccess(valOrPromise);
// If the result of the synchronous call is a promise like, convert to a promise
// for consistency
if (isThenable(result)) {
return Promise.resolve(result);
}
return result as Awaited<R>;
}
export function getMappedArgumentValues(
def: GraphQLDirective | GraphQLField<unknown, unknown>,
node: DirectiveNode | FieldNode,
context: object,
info: PartialResolveInfo,
) {
const args = getArgumentValues(def, node, info.variableValues);
const mappers = def.extensions?.pothosArgMappers as
| PothosOutputFieldConfig<SchemaTypes>['argMappers']
| undefined;
if (mappers && mappers.length > 0) {
return reduceMaybeAsync(mappers, args, (acc, argMapper) => argMapper(acc, context, info));
}
return args;
}
export function reduceMaybeAsync<T, R>(
items: T[],
initialValue: R,
fn: (value: R, item: T, i: number) => MaybePromise<R>,
): MaybePromise<R> {
function next(value: R, i: number): MaybePromise<R> {
if (i === items.length) {
return value;
}
return completeValue(fn(value, items[i], i), (result) => {
return result === null ? (null as R) : next(result, i + 1);
});
}
return next(initialValue, 0);
}