-
Notifications
You must be signed in to change notification settings - Fork 87
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
New graphql schema #785
New graphql schema #785
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just have a question and a request (docs) but it's looking great ✌️
Make retrieval of auth token cloud agnosticbooster/packages/framework-integration-tests/integration/end-to-end/read-models.integration.ts Lines 19 to 24 in 70da128
This comment was generated by todo based on a
|
make tests cloud agnosticbooster/packages/framework-integration-tests/integration/end-to-end/subscriptions.integration.ts Lines 21 to 24 in 70da128
This comment was generated by todo based on a
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✌️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lovely! Left some suggestions
const meta: ClassMetadata = Reflect.getMetadata('booster:typeinfo', classType) | ||
if (!meta) { | ||
console.log(`Could not get proper metadata information of ${classType.name}`) | ||
return [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we returning an empty array here? Shouldn't this fail in the case it doesn't find it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was failing before, and that was causing an error when creating the schema. The idea is that instead of falling, it returns an empty value, so the property type will be unknown and set as JSONObject
, allowing us to have a schema when working with interfaces or custom types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be related to the inconsistencies we saw yesterday in Mayday schema?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@otoumas I guess the types (the Typescript classes) you are using in the fields messages
and participants
are exactly the same in both read models, right?
(BTW, Why do you have two identical read models with different names? Or was it a test?)
|
||
const primitiveType = this.typeInformer.getPrimitiveExtendedType(prop.typeInfo.type) | ||
if (primitiveType === Array) return this.generateArrayFilterFor(prop) | ||
if (!this.generatedFiltersByTypeName[filterName]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps reverse this if condition and make it an early return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree!
case 'lt': | ||
if (readModelPropValue >= value) return false | ||
break | ||
case '>': | ||
case 'gt': | ||
if (readModelPropValue <= value) return false | ||
break | ||
case '>=': | ||
case 'gte': | ||
if (readModelPropValue < value) return false | ||
break | ||
case '<=': | ||
case 'lte': | ||
if (readModelPropValue > value) return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are the conditions switched here? This looks so weird...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the way the subscriptions work, they definitely will need some refactoring in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think (@adriangmweb correct me if I'm wrong) that right now this code is broken, as it relies on the old filters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a conversation with Adrián, this code is not broken
70da128
to
786e45c
Compare
"emitDecoratorMetadata": true | ||
"plugins": [ | ||
{ "transform": "metadata-booster" } | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain me what does this plugin do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adriangmweb I see that the base branch is main
, Should it be use-metadata-booster
?
@@ -37,7 +39,7 @@ export const template = `{ | |||
"scripts": { | |||
"lint:check": "eslint --ext '.js,.ts' **/*.ts", | |||
"lint:fix": "eslint --quiet --fix --ext '.js,.ts' **/*.ts", | |||
"compile": "npx tsc -b tsconfig.json", | |||
"compile": "npx ttypescript -b tsconfig.json", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be using the ttsc
command (Ttypescript compiler) instead of ttypescript
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but when using npx with ttsc
it fails on retrieving the package. That's why I'm requesting the whole package instead of using just the binary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Oh, that's when ttypescript
is not installed (or it is not listed in the dependencies), so npx tries to find that package and install it. This is not ideal because it installs a "random version" (normally the latest).
I guess we should see why it is failing (it is not failing in the integration-tests package and we are using ttsc
there (if I recall correctly)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try to sort this, to have it ready for merge 💪🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a conversation with @alvaroloes, we found out that it's necessary to use npm >= 7.0.0
in order to have npx
behaving properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll update the docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, turns out that npx
it's not necessary when using npm scripts on the package.json
file as npm is able to retrieve the binary from the root dependencies. You can see a deeper explanation here: https://www.freecodecamp.org/news/npm-vs-npx-whats-the-difference/#npm-the-package-manager
"emitDecoratorMetadata": true | ||
"plugins": [ | ||
{ "transform": "metadata-booster" } | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const meta: ClassMetadata = Reflect.getMetadata('booster:typeinfo', classType) | ||
if (!meta) { | ||
console.log(`Could not get proper metadata information of ${classType.name}`) | ||
return [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@otoumas I guess the types (the Typescript classes) you are using in the fields messages
and participants
are exactly the same in both read models, right?
(BTW, Why do you have two identical read models with different names? Or was it a test?)
@@ -151,7 +151,7 @@ function toReadModelRequestEnvelope( | |||
requestID: context.requestID, | |||
currentUser: context.user, | |||
typeName: readModelName, | |||
filters: args, | |||
filters: args.filter ? (args.filter as Record<string, ReadModelPropertyFilter>) : args, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you explained this to me long ago, but now I don't remember (Sorry! 😰 )
The truth is that it is strange. If args
contains filter
, then use that, if not we use args
. Should we always use either one or the other?
If you could elaborate a bit on the reasons why this is this way I will really appreciate it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! The thing is that for being able to use filter combinators on the schema, we need to create a new GrahpQL input type where filter
is the key and the properties are in the value. But, when using the filters on code-level like in commands, that is not necessary so we don't have to use the filter
key.
I've tried to do it more elegant, but I didn't succeed back then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks for the explanation, but still don't quite get it. This code is only executed when the search is done through GraphQL. The search executed in code with Booster.readModels
uses the searcher directly.
If I'm correct, this code then should be just like this:
filters: args.filter ? (args.filter as Record<string, ReadModelPropertyFilter>) : args, | |
filters: args.filter, |
Let me know if I'm not seeing something 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorted 🚀
|
||
const primitiveType = this.typeInformer.getPrimitiveExtendedType(prop.typeInfo.type) | ||
if (primitiveType === Array) return this.generateArrayFilterFor(prop) | ||
if (!this.generatedFiltersByTypeName[filterName]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree!
packages/framework-core/src/services/graphql/graphql-query-generator.ts
Outdated
Show resolved
Hide resolved
let nestedProperties: GraphQLInputFieldConfigMap = {} | ||
const properties = getPropertiesMetadata(prop.typeInfo.type) | ||
if (properties.length > 0) { | ||
this.typeInformer.generateGraphQLTypeFromMetadata({ class: prop.typeInfo.type, properties }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please elaborate on why is this line needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By calling this function the custom types like Money
are created and included in the schema. Without this line, those properties would be JSONObject
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha! Thanks. Yep, that's because Money is not included among the types of the typeInformer. I think the TypeInformer requires a little refactor to not require to pass all the types it can handle in the constructor, but to generate them as requested. But for another PR.
👏
...nestedProperties, | ||
and: { type: new GraphQLList(this.generatedFiltersByTypeName[filterName]) }, | ||
or: { type: new GraphQLList(this.generatedFiltersByTypeName[filterName]) }, | ||
not: { type: this.generatedFiltersByTypeName[filterName] }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woah! here my head exploded and I couldn't follow anymore. Isn't this.generatedFiltersByTypeName[filterName]
undefined? We are exactly inside the if
branch, which checked that that expression was undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hahaha I don't blame you! We are setting it here before the value is set. Why is that possible? It's because using fields as a function instead of as an object, the graphql library understands that it's a recursive call and the value will be set later.
There is not much info about it, but you can find an example when setting the PersonType
on this example: https://graphql.org/graphql-js/type/#examples
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha! Thanks for explaining
packages/framework-core/src/services/graphql/graphql-query-generator.ts
Outdated
Show resolved
Hide resolved
in: { type: GraphQLList(GraphQLString) }, | ||
beginsWith: { type: GraphQLString }, | ||
contains: { type: GraphQLString }, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brutal work @adriangmweb !! Merging this would be a massive improvement for the framework 👏
packages/framework-core/src/services/graphql/graphql-type-informer.ts
Outdated
Show resolved
Hide resolved
packages/framework-core/src/services/graphql/graphql-type-informer.ts
Outdated
Show resolved
Hide resolved
packages/framework-core/src/services/graphql/graphql-type-informer.ts
Outdated
Show resolved
Hide resolved
packages/framework-core/src/services/graphql/graphql-type-informer.ts
Outdated
Show resolved
Hide resolved
case 'lt': | ||
if (readModelPropValue >= value) return false | ||
break | ||
case '>': | ||
case 'gt': | ||
if (readModelPropValue <= value) return false | ||
break | ||
case '>=': | ||
case 'gte': | ||
if (readModelPropValue < value) return false | ||
break | ||
case '<=': | ||
case 'lte': | ||
if (readModelPropValue > value) return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think (@adriangmweb correct me if I'm wrong) that right now this code is broken, as it relies on the old filters.
|
||
const originalSubscriptionsCount = await countSubscriptionsItems() | ||
// Let's create two subscriptions to the same read model | ||
const observable = cartFilteredSubscription(client, { id: { eq: cartID } }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is working then? Even with complex filters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it works even better than the queries because you can filter subscriptions by ID of property inside an array! 🎉
const originalSubscriptionsCount = await countSubscriptionsItems() | ||
// Let's create two subscriptions to the same read model | ||
const observable = cartFilteredSubscription(client, { | ||
cartItemsIds: { includes: productId }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whaat? then subscriptions with the new schema are working???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! 🕺🏻
...ages/framework-integration-tests/integration/providers/aws/functionality/auth.integration.ts
Outdated
Show resolved
Hide resolved
@@ -48,7 +49,7 @@ | |||
"watch:local": "nodemon --watch ../framework-provider-local/dist --watch ../framework-provider-local-infrastructure --watch dist --exec \"../cli/bin/run start -e local\"", | |||
"lint:check": "eslint --ext '.js,.ts' **/*.ts", | |||
"lint:fix": "eslint --quiet --fix --ext '.js,.ts' **/*.ts", | |||
"compile": "tsc -b tsconfig.json", | |||
"compile": "npx ttsc -b tsconfig.json", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the right command. Please, check the other pacakge.json because I think you used ttypescript
instead of ttsc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk why but it was failing in the other one when using ttsc
, that's why it's changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check the other comment, I think it could be related to installing dependencies when generating the user project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more missing comments
786e45c
to
448203f
Compare
448203f
to
7dd747c
Compare
This reverts commit 9de34c0.
7dd747c
to
899f588
Compare
Description
Note: This branch has been extended from #616 as the metadata reflection introduced by the TypeScript transformers is necessary. Unfortunately, this has its limitations as stated in #661.
This branch introduces the GraphQL new schema in the core, allowing filtering by more than one property at the same time and by complex properties as well in queries and subscriptions.
Changes
The GraphQL schema now is way more secure and complete, allows complex and multiple filters instead of just one basic one. Also, the filter API is way more user-friendly.
New: now it's possible to use non-compatible types like interfaces which will be transcribed as JSON types. In the future, they will be compatible with the typing
Checks
Additional information
This PR solves #600 #602 and also #603. It also covers subscriptions adapters to the new filters #751
Due to a
DynamoDB
limitation, the schema has to make use of genericGraphQLJSONType
types, which doesn't give much info to the user and doesn't give us full security when updating types. This also doesn't give us the ability yet to query by properties of a complex property that is inside aList
. You can see more information about this limitation here #652Next steps