mobxjs / mst-gql Goto Github PK
View Code? Open in Web Editor NEWBindings for mobx-state-tree and GraphQL
License: MIT License
Bindings for mobx-state-tree and GraphQL
License: MIT License
Are there any examples that use classes? (and decorators?) vs hooks? Is that possible? I'm looking to take baby steps...
I have a scalar DateTime
that I'd love to teach mst-gql to treat as a Date.
So far I've tried editing my models/<item>Model.ts
to have:
export const ItemModel = ItemModelBase.props({
createdAt: types.maybe(types.Date),
})
and that much seems to be working, however I'm not sure where to put the code that takes the string returned from the server and transforms it into a Date.
In the Twitter example I see this code:
loadMessages(offset, count, replyTo = undefined) {
const query = self.queryMessages(
{ offset, count, replyTo },
MESSAGE_FRAGMENT
)
query.then(data => {
self.sortedMessages.push(...data)
})
return query
}
I assumed that the auto-generated self.queryMessages
would update the MST store, but here I see it being done manually, so I take it this is not the case?
If not, why not? Seems like the codebase will be full of these manual updates to the store, whereas a library such as Apollo Client will update its store itself upon each query.
UPDATE: In the README I see the store does auto update after each query. So why does loadMessages
in the example above manually add messages to the store?
https://github.com/zenflow/mst-gql-next/tree/demo-bug-stale-data-reused
I removed almost all of the code unrelated to demonstrating this bug. Pardon the next.js integration bit; it has zero impact here.
The relevant parts are in server/schema.js and pages/index.js
doneTodos
.You can see that the todo items I toggled have been reverted to their original state. 💩
This example is using the default fetchPolicy
"cache-and-network"
, so immediately after clicking "Show", the stale cached query (from when we opened the "Done Todos" the first time) is used for an instant before the freshly fetched query is received, and in using it, (here's the problem), it's model properties/data are written to the "living" model instances in store.todos
.
The bug is easier to observe with the "cache-first"
fetchPolicy
, but I wanted to demonstrate that this is fairly severe bug in the typical/basic usage (i.e. not doing anything special).
I believe mst-gql should simply (?) never copy data from the __queryCache onto root type model instances, since that data will never be more fresh than the data the model instance already has (from other queries, and even optimistic updates). Really it seems that __queryCache shouldn't need to keep properties/data of root types at all, and in the case of query results that are an array of root types, the entry value (in __queryCache) should be just an array of IDs. That would also make things much more efficient when serializing/deserializing state for SSR, localStorage, etc.
Is this likely to be fixed?
BTW, it would be awesome if mst-gql had a model instance for the root graphql type Query {}
that worked generally the same as the other graphql models, and thus would:
done
property) add/remove it to/from the store.rootQuery.doneTodos
array (of course only if store.rootQuery.hasLoaded('doneTodos')
) and then our list of "Done Todos" would stay up to date as we make changes!Thanks for your work on a super awesome and powerful package. ❤️ 💪
I'm using MST-GQL in my app and I have a "currentUser" store where I have a login function which receives and sets the access token. I want to be able to use this access token in the "Auth Bearer" header for all further requests. How do I set the header after the RootStore has been initialised?
I think it would be useful to infer the types of return values from the query builder. Here's an example from the README:
msg => msg
.timestamp
.text
.user(user => user.name.avatar)
.likes()
After a quick glance at the source code, I think the builder is not currently used in any way to infer the types of its results. However, I think it would be possible in TS to do that.
I hacked around a little proof of concept that shows how a builder could be written to support type inference:
What do you think? Let me know please if it's something you might be interested in in the future.
Depends on #8
Currently, all fields (except id
and __typename
) are generated to be a types.maybe
, as they might never be instantiated.
However, in strict typescript, this will require a lot of non-null assertions.
Also, it can be very good to make it mandatory that queries for a certain type also fetch a certain field.
So it would be could to have an option, for example, that specifies mandatory fields, for example: --mandatoryFields 'User.name,Todo.title'
.
I tried to use mst-gql with a Prisma generated schema today and ran into the following error:
Error: Not implemented printTsType yet, PR welcome for {
"kind": "INPUT_OBJECT",
"name": "ItemWhereUniqueInput",
"ofType": null
}
when looking at my schema I see:
input ItemWhereUniqueInput {
id: ID
}
Hi! Thanks for this project! I know this is experimental software, but is that because there's no maintainers or because it's not ready to be used in production?
I haven't had any issues with it, so I feel comfortable integrating it into my app; let me know if this is a mistake.
Currently, GraphQL Enum types are converted into MST's types.enumeration. However, there are still some open ends in supporting enums to use them in queries:
generateFragments
in generate.jsgenerateQueryBuilder
in generate.jsIf anyone would be able to pick this up, great. Otherwise I might do this later.
Hi,
I'm trying to play with the examples in the master branch. As a first step I just tried to run the top level tests for the project but it failed like this:
> yarn
...
> yarn build
...
> yarn test
yarn run v1.7.0
$ jest test && cd examples/2-scaffolding && yarn start
PASS tests/generator/generate.test.js
PASS tests/lib/todos/todostore.test.js
PASS tests/lib/abstractTypes/abstractTypes.test.js
Test Suites: 3 passed, 3 total
Tests: 8 passed, 8 total
Snapshots: 7 passed, 7 total
Time: 1.937s, estimated 4s
Ran all test suites matching /test/i.
warning package.json: No license field
$ yarn scaffold && yarn build
warning package.json: No license field
$ ../../generator/mst-gql-scaffold.js --force --roots 'Pokemon, Attack' --format ts --outDir src/models graphql-schema.json
mst-gql-scaffold.js --format=ts --outDir=<my_path>/mst-gql/examples/2-scaffolding/src/models graphql-schema.json
Detected types:
- [OBJECT] Query
- [SCALAR] Int
- [OBJECT] Pokemon
- [SCALAR] ID
- [SCALAR] String
- [OBJECT] PokemonDimension
- [OBJECT] PokemonAttack
- [OBJECT] Attack
- [SCALAR] Float
- [OBJECT] PokemonEvolutionRequirement
- [OBJECT] __Schema
- [OBJECT] __Type
- [ENUM] __TypeKind
- [SCALAR] Boolean
- [OBJECT] __Field
- [OBJECT] __InputValue
- [OBJECT] __EnumValue
- [OBJECT] __Directive
- [ENUM] __DirectiveLocation
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonModel.base.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonModel.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonDimensionModel.base.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonDimensionModel.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonAttackModel.base.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonAttackModel.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/AttackModel.base.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/AttackModel.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonEvolutionRequirementModel.base.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/PokemonEvolutionRequirementModel.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/RootStore.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/RootStore.base.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/reactUtils.ts
Writing file <my_path>/mst-gql/examples/2-scaffolding/src/models/index.ts
warning package.json: No license field
$ tsc
src/models/AttackModel.base.ts:6:44 - error TS2307: Cannot find module 'mst-gql'.
6 import { MSTGQLObject, QueryBuilder } from "mst-gql"
~~~~~~~~~
src/models/AttackModel.base.ts:27:10 - error TS7006: Parameter 'self' implicitly has an 'any' type.
27 .views(self => ({
~~~~
src/models/AttackModel.base.ts:29:14 - error TS2347: Untyped function calls may not accept type arguments.
29 return self.__getStore<typeof RootStore.Type>()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/models/AttackModel.base.ts:34:28 - error TS2339: Property '__attr' does not exist on type 'AttackModelSelector'.
34 get name() { return this.__attr(`name`) }
~~~~~~
src/models/AttackModel.base.ts:35:28 - error TS2339: Property '__attr' does not exist on type 'AttackModelSelector'.
35 get type() { return this.__attr(`type`) }
~~~~~~
src/models/AttackModel.base.ts:36:30 - error TS2339: Property '__attr' does not exist on type 'AttackModelSelector'.
36 get damage() { return this.__attr(`damage`) }
~~~~~~
...
I also tried to run the 1-getting-started example:
> yarn
...
> yarn start
yarn run v1.17.3
$ run-p start:server start:client
$ sleep 1 && yarn scaffold && webpack-dev-server --hot --inline
$ node ./src/server/index.js
$ ../../generator/mst-gql-scaffold.js --roots 'Todo' --excludes 'CacheControlScope,Query,Subscription' --outDir src/app/models/ --format ts http://localhost:3001/graphql
🚀 Server ready at http://localhost:3001/graphql
🚀 Subscriptions ready at ws://localhost:3001/graphql
mst-gql-scaffold.js --format=ts --outDir=<my_path>/mst-gql/examples/1-getting-started/src/app/models http://localhost:3001/graphql
› Warning: apollo update available from 2.9.0 to 2.16.3.
Detected types:
- [OBJECT] Query
- [OBJECT] Todo
- [SCALAR] ID
- [SCALAR] String
- [SCALAR] Boolean
- [OBJECT] Mutation
- [INPUT_OBJECT] CreateTodoInput
- [OBJECT] __Schema
- [OBJECT] __Type
- [ENUM] __TypeKind
- [OBJECT] __Field
- [OBJECT] __InputValue
- [OBJECT] __EnumValue
- [OBJECT] __Directive
- [ENUM] __DirectiveLocation
- [ENUM] CacheControlScope
- [SCALAR] Upload
- [SCALAR] Int
- [SCALAR] Float
Writing file <my_path>/mst-gql/examples/1-getting-started/src/app/models/TodoModel.base.ts
Skipping file <my_path>/mst-gql/examples/1-getting-started/src/app/models/TodoModel.ts
Skipping file <my_path>/mst-gql/examples/1-getting-started/src/app/models/RootStore.ts
Writing file <my_path>/mst-gql/examples/1-getting-started/src/app/models/RootStore.base.ts
Writing file <my_path>/mst-gql/examples/1-getting-started/src/app/models/reactUtils.ts
Writing file <my_path>/mst-gql/examples/1-getting-started/src/app/models/index.ts
ℹ 「atl」: Using [email protected] from typescript
ℹ 「atl」: Using tsconfig.json from <my_path>/mst-gql/examples/1-getting-started/tsconfig.json
ℹ 「atl」: Checking started in a separate process...
✖ 「atl」: Checking finished with 175 errors
✖ 「wdm」: Hash: 3d707c0d5d53f24cee20
Version: webpack 4.29.3
Time: 49130ms
Built at: 08/01/2019 4:00:12 PM
Asset Size Chunks Chunk Names
bundle.js 6.52 MiB main [emitted] main
Entrypoint main = bundle.js
[0] multi (webpack)-dev-server/client?http://0.0.0.0:3000 (webpack)/hot/dev-server.js ./examples/1-getting-started/src/app/index.tsx 52 bytes {main} [built]
[./examples/1-getting-started/node_modules/loglevel/lib/loglevel.js] 7.68 KiB {main} [built]
[./examples/1-getting-started/node_modules/mst-gql/dist/mst-gql.module.js] 19.9 KiB {main} [built]
[./examples/1-getting-started/node_modules/react-dom/index.js] 1.33 KiB {main} [built]
[./examples/1-getting-started/node_modules/react/index.js] 190 bytes {main} [built]
[./examples/1-getting-started/node_modules/url/url.js] 22.8 KiB {main} [built]
[./examples/1-getting-started/node_modules/webpack-dev-server/client/index.js?http://0.0.0.0:3000] (webpack)-dev-server/client?http://0.0.0.0:3000 7.78 KiB {main} [built]
[./examples/1-getting-started/node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.58 KiB {main} [built]
[./examples/1-getting-started/node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.05 KiB {main} [built]
[./examples/1-getting-started/node_modules/webpack-dev-server/node_modules/strip-ansi/index.js] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {main} [built]
[./examples/1-getting-started/node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
[./examples/1-getting-started/node_modules/webpack/hot/dev-server.js] (webpack)/hot/dev-server.js 1.61 KiB {main} [built]
[./examples/1-getting-started/node_modules/webpack/hot/emitter.js] (webpack)/hot/emitter.js 75 bytes {main} [built]
[./examples/1-getting-started/node_modules/webpack/hot/log-apply-result.js] (webpack)/hot/log-apply-result.js 1.27 KiB {main} [built]
[./examples/1-getting-started/src/app/index.tsx] 1.3 KiB {main} [built]
+ 168 hidden modules
WARNING in ./examples/1-getting-started/node_modules/graphql-request/dist/src/index.js
Module Warning (from ./examples/1-getting-started/node_modules/source-map-loader/index.js):
(Emitted value instead of an instance of Error) Cannot find source file '../../src/index.ts': Error: Can't resolve '../../src/index.ts' in '<my_path>/mst-gql/examples/1-getting-started/node_modules/graphql-request/dist/src'
@ ./examples/1-getting-started/node_modules/mst-gql/dist/mst-gql.module.js 3:0-48 478:13-26
@ ./examples/1-getting-started/src/app/index.tsx
@ multi (webpack)-dev-server/client?http://0.0.0.0:3000 (webpack)/hot/dev-server.js ./examples/1-getting-started/src/app/index.tsx
WARNING in ./examples/1-getting-started/node_modules/graphql-request/dist/src/types.js
Module Warning (from ./examples/1-getting-started/node_modules/source-map-loader/index.js):
(Emitted value instead of an instance of Error) Cannot find source file '../../src/types.ts': Error: Can't resolve '../../src/types.ts' in '<my_path>/mst-gql/examples/1-getting-started/node_modules/graphql-request/dist/src'
@ ./examples/1-getting-started/node_modules/graphql-request/dist/src/index.js 55:14-32 56:14-32
@ ./examples/1-getting-started/node_modules/mst-gql/dist/mst-gql.module.js
@ ./examples/1-getting-started/src/app/index.tsx
@ multi (webpack)-dev-server/client?http://0.0.0.0:3000 (webpack)/hot/dev-server.js ./examples/1-getting-started/src/app/index.tsx
ERROR in [at-loader] ./node_modules/@types/react/index.d.ts:2816:14
TS2300: Duplicate identifier 'LibraryManagedAttributes'.
ERROR in [at-loader] ./src/app/models/RootStore.base.ts:31:41
TS2304: Cannot find name 'CreateTodoInput'.
ERROR in [at-loader] ../../../../../../../../node_modules/@types/react/index.d.ts:2683:14
TS2300: Duplicate identifier 'LibraryManagedAttributes'.
ERROR in [at-loader] ../../../../../../../../node_modules/@types/react/index.d.ts:2696:13
TS2717: Subsequent property declarations must have the same type. Property 'a' must be of type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>', but here has type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
ERROR in [at-loader] ../../../../../../../../node_modules/@types/react/index.d.ts:2697:13
TS2717: Subsequent property declarations must have the same type. Property 'abbr' must be of type 'DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>', but here has type 'DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>'.
ERROR in [at-loader] ../../../../../../../../node_modules/@types/react/index.d.ts:2698:13
TS2717: Subsequent property declarations must have the same type. Property 'address' must be of type 'DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>', but here has type 'DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>'.
ERROR in [at-loader] ../../../../../../../../node_modules/@types/react/index.d.ts:2699:13
TS2717: Subsequent property declarations must have the same type. Property 'area' must be of type 'DetailedHTMLProps<AreaHTMLAttributes<HTMLAreaElement>, HTMLAreaElement>', but here has type 'DetailedHTMLProps<AreaHTMLAttributes<HTMLAreaElement>, HTMLAreaElement>'.
ERROR in [at-loader] ../../../../../../../../node_modules/@types/react/index.d.ts:2700:13
TS2717: Subsequent property declarations must have the same type. Property 'article' must be of type 'DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>', but here has type 'DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>'.
...
I tried with node 10 and 12 as well.
Should these work or am I doing something wrong?
Also, I don't seem to get the nice code completions Michel demonstrated here:
https://youtu.be/Sq2M00vghqY?t=789
For me the store
in the useQuery()
in Home.tsx
in the 1-getting-started example is always of type 'any'.
Some issues running examples. I've tried:
yarn
yarn prepare-examples
But get this error:
"./mst-gql1557842644.tgz": Tarball is not in network and can not be located in cache
I don't understand why this is a dependency in the package.json
either:
"mst-gql": "file:./mst-gql1557842644.tgz",
Sometimes we have a component that makes a query then renders some component(s) that makes more queries.
Currently getDataFromTree
does not fulfill those queries that are made after the first query(s) are done. The page is server-rendered while all queries beyond the first "layer" are still not loaded. The second layer of queries is actually triggered [server-side] by the second and final rendering of the react element tree, but the results come after the page has been sent.
You can see this issue in action in my branch for #93 . When page opens in browser, the list(s) are populated, but the "Assignee" for each list item is still "Loading...".
This is a problem when you want to take advantage of SSR while taking the approach of using components (or groups of components) that declare their own data dependencies (e.g. PR #93) rather than the approach of depending on data that was fetched in one monolithic (i.e. for every element on the page) request by code somewhere up the hierarchy of react elements. In this [first, components-requesting-their-own-data] case you would typically have multiple "layers" of queries to make. (Hopefully at some point we will be able to opt-in to batching queries together in each layer (#4), or at least have them deduplicated automatically, which seems straightforward & uncontroversial enough.)
This is something that works in apollo, as you can see from inspecting it's getDataFromTree
implementation: process()
(which includes rendering tree) calls itself recursively while hasPromises()
.
So in a similar fashion, we should in our getDataFromTree
implementation probably be, after awaiting promises, re-rendering and checking for more promises. Something like:
export async function getDataFromTree<STORE extends typeof MSTGQLStore.Type>(
tree: React.ReactElement<any>,
client: STORE,
renderFunction: (
tree: React.ReactElement<any>
) => string = require("react-dom/server").renderToStaticMarkup
): Promise<string> {
while (true) {
const html = renderFunction(tree)
if (client.__promises.size === 0) {
return html
}
await Promise.all(client.__promises)
}
}
But if you are using the default fetchPolicy "cache-and-network" for your queries, that will cause an infinite loop, since each rendering will cause a network request for every query, including ones that have already been requested and cached.
Apollo deals with this (as well as the next issue I am about to document) by, depending on ssrMode
& ssrForceFetchDelay
options, forcing fetchPolicy to "cache-only" for queries with one of the fetch policies that always uses network.
The semantics of those options are that ssrMode: true
disables network fetch policies for good, while using ssrForceFetchDelay: 100
will disable them for 100ms and then start allowing them. I think we could simplify this:
I propose that when the existing ssr
[client constructor] option is true
, we disable network fetch policies (by forcing the policy to "cache-first" as needed) only during server-side rendering and initial client-side re-rendering, and, without needing to opt-in via a ssrForceFetchDelay
, after that allow use of whatever fetch policy developer wants.
@mattiasewers @chrisdrackett What do you think? Happy to put in a PR for this!
I'm trying to get the value of an enum in for a given type. given the following schema:
enum ItemType {
EVENT
NOTE
TASK
}
type Item {
id: ID!
title: String!
type: ItemType!
}
type Query {
item(id: ID!): Item!
items: [Item!]!
}
I'm trying to do:
const { error, loading, data: items } = useQuery((store) =>
store.queryItems({}, (item) => item.type.title),
)
however it seems that type
isn't available in this case. Sorry if this is a known issue with enums!
I'm curious if anyone has thoughts around supporting optimistic updates when adding new items to the store. Because items are currently stored under their ID (which won't be available until a response comes back from the server) I'm not sure the best way to handle this case, if it is even possible.
Example 4 is currently not working 100%. I think this might have more to do with the setup instructions than the example itself. Ideally someone with knowledge of Apollo can get it up and running with good docs.
I have a schema that looks like the following:
type User {
createdAt: DateTime!
email: String!
followers(after: String, before: String, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]
following(after: String, before: String, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]
id: ID!
items(after: String, before: String, first: Int, last: Int, orderBy: ItemOrderByInput, skip: Int, where: ItemWhereInput): [Item!]
itemsCreated(after: String, before: String, first: Int, last: Int, orderBy: ItemOrderByInput, skip: Int, where: ItemWhereInput): [Item!]
name: String!
projects(after: String, before: String, first: Int, last: Int, orderBy: ProjectOrderByInput, skip: Int, where: ProjectWhereInput): [Project!]
updatedAt: DateTime!
}
but after running mst-gql
I get the following:
/**
* UserBase
* auto generated base class for the model UserModel.
*/
export const UserModelBase = MSTGQLObject
.named('User')
.props({
__typename: types.optional(types.literal("User"), "User"),
createdAt: types.maybe(types.frozen()),
email: types.maybe(types.string),
id: types.identifier,
name: types.maybe(types.string),
updatedAt: types.maybe(types.frozen()),
})
.views(self => ({
get store() {
return self.__getStore<typeof RootStore.Type>()
}
}))
export class UserModelSelector extends QueryBuilder {
get createdAt() { return this.__attr(`createdAt`) }
get email() { return this.__attr(`email`) }
get id() { return this.__attr(`id`) }
get name() { return this.__attr(`name`) }
get updatedAt() { return this.__attr(`updatedAt`) }
}
export function selectFromUser() {
return new UserModelSelector()
}
export const userModelPrimitives = selectFromUser().createdAt.email.name.updatedAt
I found that if I remove the args MST picks things up correctly. For example if I change followers to be followers: [User!]
I then see it correctly in my store:
followers: types.optional(types.array(MSTGQLRef(types.late((): any => UserModel))), []),
I don't actually use these args, so I can remove them from the generated schema.graphql
but I would need to setup a script or similar to do this every time the file is generated.
I think this project is very promising. However, since I am currently not running any projects that use mobx-state-tree and GraphQL in real life. And beyond that, I'm currently running to many OSS projects already :)
So, consider becoming a maintainer of this project if you want to see this project to be continued!
I'm attempting to use mst-gql
on react native and I've run into the following:
TypeError: new Promise(function (resolve, reject) {
this$1.onResolve = resolve;
this$1.onReject = reject;
}).finally is not a function. (In 'new Promise(function (resolve, reject) {
this$1.onResolve = resolve;
this$1.onReject = reject;
}).finally(function () {
this$1.store.ssr && this$1.store.unpushPromise(this$1.promise);
})', 'new Promise(function (resolve, reject) {
this$1.onResolve = resolve;
this$1.onReject = reject;
}).finally' is undefined)
I think this might be related to: facebook/react-native#17972
I'm going to spend a little time on it today, but figured it was worth opening the issue
I'm running into the following error on all my models that are related to each other:
'TagTypeModelBase' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
Here is the file in question:
UserModel.base.ts
...
export const UserModelBase = MSTGQLObject
.named('User')
.props({
__typename: types.optional(types.literal("User"), "User"),
createdAt: types.maybe(types.frozen()),
email: types.maybe(types.string),
id: types.identifier,
name: types.maybe(types.string),
projects: types.optional(types.array(MSTGQLRef(types.late((): IAnyModelType => ProjectModel))), []),
updatedAt: types.maybe(types.frozen()),
})
...
Project
has a link back to the User
model. When I first ran into this I found:
and thought I could get around this by editing UserModel.ts
like the following:
export const UserModel = UserModelBase.props({
projects: types.optional(
types.array(MSTGQLRef(types.late((): IAnyModelType => ProjectModel))),
[],
),
})
But I still get the error in typescript. It seems like I need to edit UserModel.base.ts
directly to fix this issue, but that is a generated file.
So I have started to define various fragments in my RootStore
that work fine there. Recently I wanted to use one of these fragments on a mutation in another store:
ItemModel
export const ItemModel = ItemModelBase.actions((self) => ({
markComplete() {
return self.store.mutateUpdateItem(
{
...
},
ITEM_FRAGMENT,
)
},
}))
The problem is that ITEM_FRAGMENT
includes a selectFromOtherModel
function from a model that are not available at this point.
I see here: https://github.com/mobxjs/mst-gql/blob/master/examples/3-twitter-clone/src/app/models/MessageModel.ts#L26 that the fragment is just written by hand, I'm curious if there is a way we can support both raw and using selectFromModel
functions.
I think the generation of the code related to React should be an opt-in feature. Store management should be agnostic.
Or at least, it should be possible to give an option to not output React code.
Currently - and very logically - the generator generates types.identifier
MST type for graphql fields of type ID
. For example:
type Todo {
id: ID
...
}
becomes:
export const TodoModelBase = MSTGQLObject
.named('Todo')
.props({
__typename: types.optional(types.literal("Todo"), "Todo"),
id: types.identifier,
...
})
Hasura (https://github.com/hasura/graphql-engine), however, generates schemas for PostgreSQL tables matching the type of the columns in the database. So, in case my id in the table is of type bigint
, which is quite typical for a primary key, the generated schema will look like this:
scalar bigint
type Todo {
id: bigint!
...
}
And for this mst-gql generates:
export const TodoModelBase = MSTGQLObject
.named('Todo')
.props({
__typename: types.optional(types.literal("Todo"), "Todo"),
id: types.maybeNull(types.frozen()),
...
})
As you can see the type of id
becomes types.maybeNull(types.frozen())
. The problem with this is that resolving references in the state tree won't work for identifiers that are not defined as types.identifier
(or types.identifierNumber
).
What I would need is something like:
export const TodoModelBase = MSTGQLObject
.named('todo')
.props({
__typename: types.optional(types.literal("Todo"), "Todo"),
id: types.identifierNumber,
...
})
Note that I need types.identifierNumber
and not even types.identifier
which is what mst-gql is able to generate currently.
So, my proposal is to introduce a mechanism, which can recognize identifiers by name/pattern matching in the graphql schema beside those that have the graphql type ID
.
Something like:
mst-gql --overrideFieldType "id:bigint:types.identifierNumber,*_id:bigint:types.identifierNumber"
The --overrideFieldType
would get a specification of field names to match and the kind of mst type definition to generate for that type instead of the default type. In the above case fields that are called id
or fields that end with _id
and are of graphql type bigint
would be defined as types.identifierNumber
MST types.
The override specification may also have the field as Type.field, in which case only the field of that specific type would be overriden:
mst-gql --overrideFieldType "Todo.id:bigint:types.identifierNumber
"
The graphqlql type may be omitted or also pattern matched as well:
mst-gql --overrideFieldType "Todo.id:*:types.identifierNumber
"
This could work for non id fields as well, of course, although I don't know if such overrides would be necessary beside identifiers.
What do you think?
How to use useQuery with typescript.
Folowing code gives me error
Expected 1-3 arguments, but got 0. TS2554
and VSCODE
(method) queryTodoItems(variables: {
where: TodoItemWhereInput | undefined;
orderBy: TodoItemOrderByInput;
skip: number | undefined;
after: string | undefined;
before: string | undefined;
first: number | undefined;
last: number | undefined;
}, resultSelector?: string | ((qb: TodoItemModelSelector) => TodoItemModelSelector), options?: QueryOptions): Query<...>
Expected 1-3 arguments, but got 0.ts(2554)
RootStore.base.ts(99, 20): An argument for 'variables' was not provided.
import axios from "axios";
import "./App.css";
import { observer } from "mobx-react";
import { useQuery } from "./models/reactUtils";
interface ItodoItem {
id: string;
text: string;
completed: boolean;
}
interface ItodoItems extends Array<ItodoItem> {}
const App: React.FC = observer(() => {
const { store, error, loading, data } = useQuery(store =>
store.queryTodoItems()
);
if (error) return <div>{error.message}</div>;
if (loading) return <div>loading</div>;
return <ul>git</ul>;
});
export default App;
When running the examples I ran into this issue when running yarn install
:
../../scripts/install-mst-gql.sh: line 6: realpath: command not found
Doing this fixed it for me:
npm install --global realpath
realpath-native
does get installed in the root node_modules
folder. Should the example bash script be updated to rely on the copy of realpath
in node_modules
?
looks like the work was done in #14, but might be nice to add documentation.
Instead, or additionally to, using the cli options, which can be pretty long.
Also, some future options might need richer data than can be easily provided in a CLI.
I read the docs on graphql and the default is every field is nullable by default:
https://graphql.org/learn/best-practices/#nullability
I initially ran into this when trying to query a relation type on an ItemList
component. The query looks like this:
The generated mobx model looks like this:
The issue I ran into is for some items not having a project on them and the type not being accurate when I get project: null
back on the query:
I could be wrong but it feels like the generated models should use maybeNull
to match with graphql and allow types to be null? When I manually edit the generated model file to use maybeNull
mst-gql correctly loaded my data into the store.
Happy to do a pull request if this makes sense and wasn't intentional!
when I ran the command:
$ yarn mst-gql --format js --force http://localhost:4000/graphql
it still generates like this in RootStore.base.js file, seem to be still typescript code with js format
export type PasswordMetaWhereInput = {
id: string | undefined
id_not: string | undefined
id_in: string[]
id_not_in: string[]
id_lt: string | undefined
id_lte: string | undefined
id_gt: string | undefined
id_gte: string | undefined
id_contains: string | undefined
id_not_contains: string | undefined
id_starts_with: string | undefined
id_not_starts_with: string | undefined
id_ends_with: string | undefined
id_not_ends_with: string | undefined
resetToken: string | undefined
resetToken_not: string | undefined
resetToken_in: string[]
resetToken_not_in: string[]
resetToken_lt: string | undefined
resetToken_lte: string | undefined
resetToken_gt: string | undefined
resetToken_gte: string | undefined
resetToken_contains: string | undefined
resetToken_not_contains: string | undefined
resetToken_starts_with: string | undefined
resetToken_not_starts_with: string | undefined
resetToken_ends_with: string | undefined
resetToken_not_ends_with: string | undefined
AND: PasswordMetaWhereInput[]
OR: PasswordMetaWhereInput[]
NOT: PasswordMetaWhereInput[]
}
export type AwardFamilyWhereUniqueInput = {
id: string | undefined
name: string | undefined
alias: string | undefined
}
Currently, if a model definition disappears from the graphql endpoint, the files that were generated for it in the past are not removed. It would be great to do that in the future.
for example instead of:
/* This is a mst-gql generated file, don't modify it manually */
/* eslint-disable */
/* tslint:disable */
import { types } from "mobx-state-tree"
/**
* LogType
*/
export const LogTypeEnum = types.enumeration("LogType", [
"DAILY",
"MONTHLY",
"YEARLY",
])
output:
/* This is a mst-gql generated file, don't modify it manually */
/* eslint-disable */
/* tslint:disable */
import { types } from "mobx-state-tree"
export type LogEnumType = "DAILY" | "MONTHLY" | "YEARLY"
/**
* LogType
*/
export const LogTypeEnum = types.enumeration("LogType", [
"DAILY",
"MONTHLY",
"YEARLY",
])
The name XXXEnumType
is terrible and too close to the MST name of XXXTypeEnum
. Any suggestions of a better name?
Will the user ever have to edit or add to this line?
If not, maybe it makes sense to move it to the generated file?
Regarding the code generation, I actually do have a custom generator for graphql-code-generator
which generates fully typed hooks for each graphql operation. I am not sure yet how it would look like here, but a similar alternative would be really nice.
export function useQResolveLocation(
variables?: QResolveLocationVariables,
baseOptions?: Hooks.QueryHookOptions<QResolveLocationVariables>,
) {
return Hooks.useQuery<QResolveLocationQuery, QResolveLocationVariables>(
QResolveLocationDocument,
variables,
baseOptions,
)
}
I am using prefixes Q, M, S, F for my *.gql
documents and based on that I can distinguish those easily.
Originally posted by @FredyC in #13 (comment)
Ok, i have this mutation on my backend:
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
createUser: {
type: UserType,
args: {
name: { type: GraphQLNonNull(GraphQLString) },
email: { type: GraphQLNonNull(GraphQLString) },
password: { type: GraphQLNonNull(GraphQLString) },
},
resolve: (root, args) => {
const user = userModel.create(args);
return user;
},
},
deleteUser: {
type: UserType,
args: {
id: { type: GraphQLNonNull(GraphQLString) },
},
resolve: (root, args) => {
userDbService.deleteUserById(args.id).exec();
return null;
},
},
},
}),
after createUser
:
// Model actions:
createUser: flow(function* createUser(user) {
yield self.mutateCreateUser(user);
}),
new record in RootSore.users appears.
How can i delete record from RootSore.users?
i tried this:
deleteUser: flow(function* deleteUser(id) {
yield self.mutateDeleteUser({ id });
yield self.queryUsers();
}),
-> nothing
this:
yield self.mutateDeleteUser({ id }, undefined, () => {
self.store.users.delete(id);
});
-> record drops, but then immediately applied new patch with old data;
A component which uses "useQuery" doesn't get updated even if another component calls the same query after a mutation.
I'm looking into using mst-gql on a project I'm working on. I'm really happy so far with the models, queries and mutations!
That being said I'm currently stuck around have live data updates. Our server is currently serverless so we don't currently use GraphQL subscriptions. What we do currently is use pusher to send data from the server to the client and then the client updates the Apollo cache.
What I'm curious about is if something similar is possible in this library. i.e. can I update the local store based on an event from pusher or similar?
Is query batching currently supported?
Seems like you could often end up making multiple requests. I believe Apollo Client has this functionality built-in.
If not supported, is this a good feature to be added to the library, or you'd recommend users handle larger queries in a different way?
The current generator generate.js
, is a pretty straight forward string concatenation tool. it's fast and straight forward, but probably not very maintainable as it grows more complex in the future. Something more solid could be a good idea, I think @birkir had some thoughts about that in the past
Got following output:
Detected types:
- [OBJECT] RootQueryType
- [SCALAR] UUID
- [OBJECT] Bot
- [SCALAR] String
- [SCALAR] DateTime
- [SCALAR] Int
- [OBJECT] BotItemsConnection
- [OBJECT] BotItemsEdge
- [OBJECT] BotItem
- [OBJECT] Media
- [INTERFACE] User
- [ENUM] UserBotRelationship
- [OBJECT] BotsConnection
- [OBJECT] BotsEdge
- [OBJECT] PageInfo
- [SCALAR] Boolean
- [ENUM] UserContactRelationship
- [OBJECT] ContactsConnection
- [OBJECT] ContactsEdge
- [OBJECT] Hidden
- [OBJECT] Presence
- [ENUM] PresenceStatus
- [SCALAR] Float
- [ENUM] SubscriptionType
- [OBJECT] SubscribersConnection
- [OBJECT] SubscribersEdge
- [OBJECT] CurrentUser
- [OBJECT] BlocksConnection
- [OBJECT] BlocksEdge
- [OBJECT] Block
- [OBJECT] BlockedUser
- [OBJECT] ConversationsConnection
- [OBJECT] ConversationsEdge
- [OBJECT] Message
- [ENUM] MessageDirection
- [OBJECT] FriendsConnection
- [OBJECT] FriendsEdge
- [OBJECT] Contact
- [OBJECT] LocationEventsConnection
- [OBJECT] LocationEventsEdge
- [OBJECT] LocationEvent
- [ENUM] LocationEventType
- [OBJECT] Location
- [OBJECT] UserLocationLiveSharesConnection
- [OBJECT] UserLocationLiveSharesEdge
- [OBJECT] UserLocationLiveShare
- [OBJECT] OtherUser
- [OBJECT] LocationsConnection
- [OBJECT] LocationsEdge
- [OBJECT] MessagesConnection
- [OBJECT] MessagesEdge
- [OBJECT] InvitationsConnection
- [OBJECT] InvitationsEdge
- [OBJECT] Invitation
- [INPUT_OBJECT] Point
- [OBJECT] LocalBots
- [OBJECT] LocalBotsCluster
- [OBJECT] BotCluster
- [SCALAR] AInt
- [ENUM] NotificationType
- [OBJECT] NotificationsConnection
- [OBJECT] NotificationsEdge
- [OBJECT] Notification
- [UNION] NotificationData
- [OBJECT] BotInvitationNotification
- [OBJECT] BotInvitation
- [OBJECT] BotInvitationResponseNotification
- [OBJECT] BotItemNotification
- [OBJECT] GeofenceEventNotification
- [ENUM] GeofenceEvent
- [OBJECT] LocationShareEndNotification
- [OBJECT] LocationShareNotification
- [OBJECT] UserInvitationNotification
- [OBJECT] UserBulkLookupResult
- [OBJECT] RootMutationType
- [INPUT_OBJECT] FriendNameInput
- [OBJECT] FriendNamePayload
- [OBJECT] ValidationMessage
- [OBJECT] ValidationOption
- [OBJECT] UserLocationCancelSharePayload
- [INPUT_OBJECT] PresenceStatusInput
- [OBJECT] PresenceStatusPayload
- [INPUT_OBJECT] UserUnblockInput
- [OBJECT] UserUnblockPayload
- [INPUT_OBJECT] UserUpdateInput
- [INPUT_OBJECT] UserParams
- [OBJECT] UserUpdatePayload
- [INPUT_OBJECT] BotDeleteInput
- [OBJECT] BotDeletePayload
- [INPUT_OBJECT] BotCreateInput
- [INPUT_OBJECT] UserLocationUpdateInput
- [INPUT_OBJECT] BotParams
- [OBJECT] BotCreatePayload
- [INPUT_OBJECT] SendMessageInput
- [OBJECT] SendMessagePayload
- [INPUT_OBJECT] UserLocationLiveShareInput
- [OBJECT] UserLocationLiveSharePayload
- [INPUT_OBJECT] UserBlockInput
- [OBJECT] UserBlockPayload
- [INPUT_OBJECT] UserInviteRedeemCodeInput
- [OBJECT] UserInviteRedeemCodePayload
- [INPUT_OBJECT] FactoryInsertInput
- [INPUT_OBJECT] AtomParam
- [INPUT_OBJECT] BoolParam
- [INPUT_OBJECT] FloatParam
- [INPUT_OBJECT] IntParam
- [INPUT_OBJECT] StringParam
- [OBJECT] FactoryInsertPayload
- [INPUT_OBJECT] UserHideInput
- [OBJECT] UserHidePayload
- [INPUT_OBJECT] FriendBulkInviteInput
- [OBJECT] FriendBulkInvitePayload
- [OBJECT] FriendBulkInviteResult
- [ENUM] BulkInviteResult
- [INPUT_OBJECT] MediaUploadParams
- [OBJECT] MediaUploadPayload
- [OBJECT] MediaUploadResult
- [OBJECT] RequestHeader
- [INPUT_OBJECT] AuthenticateInput
- [OBJECT] AuthenticatePayload
- [INPUT_OBJECT] PushNotificationsDisableInput
- [OBJECT] PushNotificationsDisablePayload
- [INPUT_OBJECT] BotUpdateInput
- [OBJECT] BotUpdatePayload
- [INPUT_OBJECT] UserLocationCancelShareInput
- [INPUT_OBJECT] MediaDeleteParams
- [OBJECT] MediaDeletePayload
- [OBJECT] UserDeletePayload
- [INPUT_OBJECT] BotInvitationRespondInput
- [OBJECT] BotInvitationRespondPayload
- [INPUT_OBJECT] BotUnsubscribeInput
- [OBJECT] BotUnsubscribePayload
- [OBJECT] UserInviteMakeCodePayload
- [INPUT_OBJECT] BotInviteInput
- [OBJECT] BotInvitePayload
- [OBJECT] UserLocationGetTokenPayload
- [INPUT_OBJECT] NotificationDeleteInput
- [OBJECT] NotificationDeletePayload
- [INPUT_OBJECT] BotItemDeleteInput
- [OBJECT] BotItemDeletePayload
- [INPUT_OBJECT] FollowInput
- [OBJECT] FollowPayload
- [INPUT_OBJECT] BotItemPublishInput
- [OBJECT] BotItemPublishPayload
- [INPUT_OBJECT] FriendDeleteInput
- [OBJECT] FriendDeletePayload
- [INPUT_OBJECT] BotSubscribeInput
- [OBJECT] BotSubscribePayload
- [OBJECT] UserLocationUpdatePayload
- [INPUT_OBJECT] PushNotificationsEnableInput
- [ENUM] NotificationPlatform
- [OBJECT] PushNotificationsEnablePayload
- [INPUT_OBJECT] FriendInviteInput
- [OBJECT] FriendInvitePayload
- [OBJECT] RootSubscriptionType
- [OBJECT] VisitorUpdate
- [ENUM] VisitorAction
- [UNION] NotificationUpdate
- [OBJECT] NotificationDeleted
- [OBJECT] UserLocationUpdate
- [OBJECT] __Schema
- [OBJECT] __Type
- [ENUM] __TypeKind
- [OBJECT] __Field
- [OBJECT] __InputValue
- [OBJECT] __EnumValue
- [OBJECT] __Directive
- [ENUM] __DirectiveLocation
- [SCALAR] ID
Warning: no root types are configured. Probably --roots should be set. Auto-detected and using the following types as root types:
/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:275
throw new Error(
^
Error: Failed to convert type {"kind":"INTERFACE","name":"User","ofType":null}. PR Welcome!
at handleFieldType (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:275:17)
at handleField (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:239:34)
at type.fields.filter.map.field (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:184:21)
at Array.map (<anonymous>)
at handleObjectType (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:184:8)
at types.filter.forEach.type (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:88:22)
at Array.forEach (<anonymous>)
at generateTypes (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:81:8)
at generate (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/generate.js:42:3)
at main (/Users/aksonov/Documents/Test/node_modules/mst-gql/generator/mst-gql-scaffold.js:96:17)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
I think this is related to #47. it seems now that data is returned in the following format given something like queryUser()
I now get:
data = {
user: {
email: '[email protected]'
}
}
however data is still typed as an instance of User
so doing data.user.email
gives me an error:
Property 'user' does not exist on type 'ModelInstanceTypeProps…
seems like we need to update the types for the data
bit of the useQuery
hook.
I have some enums in my types and after generating my models I get the following error:
/@calm/core/src/models/ItemModel.base.js
Module not found: Can't resolve './ItemTypeEnum.base' in '/@calm/core/src/models'
In ItemModel.base.js I see an unused import:
import { ItemTypeEnumSelector } from './ItemTypeEnum.base'
I wonder if this is suppose to be used below in this file under ItemModelSelector
?
Sorry if this is related to #25 I'm still figuring everything out!
Hi,
I'm trying to generate mst-gql models from a Hasura (v1.0.0-beta.4) generated graphql shema.
This is the database schema (basic Todo) I use in postgresql:
create table todo
(
id bigserial not null
constraint todo_pkey
primary key,
text text not null,
complete boolean not null
);
Hasura generates this schema for it
schema {
query: query_root
mutation: mutation_root
subscription: subscription_root
}
scalar bigint
# expression to compare columns of type bigint. All fields are combined with logical 'AND'.
input bigint_comparison_exp {
_eq: bigint
_gt: bigint
_gte: bigint
_in: [bigint]
_is_null: Boolean
_lt: bigint
_lte: bigint
_neq: bigint
_nin: [bigint]
}
# expression to compare columns of type boolean. All fields are combined with logical 'AND'.
input boolean_comparison_exp {
_eq: Boolean
_gt: Boolean
_gte: Boolean
_in: [Boolean]
_is_null: Boolean
_lt: Boolean
_lte: Boolean
_neq: Boolean
_nin: [Boolean]
}
# conflict action
enum conflict_action {
# ignore the insert on this row
ignore
# update the row with the given values
update
}
# mutation root
type mutation_root {
# delete data from the table: "todo"
delete_todo(
# filter the rows which have to be deleted
where: todo_bool_exp!
): todo_mutation_response
# insert data into the table: "todo"
insert_todo(
# the rows to be inserted
objects: [todo_insert_input!]!
# on conflict condition
on_conflict: todo_on_conflict
): todo_mutation_response
# update data of the table: "todo"
update_todo(
# increments the integer columns with given value of the filtered values
_inc: todo_inc_input
# sets the columns of the filtered rows to the given values
_set: todo_set_input
# filter the rows which have to be updated
where: todo_bool_exp!
): todo_mutation_response
}
# column ordering options
enum order_by {
# in the ascending order, nulls last
asc
# in the ascending order, nulls first
asc_nulls_first
# in the ascending order, nulls last
asc_nulls_last
# in the descending order, nulls first
desc
# in the descending order, nulls first
desc_nulls_first
# in the descending order, nulls last
desc_nulls_last
}
# query root
type query_root {
# fetch data from the table: "todo"
todo(
# distinct select on columns
distinct_on: [todo_select_column!]
# limit the nuber of rows returned
limit: Int
# skip the first n rows. Use only with order_by
offset: Int
# sort the rows by one or more columns
order_by: [todo_order_by!]
# filter the rows returned
where: todo_bool_exp
): [todo!]!
# fetch aggregated fields from the table: "todo"
todo_aggregate(
# distinct select on columns
distinct_on: [todo_select_column!]
# limit the nuber of rows returned
limit: Int
# skip the first n rows. Use only with order_by
offset: Int
# sort the rows by one or more columns
order_by: [todo_order_by!]
# filter the rows returned
where: todo_bool_exp
): todo_aggregate!
# fetch data from the table: "todo" using primary key columns
todo_by_pk(id: bigint!): todo
}
# subscription root
type subscription_root {
# fetch data from the table: "todo"
todo(
# distinct select on columns
distinct_on: [todo_select_column!]
# limit the nuber of rows returned
limit: Int
# skip the first n rows. Use only with order_by
offset: Int
# sort the rows by one or more columns
order_by: [todo_order_by!]
# filter the rows returned
where: todo_bool_exp
): [todo!]!
# fetch aggregated fields from the table: "todo"
todo_aggregate(
# distinct select on columns
distinct_on: [todo_select_column!]
# limit the nuber of rows returned
limit: Int
# skip the first n rows. Use only with order_by
offset: Int
# sort the rows by one or more columns
order_by: [todo_order_by!]
# filter the rows returned
where: todo_bool_exp
): todo_aggregate!
# fetch data from the table: "todo" using primary key columns
todo_by_pk(id: bigint!): todo
}
# expression to compare columns of type text. All fields are combined with logical 'AND'.
input text_comparison_exp {
_eq: String
_gt: String
_gte: String
_ilike: String
_in: [String]
_is_null: Boolean
_like: String
_lt: String
_lte: String
_neq: String
_nilike: String
_nin: [String]
_nlike: String
_nsimilar: String
_similar: String
}
# columns and relationships of "todo"
type todo {
complete: Boolean!
id: bigint!
text: String!
}
# aggregated selection of "todo"
type todo_aggregate {
aggregate: todo_aggregate_fields
nodes: [todo!]!
}
# aggregate fields of "todo"
type todo_aggregate_fields {
avg: todo_avg_fields
count(columns: [todo_select_column!], distinct: Boolean): Int
max: todo_max_fields
min: todo_min_fields
stddev: todo_stddev_fields
stddev_pop: todo_stddev_pop_fields
stddev_samp: todo_stddev_samp_fields
sum: todo_sum_fields
var_pop: todo_var_pop_fields
var_samp: todo_var_samp_fields
variance: todo_variance_fields
}
# order by aggregate values of table "todo"
input todo_aggregate_order_by {
avg: todo_avg_order_by
count: order_by
max: todo_max_order_by
min: todo_min_order_by
stddev: todo_stddev_order_by
stddev_pop: todo_stddev_pop_order_by
stddev_samp: todo_stddev_samp_order_by
sum: todo_sum_order_by
var_pop: todo_var_pop_order_by
var_samp: todo_var_samp_order_by
variance: todo_variance_order_by
}
# input type for inserting array relation for remote table "todo"
input todo_arr_rel_insert_input {
data: [todo_insert_input!]!
on_conflict: todo_on_conflict
}
# aggregate avg on columns
type todo_avg_fields {
id: Float
}
# order by avg() on columns of table "todo"
input todo_avg_order_by {
id: order_by
}
# Boolean expression to filter rows from the table "todo". All fields are combined with a logical 'AND'.
input todo_bool_exp {
_and: [todo_bool_exp]
_not: todo_bool_exp
_or: [todo_bool_exp]
complete: boolean_comparison_exp
id: bigint_comparison_exp
text: text_comparison_exp
}
# unique or primary key constraints on table "todo"
enum todo_constraint {
# unique or primary key constraint
todo_pkey
}
# input type for incrementing integer columne in table "todo"
input todo_inc_input {
id: bigint
}
# input type for inserting data into table "todo"
input todo_insert_input {
complete: Boolean
id: bigint
text: String
}
# aggregate max on columns
type todo_max_fields {
id: bigint
text: String
}
# order by max() on columns of table "todo"
input todo_max_order_by {
id: order_by
text: order_by
}
# aggregate min on columns
type todo_min_fields {
id: bigint
text: String
}
# order by min() on columns of table "todo"
input todo_min_order_by {
id: order_by
text: order_by
}
# response of any mutation on the table "todo"
type todo_mutation_response {
# number of affected rows by the mutation
affected_rows: Int!
# data of the affected rows by the mutation
returning: [todo!]!
}
# input type for inserting object relation for remote table "todo"
input todo_obj_rel_insert_input {
data: todo_insert_input!
on_conflict: todo_on_conflict
}
# on conflict condition type for table "todo"
input todo_on_conflict {
constraint: todo_constraint!
update_columns: [todo_update_column!]!
}
# ordering options when selecting data from "todo"
input todo_order_by {
complete: order_by
id: order_by
text: order_by
}
# select columns of table "todo"
enum todo_select_column {
# column name
complete
# column name
id
# column name
text
}
# input type for updating data in table "todo"
input todo_set_input {
complete: Boolean
id: bigint
text: String
}
# aggregate stddev on columns
type todo_stddev_fields {
id: Float
}
# order by stddev() on columns of table "todo"
input todo_stddev_order_by {
id: order_by
}
# aggregate stddev_pop on columns
type todo_stddev_pop_fields {
id: Float
}
# order by stddev_pop() on columns of table "todo"
input todo_stddev_pop_order_by {
id: order_by
}
# aggregate stddev_samp on columns
type todo_stddev_samp_fields {
id: Float
}
# order by stddev_samp() on columns of table "todo"
input todo_stddev_samp_order_by {
id: order_by
}
# aggregate sum on columns
type todo_sum_fields {
id: bigint
}
# order by sum() on columns of table "todo"
input todo_sum_order_by {
id: order_by
}
# update columns of table "todo"
enum todo_update_column {
# column name
complete
# column name
id
# column name
text
}
# aggregate var_pop on columns
type todo_var_pop_fields {
id: Float
}
# order by var_pop() on columns of table "todo"
input todo_var_pop_order_by {
id: order_by
}
# aggregate var_samp on columns
type todo_var_samp_fields {
id: Float
}
# order by var_samp() on columns of table "todo"
input todo_var_samp_order_by {
id: order_by
}
# aggregate variance on columns
type todo_variance_fields {
id: Float
}
# order by variance() on columns of table "todo"
input todo_variance_order_by {
id: order_by
}
When I run mst-gql --format ts todo-schema.graphql
and try to use the generated models, useQuery, etc. I get no code completion because the type of RootStoreBase, RootStore etc. is always 'any'. For these types I get an TS error like:
Error:(51, 14) TS7022: 'RootStoreBase' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
Unfortunately I don't know the internals of mst and also not an expert of Typescript, so I could not figure out what causes this problem.
Do you have any Idea?
Note: I started off from a more complex data schema and thought that caused the problems. Then I tried this single table Todo use case and it produced the same errors.
Our models use Relay connections, maybe mst-gql can handles it in some pretty way.
Currently, mst-gql is based on graphql-request
. Not using apollo client has a few benefits
However, using apollo might have soon benefits as well
Using apollo as transportation layer is possibly already easy to achieve, by creating an { request(query, variables): Promise }
object, that wraps around apollo client, and providing that as httpGqlClient
when creating a store
Hi. Not sure if this is the right place to put this. I kept trying to run this and kept getting an error can't read .Kind of undefined at interfaceOrUnionType.kind. I ended up cloning this and trying to get it to work locally.
I got it to work by adding a conditional check in the handleInterfaceOrUnionType func in generate.js
const isUnion = interfaceOrUnionType && interfaceOrUnionType.kind === "UNION"
And
interfaceOrUnionType && interfaceOrUnionType.ofTypes.forEach(t => {...
I think it chokes where my Schema has
{
"kind": "OBJECT",
"name": "__Schema",
Adding the check above got it to run and generate properly.
But perhaps that additional check could be added in the repo, so I don't have to use a fork. Thank you.
Alos, there were some refences to './RootStore' in the generated files. But no Rootstore is created, only a small case 'rootStore'. So had to update the paths in 'generate.js' in 3 places that incorrectly referenced uppercase Rootstore file.
I can put in a PR for these changes if you want.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.