GithubHelp home page GithubHelp logo

mst-gql's People

Contributors

aryk avatar barbalex avatar beepsoft avatar bradenm avatar chrisdrackett avatar dependabot[bot] avatar dpnolte avatar elie222 avatar emckay avatar filippodossena-mozart avatar jesse-savary avatar joaomanke avatar jovidecroock avatar laurenfackler avatar metevier avatar mtsewrs avatar mwarger avatar mweststrate avatar pvpshoot avatar rdewolff avatar rxminus avatar scherler avatar smokku avatar special-character avatar vamshi9666 avatar weglov avatar wtuminski avatar yasinuslu avatar zenflow avatar zpalin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mst-gql's Issues

Examples with classes vs hooks

Are there any examples that use classes? (and decorators?) vs hooks? Is that possible? I'm looking to take baby steps...

curious what to do with datetime or similar

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.

Automatically updating store upon query/mutation result

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?

[Bug] Fresh data gets overwritten with stale data from __queryCache

(almost) minimal reproduction repo

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

steps to reproduce

  1. Open up the app
    image
  2. Click the "Show" button under the "Done Todos" heading. This fetches todos from a separate query in the graphql schema, doneTodos.
    image
  3. Click the "Hide button. The screen should look the same as in step 1.
  4. Toggle one or more of the todos.
    image
  5. Click the "Show" button under "Done Todos" again.
    image

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:

  • Be able to replace the __queryCache (since the Query model would have the same necessary data from __queryCache)
  • Be part of the normalized data model. No duplicate (stale) data. References are used in place of actual data for other "root types".
  • Allow for optimistic updates to properties of the root graphql Query type! That would mean we can optimistically update all parts of our query results. Not just properties of [other] root type instances, but also which instances are included in query results, and non-root type data in query results. Taking the reproduction repo app for example, in the optimistic update function for toggling a todo, we could (in addition to toggling the 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. ❤️ 💪

[Question] Correct way to update access token in createHTTPClient

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?

Better TypeScript support in query builders

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:

Playground link

What do you think? Let me know please if it's something you might be interested in in the future.

It should be possible to generate non-nullable fields

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'.

INPUT_OBJECT in typescript

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
}

Should this project be used in production?

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.

Enable enums to be used in queries

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:

  • Enums fields are not added to the ModelSelector/QueryBuilder of the containing model
    • probably require changes in generateFragments in generate.js
  • Enums are not typed when being used as a query variable
    • probably require changes in generateQueryBuilder in generate.js

If anyone would be able to pick this up, great. Otherwise I might do this later.

yarn test and yarn start fails

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'.

Running examples

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",

[SSR] getDataFromTree ignores (doesn't wait for) queries triggered after loading first queries

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!

support returning enums in queries

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!

optimistic updates with new items

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.

Fix Example 4

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.

relations with arguments not showing up on model

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.

Looking for maintainers!

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!

issues with `finally` on react-native

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

Circular dependencies between models

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:

https://github.com/mobxjs/mobx-state-tree#handle-circular-dependencies-between-files-and-types-using-late

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.

using multi-model fragments within a model

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.

React should be opt-in or opt-out

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.

Alternative way to generate types.identifier/types.identifierNumber for non ID typed identifier fields

The problem

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.

Proposal

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?

using useQuery with TypeScript

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;

`realpath` missing when running examples

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?

Use `maybeNull` instead of `maybe` for default fields

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:

Screen Shot 2019-07-08 at 5 25 00 PM

The generated mobx model looks like this:

Screen Shot 2019-07-08 at 5 23 28 PM

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:

Screen Shot 2019-07-08 at 5 24 18 PM

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!

[Bug] when generate with --format js

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
}

export enum types when outputting to typescript

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?

Generating React hooks specific to operations

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)

[question] How to refetch data after delete object from model

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;

  1. Map.length // 2
  2. deleteUser() -> Map.length // 1
  3. Map.length // 2

updating the store outside of graphql

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?

Batching

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?

Use a decent code generator or templates

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

Failed to convert type

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.

types on `data` returned from useQuery not correct

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.

can't resolve enum

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!

"RootStoreBase implicitly has type 'any' ..." when generating models for a Hasura schema

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.

Support apollo client?

Currently, mst-gql is based on graphql-request. Not using apollo client has a few benefits

  • much smaller footprint
  • easy / simple setup

However, using apollo might have soon benefits as well

  • gradual adoption, if the apollo client can be used as networking layer
  • benefit from features not present in mst-gql, or which are in a more mature state in apollo
  • It could be interesting to research whether the mst-gql RootStore could act as appollo's caching layer, as it is serializable out of the box for example.

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

Bug: Can't read kind of undefined.

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.