Last active
July 20, 2022 13:24
-
-
Save UberMouse/66bedabd1ca881bef38de7e7893c98c4 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 { Observable } from "rxjs"; | |
import { map } from "rxjs/operators"; | |
import { Required } from "utility-types"; | |
import { createMachine, sendParent, assign, spawn, Actor } from "xstate"; | |
import { $YesReallyAny, assert } from "../"; | |
import { QueryResult } from "../gql/queryResultHandler"; | |
let id = 0; | |
enum Actions { | |
submitData = "submitData", | |
updateParameters = "updateParameters", | |
spawnObservable = "spawnObservable", | |
} | |
type ParentEvents<T> = | |
| { type: "dataLoader.DATA_LOADED"; data: T } | |
| { type: "dataLoader.FAILED_TO_LOAD_DATA" }; | |
export function createDataLoader< | |
TObservableFactory extends (...args: $YesReallyAny[]) => Observable<QueryResult<$YesReallyAny>>, | |
TData = ReturnType<TObservableFactory> extends Observable<QueryResult<infer TResult>> | |
? TResult | |
: never, | |
TParameters = Parameters<TObservableFactory> | |
>(observableFactory: TObservableFactory) { | |
type ObservableEvents = { type: "$NEW_DATA"; data: TData } | { type: "$ERROR" }; | |
type Events = { type: "FETCH"; params: TParameters } | ObservableEvents; | |
type Context = { | |
params: TParameters; | |
fetchImmediately: boolean; | |
observable?: Actor<$YesReallyAny, ObservableEvents>; | |
}; | |
type SpawnedContext = Required<Context, "observable">; | |
type State = | |
| { value: "loading"; context: SpawnedContext } | |
| { value: "error"; context: SpawnedContext } | |
| { value: "loaded"; context: SpawnedContext } | |
| { value: "waitingToFetch"; context: Context }; | |
return createMachine<Context, Events, State>( | |
{ | |
id: `data-loader-${id++}`, | |
initial: "fetchOrNot", | |
context: { params: [] as $YesReallyAny, fetchImmediately: true }, | |
on: { | |
FETCH: { target: "loading", actions: Actions.updateParameters }, | |
$ERROR: "error", | |
$NEW_DATA: { target: "loaded", actions: Actions.submitData }, | |
}, | |
states: { | |
fetchOrNot: { | |
on: { | |
"": [ | |
{ target: "loading", cond: (ctx) => ctx.fetchImmediately }, | |
{ target: "waitingToFetch" }, | |
], | |
}, | |
}, | |
waitingToFetch: {}, | |
loading: { | |
entry: Actions.spawnObservable, | |
}, | |
error: { | |
entry: sendParent<Context, Events, ParentEvents<TData>>("dataLoader.FAILED_TO_LOAD_DATA"), | |
}, | |
loaded: {}, | |
}, | |
}, | |
{ | |
actions: { | |
[Actions.submitData]: sendParent<Context, Events, ParentEvents<TData>>((_ctx, event) => { | |
assert(event.type === "$NEW_DATA"); | |
return { type: "dataLoader.DATA_LOADED", data: event.data }; | |
}), | |
[Actions.updateParameters]: assign({ | |
params: (_ctx, e) => { | |
assert(e.type === "FETCH"); | |
return e.params; | |
}, | |
}), | |
[Actions.spawnObservable]: assign({ | |
observable: ({ params }) => { | |
return spawn( | |
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | |
// @ts-ignore | |
observableFactory(...params).pipe( | |
map( | |
(x: QueryResult<TData>): ObservableEvents => { | |
if (x.type === "error") { | |
return { type: "$ERROR" }; | |
} else { | |
return { type: "$NEW_DATA", data: x.data }; | |
} | |
} | |
) | |
) | |
); | |
}, | |
}), | |
}, | |
} | |
); | |
} | |
export type { ParentEvents as DataLoaderEvents }; |
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
gql` | |
query getRepositories { | |
repositories { | |
id | |
name | |
path | |
branchInfo { | |
branches { | |
commit | |
abbrevCommit | |
name | |
} | |
current | |
} | |
} | |
} | |
`; | |
export function getRepositories(): Observable<QueryResult<Repository[]>> { | |
const query = gqlClient.watchQuery<GetRepositoriesQuery, GetRepositoriesQueryVariables>({ | |
query: GetRepositoriesDocument, | |
}); | |
return handleQueryResult(query, (query) => | |
query.repositories.map((repo) => ({ | |
id: repo.id, | |
name: repo.name, | |
path: repo.path, | |
branchInfo: repo.branchInfo, | |
})) | |
); | |
} | |
export type QueryResult<TTransformedData> = | |
| { type: "success"; data: TTransformedData } | |
| { type: "error"; errors: readonly GraphQLError[] }; | |
export function handleQueryResult<TQueryType, TVariables, TResultType>( | |
query: ObservableQuery<TQueryType, TVariables>, | |
mapper: (queryResult: TQueryType) => TResultType | |
): Observable<QueryResult<TResultType>> { | |
const rxjsResults = new Observable<QueryResult<TResultType>>((observer) => { | |
query.subscribe((result) => { | |
if (result.errors) { | |
observer.next({ type: "error", errors: result.errors }); | |
} else if (!result.loading) { | |
observer.next({ type: "success", data: mapper(result.data) }); | |
} | |
}); | |
}); | |
return rxjsResults; | |
} |
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
const dataLoader = createDataLoader(getRepositories); | |
const machine = createMachine( | |
{ | |
id: "repository-list", | |
initial: "fetching", | |
context: {}, | |
on: { | |
"dataLoader.DATA_LOADED": [ | |
{ | |
target: "#hasRepositories", | |
actions: [Actions.storeLoadedRepos, Actions.markFirstRepoAsActive], | |
cond: (_ctx, e) => e.data.length > 0, | |
}, | |
{ | |
target: "#noRepositories", | |
}, | |
], | |
"dataLoader.FAILED_TO_LOAD_DATA": "fetching.error", | |
}, | |
states: { | |
fetching: { | |
entry: assign({ | |
loader: () => spawn(dataLoader, "dataLoader"), | |
}), | |
initial: "loading", | |
states: { | |
loading: {}, | |
error: {}, | |
}, | |
}, | |
loaded: { | |
}, | |
}, | |
}, | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment