Empty currently, but a placeholder issue to gather ideas, requirements, formulate a plan, etc :). (probably this should become a separate library or middleware)
So please chime in with ideas, (pieces) of existing projects, etc!
cc @luisherranz @capaj @kitze
I would love to have something! That would be awesome! I have a project where I would require an integration between mbox-state-tree and GraphQL.
A middleware sounds to be a nice foundation. I will start the integration on my own as I need it for my project. I might leave some feedback
Hi @mweststrate
I spent the afternoon to build a converter. It's not perfect, but a good start.
https://github.com/birkir/graphql-mst
I'm planning on adding some relay connection utils too, or maybe in another package.
@birkir thanks!
@mweststrate thanks for tagging. I wanted to write a bit about graphtype here. I work on sort of a query builder for graphQL. The good thing about it is that it knows what types you are querying at runtime. It can add __typename property on each model.
So it should be trivial to map the graphQL response to MST types essentially giving you free deserialization of your API models.
It's still very much WIP, but if anyone has suggestions how to improve it/feature requests, create them in https://github.com/capaj/graphtype/issues
I've updated my library to use the same codegen utility function (and added support for more types etc.)
re: queries
This is what most people who use MST and GraphQL do today (with various abstraction of course)
const Todo = types.model('Todo', {
id: types.identifier,
text: types.string,
completed: types.boolean,
});
interface GetAllTodosQueryData {
allTodos: Array<{
id: number;
text: string;
completed: boolean;
}>
}
client.query<GetAllTodosQueryData>({
query: gql`
query allTodos($filter: TodoFilter) {
allTodos(filter: $filter) {
id
text
completed
}
}
`,
variables: {
filter: 'SHOW_ALL',
},
}).
.then(result => {
result.data.allTodos.forEach(todoData => {
const item = Todo.create(result.data);
todoStore.addItem(item);
})
});
By using graphql-mst we can move some of the model definitions to shared typings (server, client) by using graphql.
enum TodoFilter {
SHOW_ALL
SHOW_ACTIVE
SHOW_COMPLETED
}
type Todo {
text: String!
completed: Boolean
id: ID!
}
type Query {
allTodos(filter: TodoFilter): [Todo]
}
const { Todo, TodoFilter, Query: { allTodos } } = generateFromSchema(schema);
client.query<typeof allTodos>({
query: gql`
query allTodos($filter: TodoFilter) {
allTodos(filter: $filter) {
id
text
completed
}
}
`,
variables: {
filter: TodoFilter.SHOW_ALL,
}
}).
.then(result => {
result.data.allTodos.forEach(todoData => {
const item = Todo.create(result.data);
todoStore.addItem(item);
})
});
And if we could make queries, automatically query-able, by using something like graphtype to compile them.
enum TodoFilter {
SHOW_ALL
SHOW_ACTIVE
SHOW_COMPLETED
}
type Todo {
text: String!
completed: Boolean
id: ID!
}
type Query {
allTodos(filter: TodoFilter): [Todo]
}
const {
Todo,
TodoFilter,
Query: { allTodos }
} = generateFromSchema(schema);
graphqlMst
.query(allTodos, {
filter: TodoFilter.SHOW_ALL
})
.then(allTodos => {
allTodos.forEach(todo => {
todoStore.addItem(todo); // 'todo' is already an 'Todo' instance
});
});
The issue with this is that you can no longer control what fields you actually want to query, unless a query builder would be provided instead of using the model reference.
Note to self: https://codesandbox.io/s/9orkrrmomy
One issue I've been looking into is how to handle partial data.
My use case is that I want to use mobx-state-tree instead of apollo for handling state from a grahpql api. Apollo is great, but mst would have some advantages over it imo:
Most of the things needed for this to happen are already in place. Add __typename to all models in your queries and use a function to attach data to the correct store according to the type. If everything lives in the same tree you can use identifiers and references and this mostly just works (the company I work for use something like this in production right now).
Mst mostly expects your models to be hydrated with complete data, which doesn't match well with how graphql works. These are some of the challenges we face with our implementation:
I recently watched Rich Hickeys "Maybe Not"-talk, where he (among other things) discuss how Clojure Spec ran into a similar issue. The solution he brings up in his talk is an api for _selecting_ certain properties from a specification.
Maybe something like that could be useful for mst as well?
This is how it might look:
import { types } from 'mst';
const User = types
.model('User', {
id: types.identifier,
firstName: types.string,
lastName: types.string,
username: types.string,
adress: types.string,
phoneNumber: types.string
})
.views(self => ({
get fullName() { return self.firstName + ' ' + self.lastName; }
})
.actions(self => ({
setUsername(username) { self.username = username; }
}));
const UserBrief = User.select(self => {
return {
props: {
id: self.id,
username: self.username
},
actions: {
setUserName: self.setUsername,
onIncompleteDataAccess() {
// a special method that maybe throws or does a request for more data when the
// model doesn't have access to the data that the consumer is asking for
//
// so if you tried to access `fullName` or `phoneNumber` on a UserBrief this method
// would be called.
}
}
}
})
const UserStore = types.model({
users: types.map(types.union(User, UserBrief))
});
So the UserBrief is like an MST analogy to graphql fragment on User objectType. But unlike fragments this can carry it's own runtime methods around. Pretty neat.
the company I work for use something like this in production right now).
Good to hear!
We currently use MST with graphql. All of integrations it's just one function, which transforms server response to model snapshot format. i.e.:
fragment Issue on Issue {
id
start_date
end_date
executor_id: executor { id }
chat_id: chat { id }
}
query loadIssues {
data: issues(filter: {active: true}) {
edges {
node {
...Issue
}
}
}
}
const ItemData = types.model({
id: types.identifier,
start_date: types.maybeNull(types.string),
end_date: types.maybeNull(types.string),
chat: types.reference(Chat),
executor: types.maybeNull(types.reference(User)),
})
import { FetchResult } from 'apollo-link'
export function transform(node: any) {
if (typeof node !== 'object' || !node)
return node
if ('edges' in node)
return (node.edges || []).map((edge: any) => transform(edge.node))
if (node.constructor === Array)
return node.map((el: any) => transform(el))
const newNode: Record<string, any> = {}
for (let [key, value] of Object.entries(node)) {
const pageInfo = value && (value as any).pageInfo
if (pageInfo && value && (value as any).edges)
newNode[key + '_page_info'] = pageInfo
const page_info = value && (value as any).page_info
if (page_info && value && (value as any).edges)
newNode['page_info'] = page_info
if (!key.endsWith('_raw'))
value = transform(value)
else
key = key.replace('_raw', '')
if (key.endsWith('_id')) {
if (value)
value = (value as any).id
key = key.replace('_id', '')
}
if (key.endsWith('_ids') && value) {
if (value)
value = (value as Array<any>).map(x => x.id)
key = key.replace('_ids', '')
}
newNode[key] = value
}
return newNode
}
export default function prepare(val: FetchResult) {
if (val.errors) {
const error = val.errors[0]
error.name = '[GraphQL error]'
throw error
}
let data = transform(val.data)
if (Object.keys(data).length === 1 && data.data)
data = data.data
return data
}
This function handles various use cases, for example - transforms relay connections to simple arrays with page info as sibling, or rename fields.
May be it will helpful for somebody.
Closing this issue as mst-gql is kinda serious now, so all further questions could be handled there: https://github.com/mobxjs/mst-gql/
@Amareis I'm curious if this fit's your use case as well!
@kimgronqvist mst-gql has now a query select builder, as described here: https://github.com/mobxjs/mst-gql#customizing-the-query-result. Hope it solves your problems!
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs or questions.
Most helpful comment
One issue I've been looking into is how to handle partial data.
My use case is that I want to use mobx-state-tree instead of apollo for handling state from a grahpql api. Apollo is great, but mst would have some advantages over it imo:
Most of the things needed for this to happen are already in place. Add
__typenameto all models in your queries and use a function to attach data to the correct store according to the type. If everything lives in the same tree you can use identifiers and references and this mostly just works (the company I work for use something like this in production right now).Mst mostly expects your models to be hydrated with complete data, which doesn't match well with how graphql works. These are some of the challenges we face with our implementation:
I recently watched Rich Hickeys "Maybe Not"-talk, where he (among other things) discuss how Clojure Spec ran into a similar issue. The solution he brings up in his talk is an api for _selecting_ certain properties from a specification.
Maybe something like that could be useful for mst as well?
This is how it might look: