Created
May 28, 2019 05:43
-
-
Save flisboac/22a7134e4c38aa47b2d12bd7a36faf7e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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