Skip to content

Instantly share code, notes, and snippets.

@UberMouse
Created October 29, 2020 20:53
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save UberMouse/62631a290a7fbbb6759ffa0344d68039 to your computer and use it in GitHub Desktop.
Save UberMouse/62631a290a7fbbb6759ffa0344d68039 to your computer and use it in GitHub Desktop.
GraphQL + Apollo + XState dump
type QueryResult<TTransformedData> =
| { type: "success"; data: TTransformedData }
| { type: "error"; errors: readonly GraphQLError[] };
type ParentEvents<TData, TId extends string = "dataLoader"> =
| { type: "dataStream.NEW_DATA"; data: TData; id: TId }
| { type: "dataStream.ERROR"; id: TId };
export function createDataStream<
TObservableFactory extends (...args: $YesReallyAny[]) => Observable<QueryResult<$YesReallyAny>>,
TData = ReturnType<TObservableFactory> extends Observable<QueryResult<infer TResult>>
? TResult
: never,
TParameters = Parameters<TObservableFactory>[0]
>(observableFactory: TObservableFactory, dataStreamId = "dataStream") {
return (params: TParameters) =>
observableFactory(params).pipe(
map(
(result): ParentEvents<TData, typeof dataStreamId> => {
if (result.type === "error") {
return { type: "dataStream.ERROR", id: dataStreamId };
}
return { type: "dataStream.NEW_DATA", data: result.data, id: dataStreamId };
}
)
);
}
gql`
mutation commit($repoId: String!, $message: String!, $datasets: [String!]!) {
commit(id: $repoId, message: $message, datasets: $datasets)
}
`;
async function basePerformCommit(variables: CommitMutationVariables): Promise<boolean> {
const mutation = gqlClient.mutate<CommitMutation, CommitMutationVariables>({
mutation: CommitDocument,
variables,
refetchQueries: [{ query: GetUncommitedChangesDocument }, { query: GetTimelineDataDocument }],
});
return newHandleMutationResult(mutation, (res) => res.commit);
}
export const performCommit = attachRepoId(basePerformCommit);
gql`
query haveCommitDetails($repoId: String!) {
currentReposheetId @client @export(as: "repoId")
repository2(id: $repoId) {
... on ReadyRepository {
userDetailsSet
}
}
}
`;
export function haveCommitDetails(): GqlQuery<boolean> {
const query = gqlClient.watchQuery<HaveCommitDetailsQuery>({
query: HaveCommitDetailsDocument,
fetchPolicy: "network-only",
});
return newHandleQueryResult(query, (q) => {
assert(q.repository2.__typename === "ReadyRepository");
return q.repository2.userDetailsSet;
});
}
export type QueryResultContainer<TGqlData, TVariables, TTransformedData> = {
query: QueryWrapper<TGqlData, TVariables>;
results: Observable<QueryResult<TTransformedData>>;
};
export function handleQueryResult<TQueryType, TVariables, TResultType>(
query: ObservableQuery<TQueryType, TVariables>,
mapper: (queryResult: TQueryType) => TResultType
): QueryResultContainer<TQueryType, TVariables, TResultType> {
const rxjsResults = new Observable<QueryResult<TResultType>>((observer) => {
const subscription = query.subscribe((result) => {
if (result.errors) {
console.debug("GraphQL error: ", result.errors);
observer.next({ type: "error", errors: result.errors });
} else if (!result.loading) {
observer.next({ type: "success", data: mapper(result.data) });
}
});
return () => subscription.unsubscribe();
});
return { query, results: rxjsResults };
}
export function newHandleQueryResult<TQueryType, TVariables, TResultType>(
query: ObservableQuery<TQueryType, TVariables>,
mapper: (queryResult: TQueryType) => TResultType
): Observable<QueryResult<TResultType>> {
return handleQueryResult(query, mapper).results;
}
function MutationError(
this: { message: string; graphqlErrors: GraphQLError[]; name: string },
message: string,
graphqlErrors: GraphQLError[]
) {
this.name = "MutationError";
this.message = message;
this.graphqlErrors = graphqlErrors;
}
MutationError.prototype = Error.prototype;
export function newHandleMutationResult<TQueryType, TResultType>(
mutation: Promise<FetchResult<TQueryType>>,
mapper: (mutationResult: TQueryType) => TResultType
): Promise<TResultType> {
return mutation.then((result) => {
if (result.errors) {
// @ts-ignore what are you even on about TypeScript?
throw new MutationError("GraphQL mutation failed", result.errors);
}
return mapper(result.data!);
});
}
const haveCommitDetailsStream = createDataStream(haveCommitDetails, "commitDetails");
export const machine = createMachine<Context, Events, States>(
{
type: "parallel",
context: {
},
states: {
ui: {
initial: "loading",
states: {
loading: {
on: {
"dataStream.ERROR": "error",
"dataStream.NEW_DATA": [
{
target: "loaded.uncommittedChanges",
cond: Guards.hasUncommitedChanges,
},
{ target: "loaded.noChanges" },
],
},
},
error: {},
loaded: {}
},
},
data: {
invoke: {
src: Services.changes,
},
initial: "loading",
states: {
loading: {
on: {
"dataStream.ERROR": "error",
"dataStream.NEW_DATA": {
target: "loaded",
actions: Actions.storeChanges,
},
},
},
error: {},
loaded: {
invoke: {
src: Services.commitDetails,
},
on: {
"dataStream.NEW_DATA": [
{
actions: Actions.storeChanges,
cond: Guards.isChangesStream,
},
{ actions: Actions.storeUserCommitDetails },
],
},
},
},
},
},
},
{
actions: {
},
guards: {
},
services: {
[Services.commitDetails]: () => haveCommitDetailsStream(undefined),
},
}
);
@veeramarni
Copy link

HI, Can you please elaborate on createDataStream.ts file? I'm getting observableFactory(...).pipe is not a function

import { GraphQLError } from "graphql/error";
import { Observable } from '@apollo/client/core';
import { map } from 'rxjs/operators';

type QueryResult<TTransformedData> =
  | { type: "success"; data: TTransformedData }
  | { type: "error"; errors: readonly GraphQLError[] };

type ParentEvents<TData, TId extends string = "dataLoader"> =
  | { type: "dataStream.NEW_DATA"; data: TData; id: TId }
  | { type: "dataStream.ERROR"; id: TId };

export function createDataStream<
  TObservableFactory extends (...args: any[]) => Observable<QueryResult<any>>,
  TData = ReturnType<TObservableFactory> extends Observable<QueryResult<infer TResult>>
    ? TResult
    : never,
  TParameters = Parameters<TObservableFactory>[0]
>(observableFactory: TObservableFactory, dataStreamId = "dataStream") {
  return (params: TParameters) =>
    (observableFactory(params) as any).pipe(
      map(
        (result: any): ParentEvents<TData, typeof dataStreamId> => {
          if (result.type === "error") {
            return { type: "dataStream.ERROR", id: dataStreamId };
          }

          return { type: "dataStream.NEW_DATA", data: result.data, id: dataStreamId };
        }
      )
    );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment