Skip to content

Commit 9d2ab25

Browse files
committed
Split source up into separate files
1 parent 926078d commit 9d2ab25

10 files changed

+249
-237
lines changed

.prettierrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"bracketSpacing": false,
2+
"bracketSpacing": true,
33
"printWidth": 80,
44
"proseWrap": "never",
55
"requirePragma": false,

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Bindings for mobx-state-tree and GraphQL
44

55
---
66

7+
Alpha / WIP
8+
9+
Looking for maintainers among active GraphQL / MST users!
10+
711
Why
812

913
Pro:

src/MSTGQLObject.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { types, getParent } from "mobx-state-tree"
2+
3+
import { MSTGQLStore } from "./MSTGQLStore"
4+
5+
export const MSTGQLObject = types
6+
.model("MSTGQLObject", {
7+
__typename: types.string,
8+
id: types.identifier
9+
})
10+
.views(self => ({
11+
get store(): typeof MSTGQLStore.Type {
12+
return getParent(self, 2)
13+
}
14+
}))

src/MSTGQLStore.ts

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { SubscriptionClient } from "subscriptions-transport-ws"
2+
import { types, getEnv, recordPatches } from "mobx-state-tree"
3+
import { GraphQLClient } from "graphql-request"
4+
import { action, extendObservable, observable } from "mobx"
5+
6+
import { QueryOptions, QueryResult, CaseHandlers } from "./types"
7+
import { mergeHelper } from "./mergeHelper"
8+
import { getFirstValue } from "./utils"
9+
10+
export const MSTGQLStore = types.model("MSTGQLStore").actions(self => {
11+
const {
12+
gqlHttpClient,
13+
gqlWsClient
14+
}: { gqlHttpClient: GraphQLClient; gqlWsClient: SubscriptionClient } = getEnv(
15+
self
16+
)
17+
if (!gqlHttpClient && !gqlWsClient)
18+
throw new Error(
19+
"Either gqlHttpClient or gqlWsClient (or both) should provided in the MSTGQLStore environment"
20+
)
21+
22+
function merge(data: unknown) {
23+
if (Array.isArray(data)) return data.map(item => mergeHelper(self, item))
24+
else return mergeHelper(self, data)
25+
}
26+
27+
function makeSingleRequest(query: string, variables: any): Promise<any> {
28+
if (gqlHttpClient) return gqlHttpClient.request(query, variables)
29+
else {
30+
return new Promise((resolve, reject) => {
31+
gqlWsClient
32+
.request({
33+
query,
34+
variables
35+
})
36+
.subscribe({
37+
next(data) {
38+
resolve(data.data)
39+
},
40+
error: reject
41+
})
42+
})
43+
}
44+
}
45+
46+
function query<T>(
47+
query: string,
48+
variables?: any,
49+
options: QueryOptions = {}
50+
): QueryResult<T> {
51+
// TODO: support options.headers
52+
// TODO: support options.cacheStrategy
53+
const req = makeSingleRequest(query, variables)
54+
55+
const handleSuccess = action(data => {
56+
const value = getFirstValue(data)
57+
if (options.raw) {
58+
promise.fetching = false
59+
return Promise.resolve((promise.data = value))
60+
} else {
61+
try {
62+
promise.fetching = false
63+
const normalized = (self as any).merge(value)
64+
return Promise.resolve((promise.data = normalized))
65+
} catch (e) {
66+
return Promise.reject((promise.error = e))
67+
}
68+
}
69+
})
70+
71+
const handleFailure = action(error => {
72+
promise.fetching = false
73+
return Promise.reject((promise.error = error))
74+
})
75+
76+
const promise: QueryResult<T> = req.then(
77+
handleSuccess,
78+
handleFailure
79+
) as any
80+
extendObservable(
81+
promise,
82+
{
83+
fetching: true,
84+
data: undefined,
85+
error: undefined,
86+
refetch() {
87+
// refech returs the old observable states
88+
promise.fetching = false
89+
return makeSingleRequest(query, variables).then(
90+
handleSuccess,
91+
handleFailure
92+
)
93+
},
94+
case<R>(handlers: CaseHandlers<T, R>): R {
95+
return promise.fetching && !promise.data
96+
? handlers.fetching()
97+
: promise.error
98+
? handlers.error(promise.error)
99+
: handlers.data(promise.data!)
100+
}
101+
} as any,
102+
{ data: observable.ref }
103+
)
104+
return promise
105+
}
106+
107+
function mutate<T>(
108+
mutation: string,
109+
params?: any,
110+
optimisticUpdate?: () => void
111+
): QueryResult<T> {
112+
if (optimisticUpdate) {
113+
const recorder = recordPatches(self)
114+
optimisticUpdate()
115+
recorder.stop()
116+
const promise = query<T>(mutation, params)
117+
promise.catch(e => {
118+
recorder.undo()
119+
})
120+
return promise
121+
} else {
122+
return query(mutation, params)
123+
}
124+
}
125+
126+
function subscribe(query: string, variables?: any): () => void {
127+
if (!gqlWsClient) throw new Error("No WS client available")
128+
const sub = gqlWsClient
129+
.request({
130+
query,
131+
variables
132+
})
133+
.subscribe({
134+
next(data) {
135+
;(self as any).merge(getFirstValue(data.data))
136+
}
137+
})
138+
return () => sub.unsubscribe()
139+
}
140+
141+
return { merge, mutate, query, subscribe }
142+
})

src/createHttpClient.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { GraphQLClient } from "graphql-request"
2+
3+
export type HttpClientOptions = ConstructorParameters<typeof GraphQLClient>[1]
4+
5+
export function createHttpClient(url: string, options: HttpClientOptions = {}) {
6+
return new GraphQLClient(url, options)
7+
}

src/mergeHelper.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { typenameToCollectionName } from "./reflection"
2+
3+
export function mergeHelper(store: any, itemData: any) {
4+
const { __typename, id } = itemData
5+
if (__typename === undefined)
6+
throw new Error(
7+
"__typename field is not available on " + JSON.stringify(itemData)
8+
)
9+
if (id === undefined)
10+
throw new Error("id field is not available on " + JSON.stringify(itemData))
11+
const collection = typenameToCollectionName(__typename)
12+
const current = store[collection].get(id)
13+
if (!current) {
14+
store[collection].set(id, itemData)
15+
return store[collection].get(id)
16+
} else {
17+
// TODO: merge should be recursive for complex values
18+
Object.assign(current, itemData)
19+
return current
20+
}
21+
}

0 commit comments

Comments
 (0)