Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circular dependencies between models #45

Closed
chrisdrackett opened this issue Jun 29, 2019 · 48 comments
Closed

Circular dependencies between models #45

chrisdrackett opened this issue Jun 29, 2019 · 48 comments

Comments

@chrisdrackett
Copy link
Collaborator

chrisdrackett commented Jun 29, 2019

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.

@chrisdrackett chrisdrackett changed the title '<XXX>ModelBase' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer. Circular dependencies between models Jul 2, 2019
@Amareis
Copy link

Amareis commented Jul 5, 2019

+1, in my (complicated enough) domain models generated models are useless because of circular dependencies.

@Amareis
Copy link

Amareis commented Jul 5, 2019

Against the comment in https://github.com/mobxjs/mst-gql/blob/master/generator/generate.js#L354, types.late isn't enough to prevent circular dependencies :)

@elie222
Copy link
Collaborator

elie222 commented Jul 5, 2019

@chrisdrackett does editing the generated file solve your problems? (I realise this isn't a long term fix).

@Amareis is the problem with TS types or a JS issue?

@Amareis
Copy link

Amareis commented Jul 5, 2019

@elie222 just TS typings. For correct circular references handling we need to use types.late((): IAnyModelType => SomeModel), but if we do it everywhere, all references will be any type. So, this should be applied only at some points, to break the wheel, but currently IDK how to select that points algoritmically.

@Amareis
Copy link

Amareis commented Jul 5, 2019

https://github.com/pahen/madge can be helpful in find and resoving circular deps. My current models have 36 of it.

@chrisdrackett
Copy link
Collaborator Author

@elie222 editing the generated file does fix the problem, but like @Amareis mentioned because this makes the return type any you only want to do it where needed and not on everything. I'm not sure if that is something the library could detect, but if so, it would be awesome.

I can try and add a note about this if needed to the docs. Its really more a MST thing than a mst-gql thing, but might be worth letting users know about regardless.

@elie222
Copy link
Collaborator

elie222 commented Jul 11, 2019

So I'm thinking we can handle this by manually generating the TS types. TS shouldn't have issues with the circular dependencies in general. For example, the following code compiles fine:

interface Post {
  id: string
  comment?: Comment
}

interface Comment {
  id: string
  post: Post
}

const post: Post = {
  id: 'a',
  comment: {
    id: 'b',
    post: {
      id: 'c',
    }
  }
}

Having TS figure out the types from the model itself is nice usually, but as we're generating this code in any case, we may as well generate the TS types too and get around this circular any issue.

@mweststrate I know you've dealt with circular dependencies quite a lot. What are your thoughts on this?

@Amareis
Copy link

Amareis commented Jul 12, 2019

Yep, that's will really cool! Besides of circular deps handling, it'll make nice typenames in compiler errors, instead of usual mst types garbage.

@mweststrate
Copy link
Member

mweststrate commented Jul 12, 2019 via email

@mweststrate
Copy link
Member

Regardless on whether the above is implemented or not, in general two tips that help with breaking circular type dependencies:

  1. Break circles by not relying on type inference for method arguments / return types (example: Circular type references with TS 3.5 mobx-state-tree#1341 (comment))
  2. Use interfaces that extends types (example: https://github.com/mobxjs/mobx-state-tree#using-a-mst-type-at-design-time, the second code snippet)

Hope that helps in the mean time!

@chrisdrackett
Copy link
Collaborator Author

@elie222 have you done any work on this? I was thinking of giving it a try tomorrow and/or over the weekend

@elie222
Copy link
Collaborator

elie222 commented Jul 26, 2019 via email

@mweststrate
Copy link
Member

#69 should already make this much better

@beepsoft
Copy link
Collaborator

beepsoft commented Sep 4, 2019

So I'm thinking we can handle this by manually generating the TS types.

Wouldn't it be possible to use graphql-codegen (https://graphql-code-generator.com) for this? It can generate TS interfaces for the same graphql schema we use in mst-gql-scaffold.js.

@elie222
Copy link
Collaborator

elie222 commented Sep 4, 2019 via email

@beepsoft
Copy link
Collaborator

beepsoft commented Sep 4, 2019

@elie222 I see. And how should this MST compatible TS interface look like? Do you have any idea of this?

@elie222
Copy link
Collaborator

elie222 commented Sep 4, 2019 via email

@beepsoft
Copy link
Collaborator

beepsoft commented Sep 4, 2019

Thanks for these details!

Another idea/question: implementing this in the generator as a general mechanism for any late() type:

mobxjs/mobx-state-tree#943 (comment)

We would then always have the actual prop as of type any (because of IAnyModelType) and a matching view, which returns the prop casted using Instance<...> and would use this view function in code where we want hints from the IDE.

This is still hackish but could be an optional workaround until manually generated TS types happen.

beepsoft added a commit to beepsoft/mst-gql that referenced this issue Sep 4, 2019
Implements the trick mentioned in this comment:

mobxjs/mobx-state-tree#943 (comment)

Every late() reference field will be of type IAnyModelType and will be
accompanied by a view getter casting it to the actual type of the prop.
After this a reference field name "refField" should be accessed as
"refFieldProp" which will provide correct typings/autocomplete in IDEs.

This trick works up to typescript 3.4.x and doesn't work in 3.5 and
above :-( Using Instance<...> like this however still works in all
versions:

mobxjs/mobx-state-tree#943 (comment)

refs mobxjs#45
@beepsoft
Copy link
Collaborator

beepsoft commented Sep 5, 2019

I implemented this solution above. It works nicely with TS 3.4, but doesn't work with newer versions.

@chrisdrackett
Copy link
Collaborator Author

there seems to be something going on with TS versions past 3.4 that make things like this project and others I use (graphql-nexus/nexus-prisma#291) have issues. I haven't taken the time to dig into it (for now I'm just locked to 3.4.5).

@beepsoft
Copy link
Collaborator

Is there anyone working on @elie222's proposal about generating TS types "manually"?

So I'm thinking we can handle this by manually generating the TS types. TS shouldn't have issues with the circular dependencies in general. For example, the following code compiles fine:

interface Post {
  id: string
  comment?: Comment
}

interface Comment {
  id: string
  post: Post
}

const post: Post = {
  id: 'a',
  comment: {
    id: 'b',
    post: {
      id: 'c',
    }
  }
}

Having TS figure out the types from the model itself is nice usually, but as we're generating this code in any case, we may as well generate the TS types too and get around this circular any issue.

@mweststrate I know you've dealt with circular dependencies quite a lot. What are your thoughts on this?

@elie222
Copy link
Collaborator

elie222 commented Sep 24, 2019 via email

@beepsoft
Copy link
Collaborator

I may try to implement it, but I don't know what actually needs to be done here.

Can you give me some hints how it would work? How these generated TS types would be associated with the MSTGQLObject based MST models the generator generates? If that's how it is planned to work at all - I'm a little bit lost here.

@chrisdrackett
Copy link
Collaborator Author

I think after the types are generated we would just associate them with the return of RootStore.create, for example here: https://github.com/mobxjs/mst-gql/blob/master/examples/1-getting-started/src/app/index.tsx#L11

I think also ideally the user should be able to easily extend these types in case they are adding props, views, actions, etc. to a store themselves.

@chrisdrackett
Copy link
Collaborator Author

@beepsoft would it be helpful for me to attempt to put together example output for some of the examples in this repo? That might give us a target to discuss before we worry about how to get from the input to output.

@beepsoft
Copy link
Collaborator

@chrisdrackett Yes, an actual example would be a great starting point, thank you!

@RXminuS
Copy link
Collaborator

RXminuS commented Sep 25, 2019

I'm a bit swamped to have time to help out so instead I left a $50 bounty on bountysource :-)

@chrisdrackett
Copy link
Collaborator Author

@beepsoft ok, I'll work on it some today. Not sure if I'll get anything to share as I probably need to learn a lot more about how MST types work in general :D

@chrisdrackett chrisdrackett mentioned this issue Sep 25, 2019
4 tasks
@chrisdrackett
Copy link
Collaborator Author

I went ahead and put together a draft branch for this work here: #110

This was a quick first pass and certainly missing a lot. It also currently does not do any generation.

Hopefully this can act as a discussion starter!

@RXminuS
Copy link
Collaborator

RXminuS commented Sep 30, 2019

#112 is probably somewhat related

@beepsoft
Copy link
Collaborator

@RXminuS does #112 solve the circular dependencies problem? Or which part of it?

@kpogodin
Copy link

kpogodin commented Oct 1, 2019

+1 i have circular deps in gql

base file generate at base model field

import { UserGroupModel } from "./UserGroupModel"

export const UserGroupModelBase = MSTGQLObject
  .named('UserGroup')
  .props({
 //
    manageableUserGroups: types.optional(types.array(MSTGQLRef(types.late(() => UserGroupModel))), []),
//
  })`

at the same time custom model required import UserGroupModelBase

import { UserGroupModelBase } from './UserGroupModel.base';

export const UserGroupModel = UserGroupModelBase.actions(self => {
  return {
    addRoleId(id) {
///

as a result -> circular deps + build error
Cannot read property 'actions' of undefined at UserGroupModel

@pvpshoot
Copy link
Contributor

pvpshoot commented Oct 1, 2019

and this is QL schema
image

@RXminuS
Copy link
Collaborator

RXminuS commented Oct 1, 2019

@beepsoft I've started a new PR that's a bit better #117 . The issue that it solves is that in certain cases the actual import is not yet defined depending on the loading order. By using the internal.js module you get more control over the exact loading order. However, it doesn't solve anything about the circular type declarations preventing proper type inference on models that reference themselves.

@RXminuS
Copy link
Collaborator

RXminuS commented Oct 1, 2019

@gadzillllla Can you try with #117 and reorder the internal file to see if you can make the module load sooner?

@pvpshoot
Copy link
Contributor

pvpshoot commented Oct 2, 2019

@RXminuS we tried it, and it works fine for now

@beepsoft
Copy link
Collaborator

I just tried if it is any better with TS 3.7.0-beta, but unfortunately it is not.

@onomated
Copy link

onomated commented Oct 28, 2019

@RXminuS had the same issue as @gadzillllla, JS codebase with circular dependencies. Tried running your fork and got this error while generating the gql models

  - [OBJECT] __InputValue
  - [OBJECT] __EnumValue
  - [OBJECT] __Directive
  - [ENUM] __DirectiveLocation
/app/node_modules/mst-gql/generator/mst-gql-scaffold.js:115
    moduleLoadingOrder
    ^

ReferenceError: moduleLoadingOrder is not defined
    at main (/app/node_modules/mst-gql/generator/mst-gql-scaffold.js:115:5)
    at Object.<anonymous> (/app/node_modules/mst-gql/generator/mst-gql-scaffold.js:121:1)
    at Module._compile (internal/modules/cjs/loader.js:936:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:947:10)
    at Module.load (internal/modules/cjs/loader.js:790:32)
    at Function.Module._load (internal/modules/cjs/loader.js:703:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:999:10)
    at internal/main/run_main_module.js:17:11
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

mst-gql.config.js

module.exports = {
  force: true,
  format: "js",
  input: "app/graphql/schema.graphql",
  outDir: "app/frontend/models",
  roots: [] // auto-detect
};

@beepsoft
Copy link
Collaborator

@RXminuS this PR seems to provide a solution:

#128

Will be hopefully merged into master soon.

@onomated
Copy link

#128 is generating TS for JS spec:

export const RootStoreBase: typeof RootStoreBaseNoRefs = RootStoreBaseNoRefs

Also exhibiting the same webpack circular dependencies error while loading app:

fromRequireContextWithGlobalFallback.js:21 Error: Cannot find module 'mst-gql'
    at webpackMissingModule (reactUtils.js:1)
    at Module../app/frontend/models/reactUtils.js (reactUtils.js:1)
    at __webpack_require__ (bootstrap:19)

@beepsoft
Copy link
Collaborator

(@onomated wrong @ in previous message, sorry :-) )

I've never seen such webpack error, sorry, then it is a different issue.

@chrisdrackett
Copy link
Collaborator Author

fixed in 0.7.0

@beepsoft
Copy link
Collaborator

beepsoft commented Nov 22, 2019

🎉 Great work @godness84, thanks for your solution and @chrisdrackett for managing the merge! I'm so happy with this issue being solved!

@godness84
Copy link
Collaborator

Thanks to you guys!

@michaelspeed
Copy link

michaelspeed commented Jul 28, 2020

still facing issue with circular deps

Circular dependency detected: mobxtree/UsersPermissionsUserConnectionUpdatedByModel.base.ts -> mobxtree/UsersPermissionsUserConnectionModel.ts -> mobxtree/UsersPermissionsUserConnectionModel.base.ts -> mobxtree/UsersPermissionsUserGroupByModel.ts -> mobxtree/UsersPermissionsUserGroupByModel.base.ts -> mobxtree/UsersPermissionsUserConnectionUpdatedByModel.ts -> mobxtree/UsersPermissionsUserConnectionUpdatedByModel.base.ts

using TS 3.7.3 and mst-gql 0.12.0

@godness84
Copy link
Collaborator

@michaelspeed can you give more details? Does it compile? Does TS understand the types when you put the cursor on? Have you customized the code of any model? Maybe you introduced new circular deps?

Just looking at your few lines it's quite impossible to have any clues

@michaelspeed
Copy link

Here is the generated folder -
mobxtree.zip

and the schema -
schema.graphql.zip

@michaelspeed
Copy link

michaelspeed commented Jul 28, 2020

The error output looks like this -
error - ReferenceError: Cannot access 'UploadFileConnectionAlternativeTextModelBase' before initialization at Module.eval [as UploadFileConnectionAlternativeTextModelBase] (webpack-internal:///./mobxtree/UploadFileConnectionAlternativeTextModel.base.ts:2:136)

using Next js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests