Created
April 26, 2021 01:24
-
-
Save justinfagnani/1c07603073702b9e02b4e3ae2b24cb87 to your computer and use it in GitHub Desktop.
Apollo Controllers
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 {ApolloClient, NormalizedCacheObject} from '@apollo/client/core'; | |
/** | |
* Fired when an Apollo controller is connected to the document tree via its | |
* host. Listeners should supply an Apollo client by setting the `client` | |
* property on the event. | |
*/ | |
export class ApolloControllerConnectedEvent extends Event { | |
static readonly eventName = 'apollo-controller-connected'; | |
client?: ApolloClient<NormalizedCacheObject>; | |
constructor() { | |
super(ApolloControllerConnectedEvent.eventName, { | |
bubbles: true, | |
composed: true, | |
}); | |
} | |
} | |
declare global { | |
interface HTMLElementEventMap { | |
'apollo-controller-connected': ApolloControllerConnectedEvent; | |
} | |
} |
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 { | |
ApolloClient, | |
NormalizedCacheObject, | |
MutationOptions, | |
// TypedDocumentNode, | |
DocumentNode, | |
} from '@apollo/client/core'; | |
import {ApolloControllerConnectedEvent} from './apollo-controller-connected-event.js'; | |
import {Controller, StudioElement} from './st-element.js'; | |
export class MutationController<D = unknown, V = unknown> | |
implements Controller { | |
private _host: StudioElement; | |
private _client?: ApolloClient<NormalizedCacheObject>; | |
private _options: Omit<MutationOptions<D, V>, 'mutation'>; | |
private _mutation: DocumentNode<D, V>; | |
data?: D; | |
// TODO: status: ready | pending | complete | error | |
constructor( | |
host: StudioElement, | |
mutation: DocumentNode<D, V>, | |
options: Omit<MutationOptions<D, V>, 'mutation'> | |
) { | |
this._host = host; | |
this._mutation = mutation; | |
this._options = options; | |
host.addController(this); | |
} | |
async mutate(variables: V) { | |
const result = await this._client!.mutate({ | |
...this._options, | |
variables, | |
mutation: this._mutation, | |
}); | |
this.data = result.data ?? undefined; | |
// TODO: pull other state from the result into instance props | |
this._host.requestUpdate(); | |
return result; | |
} | |
connectedCallback() { | |
const event = new ApolloControllerConnectedEvent(); | |
this._host.dispatchEvent(event); | |
if (event.client === undefined) { | |
throw new Error('No Apollo client found'); | |
} | |
this._client = event.client; | |
} | |
} |
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 {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'; | |
import { | |
ApolloClient, | |
ApolloError, | |
ApolloQueryResult, | |
NormalizedCacheObject, | |
ObservableQuery, | |
WatchQueryOptions, | |
} from '@apollo/client/core'; | |
import {ApolloControllerConnectedEvent} from './apollo-controller-connected-event.js'; | |
import {Controller, StudioElement} from './st-element.js'; | |
export type Options<D = unknown, V = Record<string, any>> = Omit< | |
WatchQueryOptions<D, V>, | |
'query' | |
> & { | |
dataKey?: string; | |
shouldQuery?: () => boolean; | |
getVariables?: () => V; | |
onData?: (data: D) => void; | |
}; | |
export class QueryController<D = unknown, V = Record<string, any>> | |
implements Controller { | |
private _host: StudioElement; | |
private _client?: ApolloClient<NormalizedCacheObject>; | |
private _options?: Options<D, V>; | |
private _query: DocumentNode<D, V>; | |
private _observableQuery?: ObservableQuery<D, V>; | |
private _variables?: V; | |
data?: D; | |
_nextPromise: Promise<D>; | |
_resolveNextPromise!: (data: D) => void; | |
// TODO: status: ready | pending | complete | error | |
constructor( | |
host: StudioElement, | |
query: DocumentNode<D, V>, | |
options?: Options<D, V> | |
) { | |
this._host = host; | |
this._query = query; | |
this._options = options; | |
host.addController(this); | |
this._nextPromise = new Promise((res) => { | |
this._resolveNextPromise = res; | |
}); | |
} | |
update() { | |
const oldVariables = this._variables; | |
const newVariables = this._options?.getVariables?.(); | |
let variablesChanged = false; | |
if ( | |
(oldVariables === undefined && newVariables !== undefined) || | |
(oldVariables !== undefined && newVariables === undefined) | |
) { | |
variablesChanged = true; | |
} else if (newVariables !== undefined && oldVariables !== undefined) { | |
for (const [k, v] of Object.entries(newVariables)) { | |
if (oldVariables[k as keyof V] !== v) { | |
variablesChanged = true; | |
break; | |
} | |
} | |
} | |
if (variablesChanged) { | |
this._variables = newVariables; | |
if (this._observableQuery) { | |
this._observableQuery.refetch(newVariables); | |
} else { | |
this._watch(); | |
} | |
} | |
} | |
private _watch() { | |
if (!(this._options?.shouldQuery?.() ?? true)) { | |
return; | |
} | |
if (this._observableQuery) { | |
// TODO? | |
} else { | |
if (this._client !== undefined) { | |
const variables = this._options?.getVariables?.() ?? this._variables; | |
const options: WatchQueryOptions<V, D> = { | |
// TODO: omit `dataKey` | |
...(this._options as WatchQueryOptions<V, D>), | |
query: this._query, | |
variables, | |
}; | |
this._observableQuery = this._client.watchQuery(options); | |
this._observableQuery.subscribe({ | |
next: this.nextData.bind(this), | |
error: this.nextError.bind(this), | |
}); | |
} | |
} | |
} | |
get next() { | |
return this._nextPromise; | |
} | |
protected nextData(result: ApolloQueryResult<D>): void { | |
this.data = result.data; | |
this._options?.onData?.(result.data); | |
this._host.requestUpdate(this._options?.dataKey); | |
this._resolveNextPromise(this.data); | |
this._nextPromise = new Promise((res) => { | |
this._resolveNextPromise = res; | |
}); | |
// this.error = result.error; | |
// this.errors = result.errors; | |
// this.loading = result.loading; | |
// this.networkStatus = result.networkStatus; | |
// this.partial = result.partial; | |
} | |
protected nextError(error: ApolloError): void { | |
console.error(error); | |
this.error = error; | |
this.loading = false; | |
this._host.requestUpdate(); | |
} | |
connectedCallback() { | |
const event = new ApolloControllerConnectedEvent(); | |
this._host.dispatchEvent(event); | |
// TODO: if client is different, re-subscribe | |
this._client = event.client; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment