Skip to content

Instantly share code, notes, and snippets.

@flisboac
Created May 28, 2019 05:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flisboac/22a7134e4c38aa47b2d12bd7a36faf7e to your computer and use it in GitHub Desktop.
Save flisboac/22a7134e4c38aa47b2d12bd7a36faf7e to your computer and use it in GitHub Desktop.
import { Injectable } from '@angular/core';
import API, { graphqlOperation } from '@aws-amplify/api';
import * as Observable from 'zen-observable';
import { GraphQLResult } from '@aws-amplify/api/lib/types';
export type GqlOperationKind = 'query' | 'mutation' | 'subscription';
export type GqlKindFromOper<O extends GqlOperation<any>> = O extends GqlOperation<any> ? GqlOperation<any>['kind'] : never;
export type GqlResultFromOper<O extends GqlOperation<any>> = O extends GqlOperation<infer R> ? R : never;
export type GqlParamsFromOper<O extends GqlOperation<any>> = O extends GqlOperation<any, infer P> ? P : never;
export type GqlVariablesFromOper<O extends GqlOperation<any>> = O extends GqlOperation<any, any, infer V> ? V : never;
export interface GqlResult<TResult extends object> {
data?: TResult;
errors?: object[];
extensions?: {
[key: string]: any;
};
}
export interface GqlOperationContext {
operator: GqlOperator;
errors?: object[];
extensions?: {
[key: string]: any;
};
}
export interface GqlOperationConstructor<
TOper extends GqlOperation<any>,
TParams extends object = GqlParamsFromOper<TOper>,
TVariables extends object = GqlVariablesFromOper<TOper>,
> {
readonly kind?: GqlOperationKind;
new (params: TParams): TOper;
}
export interface GqlValueOperationConstructor<
TOper extends GqlValueOperation<any>,
TParams extends object = GqlParamsFromOper<TOper>,
TVariables extends object = GqlVariablesFromOper<TOper>,
> extends GqlOperationConstructor<TOper, TParams, TVariables> {
readonly kind?: 'query' | 'mutation';
}
export interface GqlSubscriptionOperationConstructor<
TOper extends GqlSubscriptionOperation<any>,
TParams extends object = GqlParamsFromOper<TOper>,
TVariables extends object = GqlVariablesFromOper<TOper>,
> extends GqlOperationConstructor<TOper, TParams, TVariables> {
readonly kind?: 'subscription';
}
export interface GqlOperation<
TResult,
TParams extends object = object,
TVariables extends object = object,
> {
readonly kind: GqlOperationKind;
readonly field?: string;
readonly query: string;
readonly params?: TParams;
readonly variables?: TVariables; // Query arguments
convert(operationResult: any, context: GqlOperationContext): Promise<TResult>;
}
export interface GqlValueOperation<
TResult,
TParams extends object = object,
TVariables extends object = object,
> extends GqlOperation<TResult, TParams, TVariables> {
readonly kind: 'query' | 'mutation';
}
export interface GqlSubscriptionOperation<
TResult,
TParams extends object = object,
TVariables extends object = object,
> extends GqlOperation<TResult, TParams, TVariables> {
readonly kind: 'subscription';
}
export interface GqlOperator {
runOperation<TOper extends GqlValueOperation<any>>(oper: TOper): Promise<GqlResultFromOper<TOper>>;
runOperation<TOper extends GqlSubscriptionOperation<any>>(oper: TOper): Promise<Observable<GqlResultFromOper<TOper>>>;
runOperation<TOper extends GqlOperation<any>>(oper: TOper): Promise<GqlResultFromOper<TOper> | Observable<GqlResultFromOper<TOper>>>;
run<
TOper extends GqlValueOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlValueOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<GqlResultFromOper<TOper>>;
run<
TOper extends GqlSubscriptionOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlSubscriptionOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<Observable<GqlResultFromOper<TOper>>>;
run<
TOper extends GqlOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<GqlResultFromOper<TOper> | Observable<GqlResultFromOper<TOper>>>;
}
export class GqlError<TResult extends GraphQLResult = any> extends Error {
constructor(message: string, public result?: TResult) {
super(message);
}
get errors() { return this.result ? this.result.errors : undefined; }
}
export function getGqlResultData(
result?: GraphQLResult
) {
if (!result) throw new GqlError('The GQL query yielded no result.');
if (result.errors) throw new GqlError('The GQL query resulted in errors.', result);
if (!result.data) throw new GqlError('The GQL query result has no data.', result);
return result.data;
}
export function getGqlResultDataField(
result?: GraphQLResult,
fieldName?: string,
) {
const data = getGqlResultData(result);
const fields = Object.keys(data);
const value =
fieldName ? data[fieldName]
: fields.length === 1 ? data[fields[0]]
: data;
return value;
}
export function getGqlResultErrors<TResult extends object>(
result?: GqlResult<TResult>
) {
if (!result) return undefined;
if (result.errors && result.errors.length > 0) return result.errors;
return undefined;
}
@Injectable({
providedIn: 'root',
})
export class GqlApiService implements GqlOperator {
runOperation<TOper extends GqlValueOperation<any>>(oper: TOper): Promise<GqlResultFromOper<TOper>>;
runOperation<TOper extends GqlSubscriptionOperation<any>>(oper: TOper): Promise<Observable<GqlResultFromOper<TOper>>>;
runOperation<TOper extends GqlOperation<any>>(oper: TOper): Promise<GqlResultFromOper<TOper> | Observable<GqlResultFromOper<TOper>>>;
async runOperation<TOper extends GqlOperation<any>>(oper: TOper) {
const { kind, query, variables, params } = oper;
const operator = this;
const operation = graphqlOperation(query, variables || params || {});
switch (kind) {
case 'query':
case 'mutation': {
const result = await (API.graphql(operation) as Promise<GraphQLResult>);
const errors = getGqlResultErrors(result);
const value = getGqlResultDataField(result);
return oper.convert(value, { operator, errors });
}
case 'subscription': {
const observable = API.graphql(operation) as Observable<object>;
return observable.map(value => oper.convert(value, { operator }));
}}
throw new GqlError(`Unknown operation kind "${kind}".`);
}
run<
TOper extends GqlValueOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlValueOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<GqlResultFromOper<TOper>>;
run<
TOper extends GqlSubscriptionOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlSubscriptionOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<Observable<GqlResultFromOper<TOper>>>;
run<
TOper extends GqlOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<GqlResultFromOper<TOper> | Observable<GqlResultFromOper<TOper>>>;
async run<
TOper extends GqlOperation<any>,
TParams extends GqlParamsFromOper<TOper> = GqlParamsFromOper<TOper>,
>(
operClass: GqlOperationConstructor<TOper, TParams>,
params: TParams,
): Promise<GqlResultFromOper<TOper> | Observable<GqlResultFromOper<TOper>>> {
const oper = new operClass(params);
return await this.runOperation(oper);
}
}
class GetStructureTypeOper {
kind = 'query' as const;
query = '';
constructor(
public params: { id: string },
) {
this.query = `
${this.kind} (
$id: String
) {
getStructureType($id) {
id
# ...
}
}
`;
}
async convert(operationResult: { id: string }) {
return operationResult;
}
}
declare const o: GqlOperator;
const r = o.run(GetStructureTypeOper, { id: '1' });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment