From 8159ffd78c19b3f586fc99ffa206ffa96202850a Mon Sep 17 00:00:00 2001 From: Jason Hanggi Date: Wed, 26 Apr 2023 17:25:36 -0500 Subject: [PATCH 1/8] Fix error when resolving interface in federation I encountered an error on my application while trying to resolve an interface from a reference resolver in a federated graph. I was able to replicate the issue in the apollo-federation example. The first thing I did was update the example graphs to federation v2. Then I turned Product into an interface and created two subtypes for it. I then used the example graphql and got my error. If I changed the `resolveProductReference` to be synchronous the issue went away, but in my case I need it to be async. Thanks to the stacktrace I was able to track it down to the instance in the resolveType function being a promise in this scenario. If we await it, everything looks good. --- examples/apollo-federation/accounts/user.ts | 1 + .../helpers/buildFederatedSchema.ts | 17 ++++++++++++++++- examples/apollo-federation/inventory/product.ts | 1 + examples/apollo-federation/products/data.ts | 15 ++++++++------- examples/apollo-federation/products/dining.ts | 9 +++++++++ examples/apollo-federation/products/product.ts | 6 +++--- examples/apollo-federation/products/seating.ts | 9 +++++++++ .../reviews/product/product.ts | 1 + 8 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 examples/apollo-federation/products/dining.ts create mode 100644 examples/apollo-federation/products/seating.ts diff --git a/examples/apollo-federation/accounts/user.ts b/examples/apollo-federation/accounts/user.ts index 3e74c425a..9e1cbaa1c 100644 --- a/examples/apollo-federation/accounts/user.ts +++ b/examples/apollo-federation/accounts/user.ts @@ -6,6 +6,7 @@ export default class User { @Field(type => ID) id: string; + @Directive("@shareable") @Field() username: string; diff --git a/examples/apollo-federation/helpers/buildFederatedSchema.ts b/examples/apollo-federation/helpers/buildFederatedSchema.ts index 80d2e3dc7..d96d3ebff 100644 --- a/examples/apollo-federation/helpers/buildFederatedSchema.ts +++ b/examples/apollo-federation/helpers/buildFederatedSchema.ts @@ -16,7 +16,22 @@ export async function buildFederatedSchema( // build Apollo Subgraph schema const federatedSchema = buildSubgraphSchema({ - typeDefs: gql(printSchemaWithDirectives(schema)), + typeDefs: gql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: [ + "@key" + "@shareable" + "@provides" + "@extends" + "@requires" + "@external" + "@interfaceObject" + ] + ) + ${printSchemaWithDirectives(schema)} + `, // merge schema's resolvers with reference resolvers resolvers: deepMerge(createResolversMap(schema) as any, referenceResolvers), }); diff --git a/examples/apollo-federation/inventory/product.ts b/examples/apollo-federation/inventory/product.ts index a758b5a5a..58b175436 100644 --- a/examples/apollo-federation/inventory/product.ts +++ b/examples/apollo-federation/inventory/product.ts @@ -2,6 +2,7 @@ import { ObjectType, Directive, Field } from "../../../src"; @ObjectType() @Directive("@extends") +@Directive("@interfaceObject") @Directive(`@key(fields: "upc")`) export default class Product { @Field() diff --git a/examples/apollo-federation/products/data.ts b/examples/apollo-federation/products/data.ts index 9f98968fe..05682e2b6 100644 --- a/examples/apollo-federation/products/data.ts +++ b/examples/apollo-federation/products/data.ts @@ -1,26 +1,27 @@ +import Seating from "./seating"; +import Dining from "./dining"; import Product from "./product"; export const products: Product[] = [ - createProduct({ + Object.assign(new Dining(), { upc: "1", name: "Table", price: 899, weight: 100, + height: "3ft", }), - createProduct({ + Object.assign(new Seating(), { upc: "2", name: "Couch", price: 1299, weight: 1000, + seats: 2, }), - createProduct({ + Object.assign(new Seating(), { upc: "3", name: "Chair", price: 54, weight: 50, + seats: 1, }), ]; - -function createProduct(productData: Partial) { - return Object.assign(new Product(), productData); -} diff --git a/examples/apollo-federation/products/dining.ts b/examples/apollo-federation/products/dining.ts new file mode 100644 index 000000000..7638a4d6e --- /dev/null +++ b/examples/apollo-federation/products/dining.ts @@ -0,0 +1,9 @@ +import { Directive, Field, ObjectType } from "../../../src"; +import Product from "./product"; + +@Directive(`@key(fields: "upc")`) +@ObjectType({ implements: Product }) +export default class Dining extends Product { + @Field() + height: string; +} diff --git a/examples/apollo-federation/products/product.ts b/examples/apollo-federation/products/product.ts index 4761234d5..dcb0f2418 100644 --- a/examples/apollo-federation/products/product.ts +++ b/examples/apollo-federation/products/product.ts @@ -1,8 +1,8 @@ -import { ObjectType, Directive, Field } from "../../../src"; +import { Directive, Field, InterfaceType } from "../../../src"; @Directive(`@key(fields: "upc")`) -@ObjectType() -export default class Product { +@InterfaceType() +export default abstract class Product { @Field() upc: string; diff --git a/examples/apollo-federation/products/seating.ts b/examples/apollo-federation/products/seating.ts new file mode 100644 index 000000000..a333c7118 --- /dev/null +++ b/examples/apollo-federation/products/seating.ts @@ -0,0 +1,9 @@ +import { Directive, Field, ObjectType } from "../../../src"; +import Product from "./product"; + +@Directive(`@key(fields: "upc")`) +@ObjectType({ implements: Product }) +export default class Seating extends Product { + @Field() + seats: number; +} diff --git a/examples/apollo-federation/reviews/product/product.ts b/examples/apollo-federation/reviews/product/product.ts index 4a21adf8c..6dfc75125 100644 --- a/examples/apollo-federation/reviews/product/product.ts +++ b/examples/apollo-federation/reviews/product/product.ts @@ -2,6 +2,7 @@ import { ObjectType, Directive, Field } from "../../../../src"; @Directive("@extends") @Directive(`@key(fields: "upc")`) +@Directive("@interfaceObject") @ObjectType() export default class Product { @Directive("@external") From 3274b3dac53ba884f5fa4bb6f1af93f43a5e81de Mon Sep 17 00:00:00 2001 From: Jason Hanggi Date: Wed, 31 May 2023 12:07:20 -0500 Subject: [PATCH 2/8] upgrade apollo/subgraph --- package-lock.json | 117 ++++++++++++++++++++++++---------------------- package.json | 4 +- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ec08dd3c..0a8ba3b76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,8 +27,8 @@ "tslib": "^2.5.0" }, "devDependencies": { - "@apollo/gateway": "^2.4.3", - "@apollo/subgraph": "^2.4.3", + "@apollo/gateway": "^2.4.7", + "@apollo/subgraph": "^2.4.7", "@babel/plugin-syntax-decorators": "^7.21.0", "@babel/preset-env": "^7.21.5", "@babel/preset-typescript": "^7.21.5", @@ -77,6 +77,11 @@ "peerDependencies": { "class-validator": ">=0.14.0", "graphql": "^16.6.0" + }, + "peerDependenciesMeta": { + "class-validator": { + "optional": true + } } }, "node_modules/@ampproject/remapping": { @@ -111,13 +116,13 @@ } }, "node_modules/@apollo/composition": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/composition/-/composition-2.4.3.tgz", - "integrity": "sha512-C0829vugQ+jB+xg1ttrq0fpuHyL78Ap5USiV66k8XjqB0/NxNJiBHARiLGyHa+/Mgl1mFs5hZpIwva4sUuSGpQ==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/composition/-/composition-2.4.7.tgz", + "integrity": "sha512-eXRTG6J5ywwXsWgP0Wgic4zeBXO8jBBvI0FM4t/gay4DI+1zEL5qmoF2HN/jFik0dnd/ud5kh4djvFLmWEwbeA==", "dev": true, "dependencies": { - "@apollo/federation-internals": "2.4.3", - "@apollo/query-graphs": "2.4.3" + "@apollo/federation-internals": "2.4.7", + "@apollo/query-graphs": "2.4.7" }, "engines": { "node": ">=14.15.0" @@ -127,9 +132,9 @@ } }, "node_modules/@apollo/federation-internals": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/federation-internals/-/federation-internals-2.4.3.tgz", - "integrity": "sha512-GPkYC4PRcJNkxGr6cuj4k27l/Tzb7TYJVTfrtMKZ+emiXH9/YY1ph10H7fwqzQTuimri9fRGAEU5XX0IPk99QA==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/federation-internals/-/federation-internals-2.4.7.tgz", + "integrity": "sha512-a4iH+72yVd0zNGC5ZGEDfbqI81wktE7qAHjH4rQzJTqMQ74By3PoMD2DefOArcweonvd4b/h4oQJXGvLK2V4Ew==", "dev": true, "dependencies": { "@types/uuid": "^9.0.0", @@ -145,14 +150,14 @@ } }, "node_modules/@apollo/gateway": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-2.4.3.tgz", - "integrity": "sha512-gdTzNQhJeVrMx4qrSCurzXz47n2w1+zeN147u/Nakmxg9pviM4B+I1TPdDJSwjrhQmOkXvA+SH7H9Nwbh2m9Iw==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-2.4.7.tgz", + "integrity": "sha512-ZcqSJEUxVJqqPKJ97q72Hyp0YEQfHW+oCRP9osF6XKkrGX1C7nxLm6EzO261+m1bUwkdtjk5CLfXxa9yxIDRdA==", "dev": true, "dependencies": { - "@apollo/composition": "2.4.3", - "@apollo/federation-internals": "2.4.3", - "@apollo/query-planner": "2.4.3", + "@apollo/composition": "2.4.7", + "@apollo/federation-internals": "2.4.7", + "@apollo/query-planner": "2.4.7", "@apollo/server-gateway-interface": "^1.1.0", "@apollo/usage-reporting-protobuf": "^4.1.0", "@apollo/utils.createhash": "^2.0.0", @@ -202,12 +207,12 @@ } }, "node_modules/@apollo/query-graphs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/query-graphs/-/query-graphs-2.4.3.tgz", - "integrity": "sha512-rewBB3KSiZRGK92M21nbpxBFq2Ty+t6KqH9CFrWstpocNAw4P2SMqs9rA3mEQhLYrEyIaWTMwD199/vi/M7QMw==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/query-graphs/-/query-graphs-2.4.7.tgz", + "integrity": "sha512-LLoY0GgP3mcF+8zp3cf701ixHrsFh1WH1R8HPtO0NOgArmVMTl0bvsEHzFqUNdMd/PcU2b9SMcCmPwN7dKRs+A==", "dev": true, "dependencies": { - "@apollo/federation-internals": "2.4.3", + "@apollo/federation-internals": "2.4.7", "deep-equal": "^2.0.5", "ts-graphviz": "^1.5.4", "uuid": "^9.0.0" @@ -220,13 +225,13 @@ } }, "node_modules/@apollo/query-planner": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/query-planner/-/query-planner-2.4.3.tgz", - "integrity": "sha512-PvBGTm1xRNTbzbQ25SEwj/AJ1mQkZdVQiWifoRzw2dRIRcqSbimsV3ma35N1t7Eu5YiTHOSUBdEYxBz0Pm1dgg==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/query-planner/-/query-planner-2.4.7.tgz", + "integrity": "sha512-0fTA1W5NKtfxLq5+GY9ORPSQB/MRH1xOt3SbSS8o9yhR9GKzzE3yYNc88HgYAe2lfdDOesCME2t1kKrE76uzdA==", "dev": true, "dependencies": { - "@apollo/federation-internals": "2.4.3", - "@apollo/query-graphs": "2.4.3", + "@apollo/federation-internals": "2.4.7", + "@apollo/query-graphs": "2.4.7", "@apollo/utils.keyvaluecache": "^2.1.0", "chalk": "^4.1.0", "deep-equal": "^2.0.5", @@ -255,13 +260,13 @@ } }, "node_modules/@apollo/subgraph": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/subgraph/-/subgraph-2.4.3.tgz", - "integrity": "sha512-O/VqXLA+ut1yFVmCsgwusgqDczScc5zYrHSW64xBX5hJ5/ZsgrcrWlgaf3bo7JSHsVlabnYHfXBOS9hKYjUlOg==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/subgraph/-/subgraph-2.4.7.tgz", + "integrity": "sha512-2P6sROTIiu6hy8q8xCT1vznBctGG5PXclpmMcaEElBmkVfpBlrBsOCdge83onCJ4ODrolZZ6nmyJQFbxHCOiEA==", "dev": true, "dependencies": { "@apollo/cache-control-types": "^1.0.2", - "@apollo/federation-internals": "2.4.3" + "@apollo/federation-internals": "2.4.7" }, "engines": { "node": ">=14.15.0" @@ -12931,19 +12936,19 @@ "requires": {} }, "@apollo/composition": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/composition/-/composition-2.4.3.tgz", - "integrity": "sha512-C0829vugQ+jB+xg1ttrq0fpuHyL78Ap5USiV66k8XjqB0/NxNJiBHARiLGyHa+/Mgl1mFs5hZpIwva4sUuSGpQ==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/composition/-/composition-2.4.7.tgz", + "integrity": "sha512-eXRTG6J5ywwXsWgP0Wgic4zeBXO8jBBvI0FM4t/gay4DI+1zEL5qmoF2HN/jFik0dnd/ud5kh4djvFLmWEwbeA==", "dev": true, "requires": { - "@apollo/federation-internals": "2.4.3", - "@apollo/query-graphs": "2.4.3" + "@apollo/federation-internals": "2.4.7", + "@apollo/query-graphs": "2.4.7" } }, "@apollo/federation-internals": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/federation-internals/-/federation-internals-2.4.3.tgz", - "integrity": "sha512-GPkYC4PRcJNkxGr6cuj4k27l/Tzb7TYJVTfrtMKZ+emiXH9/YY1ph10H7fwqzQTuimri9fRGAEU5XX0IPk99QA==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/federation-internals/-/federation-internals-2.4.7.tgz", + "integrity": "sha512-a4iH+72yVd0zNGC5ZGEDfbqI81wktE7qAHjH4rQzJTqMQ74By3PoMD2DefOArcweonvd4b/h4oQJXGvLK2V4Ew==", "dev": true, "requires": { "@types/uuid": "^9.0.0", @@ -12953,14 +12958,14 @@ } }, "@apollo/gateway": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-2.4.3.tgz", - "integrity": "sha512-gdTzNQhJeVrMx4qrSCurzXz47n2w1+zeN147u/Nakmxg9pviM4B+I1TPdDJSwjrhQmOkXvA+SH7H9Nwbh2m9Iw==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/gateway/-/gateway-2.4.7.tgz", + "integrity": "sha512-ZcqSJEUxVJqqPKJ97q72Hyp0YEQfHW+oCRP9osF6XKkrGX1C7nxLm6EzO261+m1bUwkdtjk5CLfXxa9yxIDRdA==", "dev": true, "requires": { - "@apollo/composition": "2.4.3", - "@apollo/federation-internals": "2.4.3", - "@apollo/query-planner": "2.4.3", + "@apollo/composition": "2.4.7", + "@apollo/federation-internals": "2.4.7", + "@apollo/query-planner": "2.4.7", "@apollo/server-gateway-interface": "^1.1.0", "@apollo/usage-reporting-protobuf": "^4.1.0", "@apollo/utils.createhash": "^2.0.0", @@ -12999,25 +13004,25 @@ } }, "@apollo/query-graphs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/query-graphs/-/query-graphs-2.4.3.tgz", - "integrity": "sha512-rewBB3KSiZRGK92M21nbpxBFq2Ty+t6KqH9CFrWstpocNAw4P2SMqs9rA3mEQhLYrEyIaWTMwD199/vi/M7QMw==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/query-graphs/-/query-graphs-2.4.7.tgz", + "integrity": "sha512-LLoY0GgP3mcF+8zp3cf701ixHrsFh1WH1R8HPtO0NOgArmVMTl0bvsEHzFqUNdMd/PcU2b9SMcCmPwN7dKRs+A==", "dev": true, "requires": { - "@apollo/federation-internals": "2.4.3", + "@apollo/federation-internals": "2.4.7", "deep-equal": "^2.0.5", "ts-graphviz": "^1.5.4", "uuid": "^9.0.0" } }, "@apollo/query-planner": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/query-planner/-/query-planner-2.4.3.tgz", - "integrity": "sha512-PvBGTm1xRNTbzbQ25SEwj/AJ1mQkZdVQiWifoRzw2dRIRcqSbimsV3ma35N1t7Eu5YiTHOSUBdEYxBz0Pm1dgg==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/query-planner/-/query-planner-2.4.7.tgz", + "integrity": "sha512-0fTA1W5NKtfxLq5+GY9ORPSQB/MRH1xOt3SbSS8o9yhR9GKzzE3yYNc88HgYAe2lfdDOesCME2t1kKrE76uzdA==", "dev": true, "requires": { - "@apollo/federation-internals": "2.4.3", - "@apollo/query-graphs": "2.4.3", + "@apollo/federation-internals": "2.4.7", + "@apollo/query-graphs": "2.4.7", "@apollo/utils.keyvaluecache": "^2.1.0", "chalk": "^4.1.0", "deep-equal": "^2.0.5", @@ -13037,13 +13042,13 @@ } }, "@apollo/subgraph": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@apollo/subgraph/-/subgraph-2.4.3.tgz", - "integrity": "sha512-O/VqXLA+ut1yFVmCsgwusgqDczScc5zYrHSW64xBX5hJ5/ZsgrcrWlgaf3bo7JSHsVlabnYHfXBOS9hKYjUlOg==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@apollo/subgraph/-/subgraph-2.4.7.tgz", + "integrity": "sha512-2P6sROTIiu6hy8q8xCT1vznBctGG5PXclpmMcaEElBmkVfpBlrBsOCdge83onCJ4ODrolZZ6nmyJQFbxHCOiEA==", "dev": true, "requires": { "@apollo/cache-control-types": "^1.0.2", - "@apollo/federation-internals": "2.4.3" + "@apollo/federation-internals": "2.4.7" } }, "@apollo/usage-reporting-protobuf": { diff --git a/package.json b/package.json index b7b766bb9..c8fe5d51e 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "tslib": "^2.5.0" }, "devDependencies": { - "@apollo/gateway": "^2.4.3", - "@apollo/subgraph": "^2.4.3", + "@apollo/gateway": "^2.4.7", + "@apollo/subgraph": "^2.4.7", "@babel/plugin-syntax-decorators": "^7.21.0", "@babel/preset-env": "^7.21.5", "@babel/preset-typescript": "^7.21.5", From 03d84b01f255b2c514b643208969b4fa2cefda2e Mon Sep 17 00:00:00 2001 From: Jason Hanggi Date: Wed, 21 Jun 2023 16:58:24 -0500 Subject: [PATCH 3/8] move federation 2 example into its own example --- .vscode/launch.json | 1 + examples/apollo-federation-2/accounts/data.ts | 20 ++++++ .../apollo-federation-2/accounts/index.ts | 25 ++++++++ .../apollo-federation-2/accounts/resolver.ts | 12 ++++ .../accounts/user-reference.ts | 6 ++ examples/apollo-federation-2/accounts/user.ts | 18 ++++++ examples/apollo-federation-2/examples.graphql | 20 ++++++ .../helpers/buildFederatedSchema.ts | 40 ++++++++++++ examples/apollo-federation-2/index.ts | 38 ++++++++++++ .../apollo-federation-2/inventory/data.ts | 10 +++ .../apollo-federation-2/inventory/index.ts | 25 ++++++++ .../inventory/product-reference.ts | 17 +++++ .../apollo-federation-2/inventory/product.ts | 22 +++++++ .../apollo-federation-2/inventory/resolver.ts | 18 ++++++ examples/apollo-federation-2/products/data.ts | 27 ++++++++ .../apollo-federation-2/products/dining.ts | 9 +++ .../apollo-federation-2/products/index.ts | 26 ++++++++ .../products/product-reference.ts | 8 +++ .../apollo-federation-2/products/product.ts | 17 +++++ .../apollo-federation-2/products/resolver.ts | 15 +++++ .../apollo-federation-2/products/seating.ts | 9 +++ examples/apollo-federation-2/reviews/index.ts | 23 +++++++ .../reviews/product/product.ts | 11 ++++ .../reviews/product/resolver.ts | 13 ++++ .../reviews/review/data.ts | 62 +++++++++++++++++++ .../reviews/review/resolver.ts | 12 ++++ .../reviews/review/review.ts | 21 +++++++ .../reviews/user/resolver.ts | 13 ++++ .../apollo-federation-2/reviews/user/user.ts | 14 +++++ examples/apollo-federation-2/schema.graphql | 57 +++++++++++++++++ examples/apollo-federation/accounts/user.ts | 1 - .../helpers/buildFederatedSchema.ts | 17 +---- .../apollo-federation/inventory/product.ts | 1 - examples/apollo-federation/products/data.ts | 15 +++-- .../apollo-federation/products/product.ts | 6 +- .../reviews/product/product.ts | 1 - 36 files changed, 620 insertions(+), 30 deletions(-) create mode 100644 examples/apollo-federation-2/accounts/data.ts create mode 100644 examples/apollo-federation-2/accounts/index.ts create mode 100644 examples/apollo-federation-2/accounts/resolver.ts create mode 100644 examples/apollo-federation-2/accounts/user-reference.ts create mode 100644 examples/apollo-federation-2/accounts/user.ts create mode 100644 examples/apollo-federation-2/examples.graphql create mode 100644 examples/apollo-federation-2/helpers/buildFederatedSchema.ts create mode 100644 examples/apollo-federation-2/index.ts create mode 100644 examples/apollo-federation-2/inventory/data.ts create mode 100644 examples/apollo-federation-2/inventory/index.ts create mode 100644 examples/apollo-federation-2/inventory/product-reference.ts create mode 100644 examples/apollo-federation-2/inventory/product.ts create mode 100644 examples/apollo-federation-2/inventory/resolver.ts create mode 100644 examples/apollo-federation-2/products/data.ts create mode 100644 examples/apollo-federation-2/products/dining.ts create mode 100644 examples/apollo-federation-2/products/index.ts create mode 100644 examples/apollo-federation-2/products/product-reference.ts create mode 100644 examples/apollo-federation-2/products/product.ts create mode 100644 examples/apollo-federation-2/products/resolver.ts create mode 100644 examples/apollo-federation-2/products/seating.ts create mode 100644 examples/apollo-federation-2/reviews/index.ts create mode 100644 examples/apollo-federation-2/reviews/product/product.ts create mode 100644 examples/apollo-federation-2/reviews/product/resolver.ts create mode 100644 examples/apollo-federation-2/reviews/review/data.ts create mode 100644 examples/apollo-federation-2/reviews/review/resolver.ts create mode 100644 examples/apollo-federation-2/reviews/review/review.ts create mode 100644 examples/apollo-federation-2/reviews/user/resolver.ts create mode 100644 examples/apollo-federation-2/reviews/user/user.ts create mode 100644 examples/apollo-federation-2/schema.graphql diff --git a/.vscode/launch.json b/.vscode/launch.json index 2a50fa6ff..f72c2ad06 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,6 +24,7 @@ "apollo-cache", "apollo-client", "apollo-federation", + "apollo-federation-2", "authorization", "automatic-validation", "custom-validation", diff --git a/examples/apollo-federation-2/accounts/data.ts b/examples/apollo-federation-2/accounts/data.ts new file mode 100644 index 000000000..0b4f3e096 --- /dev/null +++ b/examples/apollo-federation-2/accounts/data.ts @@ -0,0 +1,20 @@ +import User from "./user"; + +export const users: User[] = [ + createUser({ + id: "1", + name: "Ada Lovelace", + birthDate: "1815-12-10", + username: "@ada", + }), + createUser({ + id: "2", + name: "Alan Turing", + birthDate: "1912-06-23", + username: "@complete", + }), +]; + +function createUser(userData: Partial) { + return Object.assign(new User(), userData); +} diff --git a/examples/apollo-federation-2/accounts/index.ts b/examples/apollo-federation-2/accounts/index.ts new file mode 100644 index 000000000..a9f6ead2d --- /dev/null +++ b/examples/apollo-federation-2/accounts/index.ts @@ -0,0 +1,25 @@ +import { ApolloServer } from "apollo-server"; + +import AccountsResolver from "./resolver"; +import User from "./user"; +import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; +import { resolveUserReference } from "./user-reference"; + +export async function listen(port: number): Promise { + const schema = await buildFederatedSchema( + { + resolvers: [AccountsResolver], + orphanedTypes: [User], + }, + { + User: { __resolveReference: resolveUserReference }, + }, + ); + + const server = new ApolloServer({ schema }); + + const { url } = await server.listen({ port }); + console.log(`Accounts service ready at ${url}`); + + return url; +} diff --git a/examples/apollo-federation-2/accounts/resolver.ts b/examples/apollo-federation-2/accounts/resolver.ts new file mode 100644 index 000000000..cd6a9f257 --- /dev/null +++ b/examples/apollo-federation-2/accounts/resolver.ts @@ -0,0 +1,12 @@ +import { Resolver, Query } from "../../../src"; + +import User from "./user"; +import { users } from "./data"; + +@Resolver(of => User) +export default class AccountsResolver { + @Query(returns => User) + me(): User { + return users[0]; + } +} diff --git a/examples/apollo-federation-2/accounts/user-reference.ts b/examples/apollo-federation-2/accounts/user-reference.ts new file mode 100644 index 000000000..aed680677 --- /dev/null +++ b/examples/apollo-federation-2/accounts/user-reference.ts @@ -0,0 +1,6 @@ +import { users } from "./data"; +import User from "./user"; + +export async function resolveUserReference(reference: Pick): Promise { + return users.find(u => u.id === reference.id)!; +} diff --git a/examples/apollo-federation-2/accounts/user.ts b/examples/apollo-federation-2/accounts/user.ts new file mode 100644 index 000000000..9e1cbaa1c --- /dev/null +++ b/examples/apollo-federation-2/accounts/user.ts @@ -0,0 +1,18 @@ +import { Field, ObjectType, Directive, ID } from "../../../src"; + +@Directive(`@key(fields: "id")`) +@ObjectType() +export default class User { + @Field(type => ID) + id: string; + + @Directive("@shareable") + @Field() + username: string; + + @Field() + name: string; + + @Field() + birthDate: string; +} diff --git a/examples/apollo-federation-2/examples.graphql b/examples/apollo-federation-2/examples.graphql new file mode 100644 index 000000000..45dca094f --- /dev/null +++ b/examples/apollo-federation-2/examples.graphql @@ -0,0 +1,20 @@ +query { + topProducts { + name + price + shippingEstimate + inStock + reviews { + body + author { + name + birthDate + reviews { + product { + name + } + } + } + } + } +} diff --git a/examples/apollo-federation-2/helpers/buildFederatedSchema.ts b/examples/apollo-federation-2/helpers/buildFederatedSchema.ts new file mode 100644 index 000000000..d96d3ebff --- /dev/null +++ b/examples/apollo-federation-2/helpers/buildFederatedSchema.ts @@ -0,0 +1,40 @@ +import { buildSchema, BuildSchemaOptions, createResolversMap } from "../../../src"; +import gql from "graphql-tag"; +import deepMerge from "lodash.merge"; +import { buildSubgraphSchema } from "@apollo/subgraph"; +import { IResolvers, printSchemaWithDirectives } from "@graphql-tools/utils"; + +export async function buildFederatedSchema( + options: Omit, + referenceResolvers?: IResolvers, +) { + // build TypeGraphQL schema + const schema = await buildSchema({ + ...options, + skipCheck: true, // disable check to allow schemas without query, etc. + }); + + // build Apollo Subgraph schema + const federatedSchema = buildSubgraphSchema({ + typeDefs: gql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: [ + "@key" + "@shareable" + "@provides" + "@extends" + "@requires" + "@external" + "@interfaceObject" + ] + ) + ${printSchemaWithDirectives(schema)} + `, + // merge schema's resolvers with reference resolvers + resolvers: deepMerge(createResolversMap(schema) as any, referenceResolvers), + }); + + return federatedSchema; +} diff --git a/examples/apollo-federation-2/index.ts b/examples/apollo-federation-2/index.ts new file mode 100644 index 000000000..d38244cd4 --- /dev/null +++ b/examples/apollo-federation-2/index.ts @@ -0,0 +1,38 @@ +import "reflect-metadata"; +import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway"; +import { ApolloServer } from "apollo-server"; +import path from "path"; +import { emitSchemaDefinitionFile } from "../../src"; + +import * as accounts from "./accounts"; +import * as reviews from "./reviews"; +import * as products from "./products"; +import * as inventory from "./inventory"; + +async function bootstrap() { + const gateway = new ApolloGateway({ + supergraphSdl: new IntrospectAndCompose({ + subgraphs: [ + { name: "accounts", url: await accounts.listen(4001) }, + { name: "reviews", url: await reviews.listen(4002) }, + { name: "products", url: await products.listen(4003) }, + { name: "inventory", url: await inventory.listen(4004) }, + ], + }), + }); + + const { schema, executor } = await gateway.load(); + + await emitSchemaDefinitionFile(path.resolve(__dirname, "schema.graphql"), schema); + + const server = new ApolloServer({ + schema, + executor, + }); + + server.listen({ port: 4000 }).then(({ url }) => { + console.log(`Apollo Gateway ready at ${url}`); + }); +} + +bootstrap().catch(console.error); diff --git a/examples/apollo-federation-2/inventory/data.ts b/examples/apollo-federation-2/inventory/data.ts new file mode 100644 index 000000000..4796e02b2 --- /dev/null +++ b/examples/apollo-federation-2/inventory/data.ts @@ -0,0 +1,10 @@ +export interface Inventory { + upc: string; + inStock: boolean; +} + +export const inventory: Inventory[] = [ + { upc: "1", inStock: true }, + { upc: "2", inStock: false }, + { upc: "3", inStock: true }, +]; diff --git a/examples/apollo-federation-2/inventory/index.ts b/examples/apollo-federation-2/inventory/index.ts new file mode 100644 index 000000000..9642e3dbf --- /dev/null +++ b/examples/apollo-federation-2/inventory/index.ts @@ -0,0 +1,25 @@ +import { ApolloServer } from "apollo-server"; + +import InventoryResolver from "./resolver"; +import Product from "./product"; +import { resolveProductReference } from "./product-reference"; +import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; + +export async function listen(port: number): Promise { + const schema = await buildFederatedSchema( + { + resolvers: [InventoryResolver], + orphanedTypes: [Product], + }, + { + Product: { __resolveReference: resolveProductReference }, + }, + ); + + const server = new ApolloServer({ schema }); + + const { url } = await server.listen({ port }); + console.log(`Inventory service ready at ${url}`); + + return url; +} diff --git a/examples/apollo-federation-2/inventory/product-reference.ts b/examples/apollo-federation-2/inventory/product-reference.ts new file mode 100644 index 000000000..7cc998250 --- /dev/null +++ b/examples/apollo-federation-2/inventory/product-reference.ts @@ -0,0 +1,17 @@ +import { inventory } from "./data"; +import Product from "./product"; + +export async function resolveProductReference( + reference: Pick, +): Promise { + const found = inventory.find(i => i.upc === reference.upc); + + if (!found) { + return; + } + + return Object.assign(new Product(), { + ...reference, + ...found, + }); +} diff --git a/examples/apollo-federation-2/inventory/product.ts b/examples/apollo-federation-2/inventory/product.ts new file mode 100644 index 000000000..58b175436 --- /dev/null +++ b/examples/apollo-federation-2/inventory/product.ts @@ -0,0 +1,22 @@ +import { ObjectType, Directive, Field } from "../../../src"; + +@ObjectType() +@Directive("@extends") +@Directive("@interfaceObject") +@Directive(`@key(fields: "upc")`) +export default class Product { + @Field() + @Directive("@external") + upc: string; + + @Field() + @Directive("@external") + weight: number; + + @Field() + @Directive("@external") + price: number; + + @Field() + inStock: boolean; +} diff --git a/examples/apollo-federation-2/inventory/resolver.ts b/examples/apollo-federation-2/inventory/resolver.ts new file mode 100644 index 000000000..7f1612a84 --- /dev/null +++ b/examples/apollo-federation-2/inventory/resolver.ts @@ -0,0 +1,18 @@ +import { Resolver, FieldResolver, Directive, Root } from "../../../src"; + +import Product from "./product"; + +@Resolver(of => Product) +export default class InventoryResolver { + @Directive(`@requires(fields: "price weight")`) + @FieldResolver(returns => Number) + async shippingEstimate(@Root() product: Product): Promise { + // free for expensive items + if (product.price > 1000) { + return 0; + } + + // estimate is based on weight + return product.weight * 0.5; + } +} diff --git a/examples/apollo-federation-2/products/data.ts b/examples/apollo-federation-2/products/data.ts new file mode 100644 index 000000000..05682e2b6 --- /dev/null +++ b/examples/apollo-federation-2/products/data.ts @@ -0,0 +1,27 @@ +import Seating from "./seating"; +import Dining from "./dining"; +import Product from "./product"; + +export const products: Product[] = [ + Object.assign(new Dining(), { + upc: "1", + name: "Table", + price: 899, + weight: 100, + height: "3ft", + }), + Object.assign(new Seating(), { + upc: "2", + name: "Couch", + price: 1299, + weight: 1000, + seats: 2, + }), + Object.assign(new Seating(), { + upc: "3", + name: "Chair", + price: 54, + weight: 50, + seats: 1, + }), +]; diff --git a/examples/apollo-federation-2/products/dining.ts b/examples/apollo-federation-2/products/dining.ts new file mode 100644 index 000000000..7638a4d6e --- /dev/null +++ b/examples/apollo-federation-2/products/dining.ts @@ -0,0 +1,9 @@ +import { Directive, Field, ObjectType } from "../../../src"; +import Product from "./product"; + +@Directive(`@key(fields: "upc")`) +@ObjectType({ implements: Product }) +export default class Dining extends Product { + @Field() + height: string; +} diff --git a/examples/apollo-federation-2/products/index.ts b/examples/apollo-federation-2/products/index.ts new file mode 100644 index 000000000..f0dc2ee5d --- /dev/null +++ b/examples/apollo-federation-2/products/index.ts @@ -0,0 +1,26 @@ +import { ApolloServer } from "apollo-server"; + +import ProductsResolver from "./resolver"; +import Product from "./product"; +import { resolveProductReference } from "./product-reference"; +import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; + +export async function listen(port: number): Promise { + const schema = await buildFederatedSchema( + { + resolvers: [ProductsResolver], + orphanedTypes: [Product], + }, + { + Product: { __resolveReference: resolveProductReference }, + }, + ); + + const server = new ApolloServer({ schema }); + + const { url } = await server.listen({ port }); + + console.log(`Products service ready at ${url}`); + + return url; +} diff --git a/examples/apollo-federation-2/products/product-reference.ts b/examples/apollo-federation-2/products/product-reference.ts new file mode 100644 index 000000000..1d9d9b53b --- /dev/null +++ b/examples/apollo-federation-2/products/product-reference.ts @@ -0,0 +1,8 @@ +import { products } from "./data"; +import Product from "./product"; + +export async function resolveProductReference( + reference: Pick, +): Promise { + return products.find(p => p.upc === reference.upc); +} diff --git a/examples/apollo-federation-2/products/product.ts b/examples/apollo-federation-2/products/product.ts new file mode 100644 index 000000000..dcb0f2418 --- /dev/null +++ b/examples/apollo-federation-2/products/product.ts @@ -0,0 +1,17 @@ +import { Directive, Field, InterfaceType } from "../../../src"; + +@Directive(`@key(fields: "upc")`) +@InterfaceType() +export default abstract class Product { + @Field() + upc: string; + + @Field() + name: string; + + @Field() + price: number; + + @Field() + weight: number; +} diff --git a/examples/apollo-federation-2/products/resolver.ts b/examples/apollo-federation-2/products/resolver.ts new file mode 100644 index 000000000..70d60c306 --- /dev/null +++ b/examples/apollo-federation-2/products/resolver.ts @@ -0,0 +1,15 @@ +import { Resolver, Query, Arg } from "../../../src"; + +import Product from "./product"; +import { products } from "./data"; + +@Resolver(of => Product) +export default class ProductsResolver { + @Query(returns => [Product]) + async topProducts( + @Arg("first", { defaultValue: 5 }) + first: number, + ): Promise { + return products.slice(0, first); + } +} diff --git a/examples/apollo-federation-2/products/seating.ts b/examples/apollo-federation-2/products/seating.ts new file mode 100644 index 000000000..a333c7118 --- /dev/null +++ b/examples/apollo-federation-2/products/seating.ts @@ -0,0 +1,9 @@ +import { Directive, Field, ObjectType } from "../../../src"; +import Product from "./product"; + +@Directive(`@key(fields: "upc")`) +@ObjectType({ implements: Product }) +export default class Seating extends Product { + @Field() + seats: number; +} diff --git a/examples/apollo-federation-2/reviews/index.ts b/examples/apollo-federation-2/reviews/index.ts new file mode 100644 index 000000000..26a9e23e5 --- /dev/null +++ b/examples/apollo-federation-2/reviews/index.ts @@ -0,0 +1,23 @@ +import { ApolloServer } from "apollo-server"; + +import ReviewsResolver from "./review/resolver"; +import ProductReviewsResolver from "./product/resolver"; +import UserReviewsResolver from "./user/resolver"; +import Review from "./review/review"; +import User from "./user/user"; +import Product from "./product/product"; +import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; + +export async function listen(port: number): Promise { + const schema = await buildFederatedSchema({ + resolvers: [ReviewsResolver, ProductReviewsResolver, UserReviewsResolver], + orphanedTypes: [User, Review, Product], + }); + + const server = new ApolloServer({ schema }); + + const { url } = await server.listen({ port }); + console.log(`Reviews service ready at ${url}`); + + return url; +} diff --git a/examples/apollo-federation-2/reviews/product/product.ts b/examples/apollo-federation-2/reviews/product/product.ts new file mode 100644 index 000000000..6dfc75125 --- /dev/null +++ b/examples/apollo-federation-2/reviews/product/product.ts @@ -0,0 +1,11 @@ +import { ObjectType, Directive, Field } from "../../../../src"; + +@Directive("@extends") +@Directive(`@key(fields: "upc")`) +@Directive("@interfaceObject") +@ObjectType() +export default class Product { + @Directive("@external") + @Field() + upc: string; +} diff --git a/examples/apollo-federation-2/reviews/product/resolver.ts b/examples/apollo-federation-2/reviews/product/resolver.ts new file mode 100644 index 000000000..d60de4224 --- /dev/null +++ b/examples/apollo-federation-2/reviews/product/resolver.ts @@ -0,0 +1,13 @@ +import { Resolver, FieldResolver, Root } from "../../../../src"; + +import Product from "./product"; +import { reviews } from "../review/data"; +import Review from "../review/review"; + +@Resolver(of => Product) +export default class ProductReviewsResolver { + @FieldResolver(() => [Review]) + async reviews(@Root() product: Product): Promise { + return reviews.filter(review => review.product.upc === product.upc); + } +} diff --git a/examples/apollo-federation-2/reviews/review/data.ts b/examples/apollo-federation-2/reviews/review/data.ts new file mode 100644 index 000000000..51ffd7c91 --- /dev/null +++ b/examples/apollo-federation-2/reviews/review/data.ts @@ -0,0 +1,62 @@ +import Review from "./review"; +import User from "../user/user"; +import Product from "../product/product"; + +export const reviews: Review[] = [ + createReview({ + id: "1", + author: createUser({ + id: "1", + username: "@ada", + }), + product: createProduct({ + upc: "1", + }), + body: "Love it!", + }), + createReview({ + id: "2", + author: createUser({ + id: "1", + username: "@ada", + }), + product: createProduct({ + upc: "2", + }), + body: "Too expensive.", + }), + createReview({ + id: "3", + author: createUser({ + id: "2", + username: "@complete", + }), + product: createProduct({ + upc: "3", + }), + body: "Could be better.", + }), + createReview({ + id: "4", + author: createUser({ + id: "2", + username: "@complete", + }), + product: createProduct({ + upc: "1", + }), + body: "Prefer something else.", + }), +]; + +function createReview(reviewData: Partial) { + return Object.assign(new Review(), reviewData); +} + +function createUser(userData: Partial) { + return Object.assign(new User(), userData); +} + +function createProduct(productData: Partial) { + return Object.assign(new Product(), productData); +} diff --git a/examples/apollo-federation-2/reviews/review/resolver.ts b/examples/apollo-federation-2/reviews/review/resolver.ts new file mode 100644 index 000000000..c20b8a2ba --- /dev/null +++ b/examples/apollo-federation-2/reviews/review/resolver.ts @@ -0,0 +1,12 @@ +import { Resolver, FieldResolver } from "../../../../src"; + +import Review from "./review"; +import { reviews } from "./data"; + +@Resolver(of => Review) +export default class ReviewsResolver { + @FieldResolver(returns => [Review]) + async reviews(): Promise { + return reviews; + } +} diff --git a/examples/apollo-federation-2/reviews/review/review.ts b/examples/apollo-federation-2/reviews/review/review.ts new file mode 100644 index 000000000..bf1ea52c5 --- /dev/null +++ b/examples/apollo-federation-2/reviews/review/review.ts @@ -0,0 +1,21 @@ +import { Directive, ObjectType, Field, ID } from "../../../../src"; + +import User from "../user/user"; +import Product from "../product/product"; + +@Directive(`@key(fields: "id")`) +@ObjectType() +export default class Review { + @Field(type => ID) + id: string; + + @Field() + body: string; + + @Directive(`@provides(fields: "username")`) + @Field() + author: User; + + @Field() + product: Product; +} diff --git a/examples/apollo-federation-2/reviews/user/resolver.ts b/examples/apollo-federation-2/reviews/user/resolver.ts new file mode 100644 index 000000000..cc9de8d4b --- /dev/null +++ b/examples/apollo-federation-2/reviews/user/resolver.ts @@ -0,0 +1,13 @@ +import { Resolver, FieldResolver, Root } from "../../../../src"; + +import User from "./user"; +import Review from "../review/review"; +import { reviews } from "../review/data"; + +@Resolver(of => User) +export default class UserReviewsResolver { + @FieldResolver(returns => [Review]) + async reviews(@Root() user: User): Promise { + return reviews.filter(review => review.author.id === user.id); + } +} diff --git a/examples/apollo-federation-2/reviews/user/user.ts b/examples/apollo-federation-2/reviews/user/user.ts new file mode 100644 index 000000000..cecb0358e --- /dev/null +++ b/examples/apollo-federation-2/reviews/user/user.ts @@ -0,0 +1,14 @@ +import { Directive, ObjectType, Field, ID } from "../../../../src"; + +@Directive("@extends") +@Directive(`@key(fields: "id")`) +@ObjectType() +export default class User { + @Directive("@external") + @Field(type => ID) + id: string; + + @Directive("@external") + @Field() + username: string; +} diff --git a/examples/apollo-federation-2/schema.graphql b/examples/apollo-federation-2/schema.graphql new file mode 100644 index 000000000..9da36f9d9 --- /dev/null +++ b/examples/apollo-federation-2/schema.graphql @@ -0,0 +1,57 @@ +# ----------------------------------------------- +# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!! +# !!! DO NOT MODIFY THIS FILE BY YOURSELF !!! +# ----------------------------------------------- + +type Dining implements Product { + height: String! + inStock: Boolean! + name: String! + price: Float! + reviews: [Review!]! + shippingEstimate: Float! + upc: String! + weight: Float! +} + +interface Product { + inStock: Boolean! + name: String! + price: Float! + reviews: [Review!]! + shippingEstimate: Float! + upc: String! + weight: Float! +} + +type Query { + me: User! + topProducts(first: Float! = 5): [Product!]! +} + +type Review { + author: User! + body: String! + id: ID! + product: Product! + reviews: [Review!]! +} + +type Seating implements Product { + inStock: Boolean! + name: String! + price: Float! + reviews: [Review!]! + seats: Float! + shippingEstimate: Float! + upc: String! + weight: Float! +} + +type User { + birthDate: String! + id: ID! + name: String! + reviews: [Review!]! + username: String! +} \ No newline at end of file diff --git a/examples/apollo-federation/accounts/user.ts b/examples/apollo-federation/accounts/user.ts index 9e1cbaa1c..3e74c425a 100644 --- a/examples/apollo-federation/accounts/user.ts +++ b/examples/apollo-federation/accounts/user.ts @@ -6,7 +6,6 @@ export default class User { @Field(type => ID) id: string; - @Directive("@shareable") @Field() username: string; diff --git a/examples/apollo-federation/helpers/buildFederatedSchema.ts b/examples/apollo-federation/helpers/buildFederatedSchema.ts index d96d3ebff..80d2e3dc7 100644 --- a/examples/apollo-federation/helpers/buildFederatedSchema.ts +++ b/examples/apollo-federation/helpers/buildFederatedSchema.ts @@ -16,22 +16,7 @@ export async function buildFederatedSchema( // build Apollo Subgraph schema const federatedSchema = buildSubgraphSchema({ - typeDefs: gql` - extend schema - @link( - url: "https://specs.apollo.dev/federation/v2.3" - import: [ - "@key" - "@shareable" - "@provides" - "@extends" - "@requires" - "@external" - "@interfaceObject" - ] - ) - ${printSchemaWithDirectives(schema)} - `, + typeDefs: gql(printSchemaWithDirectives(schema)), // merge schema's resolvers with reference resolvers resolvers: deepMerge(createResolversMap(schema) as any, referenceResolvers), }); diff --git a/examples/apollo-federation/inventory/product.ts b/examples/apollo-federation/inventory/product.ts index 58b175436..a758b5a5a 100644 --- a/examples/apollo-federation/inventory/product.ts +++ b/examples/apollo-federation/inventory/product.ts @@ -2,7 +2,6 @@ import { ObjectType, Directive, Field } from "../../../src"; @ObjectType() @Directive("@extends") -@Directive("@interfaceObject") @Directive(`@key(fields: "upc")`) export default class Product { @Field() diff --git a/examples/apollo-federation/products/data.ts b/examples/apollo-federation/products/data.ts index 05682e2b6..9f98968fe 100644 --- a/examples/apollo-federation/products/data.ts +++ b/examples/apollo-federation/products/data.ts @@ -1,27 +1,26 @@ -import Seating from "./seating"; -import Dining from "./dining"; import Product from "./product"; export const products: Product[] = [ - Object.assign(new Dining(), { + createProduct({ upc: "1", name: "Table", price: 899, weight: 100, - height: "3ft", }), - Object.assign(new Seating(), { + createProduct({ upc: "2", name: "Couch", price: 1299, weight: 1000, - seats: 2, }), - Object.assign(new Seating(), { + createProduct({ upc: "3", name: "Chair", price: 54, weight: 50, - seats: 1, }), ]; + +function createProduct(productData: Partial) { + return Object.assign(new Product(), productData); +} diff --git a/examples/apollo-federation/products/product.ts b/examples/apollo-federation/products/product.ts index dcb0f2418..4761234d5 100644 --- a/examples/apollo-federation/products/product.ts +++ b/examples/apollo-federation/products/product.ts @@ -1,8 +1,8 @@ -import { Directive, Field, InterfaceType } from "../../../src"; +import { ObjectType, Directive, Field } from "../../../src"; @Directive(`@key(fields: "upc")`) -@InterfaceType() -export default abstract class Product { +@ObjectType() +export default class Product { @Field() upc: string; diff --git a/examples/apollo-federation/reviews/product/product.ts b/examples/apollo-federation/reviews/product/product.ts index 6dfc75125..4a21adf8c 100644 --- a/examples/apollo-federation/reviews/product/product.ts +++ b/examples/apollo-federation/reviews/product/product.ts @@ -2,7 +2,6 @@ import { ObjectType, Directive, Field } from "../../../../src"; @Directive("@extends") @Directive(`@key(fields: "upc")`) -@Directive("@interfaceObject") @ObjectType() export default class Product { @Directive("@external") From 56e37805d7ad647bf1fe27b5c924a35fb4c4309c Mon Sep 17 00:00:00 2001 From: Jason Hanggi Date: Wed, 9 Aug 2023 15:00:51 -0500 Subject: [PATCH 4/8] switch to new apollo standalone server --- .../apollo-federation-2/accounts/index.ts | 8 +-- examples/apollo-federation-2/index.ts | 52 +++++++++++-------- .../apollo-federation-2/inventory/index.ts | 8 +-- .../apollo-federation-2/products/index.ts | 9 ++-- examples/apollo-federation-2/reviews/index.ts | 12 ++--- 5 files changed, 48 insertions(+), 41 deletions(-) diff --git a/examples/apollo-federation-2/accounts/index.ts b/examples/apollo-federation-2/accounts/index.ts index a9f6ead2d..1a9020964 100644 --- a/examples/apollo-federation-2/accounts/index.ts +++ b/examples/apollo-federation-2/accounts/index.ts @@ -1,9 +1,9 @@ -import { ApolloServer } from "apollo-server"; - +import { ApolloServer } from "@apollo/server"; +import { startStandaloneServer } from "@apollo/server/standalone"; import AccountsResolver from "./resolver"; import User from "./user"; -import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; import { resolveUserReference } from "./user-reference"; +import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { const schema = await buildFederatedSchema( @@ -18,7 +18,7 @@ export async function listen(port: number): Promise { const server = new ApolloServer({ schema }); - const { url } = await server.listen({ port }); + const { url } = await startStandaloneServer(server, { listen: { port } }); console.log(`Accounts service ready at ${url}`); return url; diff --git a/examples/apollo-federation-2/index.ts b/examples/apollo-federation-2/index.ts index d38244cd4..154affba6 100644 --- a/examples/apollo-federation-2/index.ts +++ b/examples/apollo-federation-2/index.ts @@ -1,38 +1,46 @@ import "reflect-metadata"; -import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway"; -import { ApolloServer } from "apollo-server"; import path from "path"; -import { emitSchemaDefinitionFile } from "../../src"; - +import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway"; +import { ApolloServer } from "@apollo/server"; +import { startStandaloneServer } from "@apollo/server/standalone"; import * as accounts from "./accounts"; -import * as reviews from "./reviews"; -import * as products from "./products"; import * as inventory from "./inventory"; +import * as products from "./products"; +import * as reviews from "./reviews"; +import { emitSchemaDefinitionFile } from "../../src"; + +const startGraph = async (name: string, urlOrPromise: string | Promise) => { + const url = await urlOrPromise; + return { name, url }; +}; async function bootstrap() { - const gateway = new ApolloGateway({ + const subgraphs = await Promise.all([ + startGraph("accounts", accounts.listen(4001)), + startGraph("reviews", reviews.listen(4002)), + startGraph("products", products.listen(4003)), + startGraph("inventory", inventory.listen(4004)), + ]); + + const schemaGateway = new ApolloGateway({ supergraphSdl: new IntrospectAndCompose({ - subgraphs: [ - { name: "accounts", url: await accounts.listen(4001) }, - { name: "reviews", url: await reviews.listen(4002) }, - { name: "products", url: await products.listen(4003) }, - { name: "inventory", url: await inventory.listen(4004) }, - ], + subgraphs, }), }); - - const { schema, executor } = await gateway.load(); - + const { schema } = await schemaGateway.load(); await emitSchemaDefinitionFile(path.resolve(__dirname, "schema.graphql"), schema); + await schemaGateway.stop(); - const server = new ApolloServer({ - schema, - executor, + const gateway = new ApolloGateway({ + supergraphSdl: new IntrospectAndCompose({ + subgraphs, + }), }); + const server = new ApolloServer({ gateway }); - server.listen({ port: 4000 }).then(({ url }) => { - console.log(`Apollo Gateway ready at ${url}`); - }); + const { url } = await startStandaloneServer(server, { listen: { port: 4000 } }); + + console.log(`Apollo Gateway ready at ${url}`); } bootstrap().catch(console.error); diff --git a/examples/apollo-federation-2/inventory/index.ts b/examples/apollo-federation-2/inventory/index.ts index 9642e3dbf..05ed75790 100644 --- a/examples/apollo-federation-2/inventory/index.ts +++ b/examples/apollo-federation-2/inventory/index.ts @@ -1,8 +1,8 @@ -import { ApolloServer } from "apollo-server"; - -import InventoryResolver from "./resolver"; +import { ApolloServer } from "@apollo/server"; +import { startStandaloneServer } from "@apollo/server/standalone"; import Product from "./product"; import { resolveProductReference } from "./product-reference"; +import InventoryResolver from "./resolver"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { @@ -18,7 +18,7 @@ export async function listen(port: number): Promise { const server = new ApolloServer({ schema }); - const { url } = await server.listen({ port }); + const { url } = await startStandaloneServer(server, { listen: { port } }); console.log(`Inventory service ready at ${url}`); return url; diff --git a/examples/apollo-federation-2/products/index.ts b/examples/apollo-federation-2/products/index.ts index f0dc2ee5d..b064660a0 100644 --- a/examples/apollo-federation-2/products/index.ts +++ b/examples/apollo-federation-2/products/index.ts @@ -1,8 +1,8 @@ -import { ApolloServer } from "apollo-server"; - -import ProductsResolver from "./resolver"; +import { ApolloServer } from "@apollo/server"; +import { startStandaloneServer } from "@apollo/server/standalone"; import Product from "./product"; import { resolveProductReference } from "./product-reference"; +import ProductsResolver from "./resolver"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { @@ -18,8 +18,7 @@ export async function listen(port: number): Promise { const server = new ApolloServer({ schema }); - const { url } = await server.listen({ port }); - + const { url } = await startStandaloneServer(server, { listen: { port } }); console.log(`Products service ready at ${url}`); return url; diff --git a/examples/apollo-federation-2/reviews/index.ts b/examples/apollo-federation-2/reviews/index.ts index 26a9e23e5..e5e09d703 100644 --- a/examples/apollo-federation-2/reviews/index.ts +++ b/examples/apollo-federation-2/reviews/index.ts @@ -1,11 +1,11 @@ -import { ApolloServer } from "apollo-server"; - -import ReviewsResolver from "./review/resolver"; +import { ApolloServer } from "@apollo/server"; +import { startStandaloneServer } from "@apollo/server/standalone"; +import Product from "./product/product"; import ProductReviewsResolver from "./product/resolver"; -import UserReviewsResolver from "./user/resolver"; +import ReviewsResolver from "./review/resolver"; import Review from "./review/review"; +import UserReviewsResolver from "./user/resolver"; import User from "./user/user"; -import Product from "./product/product"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { @@ -16,7 +16,7 @@ export async function listen(port: number): Promise { const server = new ApolloServer({ schema }); - const { url } = await server.listen({ port }); + const { url } = await startStandaloneServer(server, { listen: { port } }); console.log(`Reviews service ready at ${url}`); return url; From 202be13fbfd8639261cb207a8df25e611fe98737 Mon Sep 17 00:00:00 2001 From: Jason Hanggi Date: Wed, 9 Aug 2023 15:07:12 -0500 Subject: [PATCH 5/8] Add apollo federation 2 example to readme --- examples/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 7d5e6638f..935b1b01a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -41,7 +41,8 @@ Each subdirectory contains a `examples.graphql` file with predefined GraphQL que - [TypeORM (automatic, lazy relations) \*](./typeorm-lazy-relations) - [MikroORM \*](./mikro-orm) - [Typegoose \*](./typegoose) -- [Apollo federation](./apollo-federation) +- [Apollo Federation](./apollo-federation) +- [Apollo Federation 2](./apollo-federation-2) - [Apollo Cache Control](./apollo-cache) - [GraphQL Scalars](./graphql-scalars) - [TSyringe](./tsyringe) From 7561da429d21269adfec4bf68a71ec52dae8cb11 Mon Sep 17 00:00:00 2001 From: Jason Hanggi Date: Wed, 9 Aug 2023 15:30:03 -0500 Subject: [PATCH 6/8] lint/cleanup --- examples/apollo-federation-2/accounts/data.ts | 10 +++---- .../apollo-federation-2/accounts/index.ts | 6 ++-- .../apollo-federation-2/accounts/resolver.ts | 11 ++++--- .../{user-reference.ts => user.reference.ts} | 2 +- examples/apollo-federation-2/accounts/user.ts | 14 ++++----- .../helpers/buildFederatedSchema.ts | 6 ++-- examples/apollo-federation-2/index.ts | 2 +- .../apollo-federation-2/inventory/index.ts | 6 ++-- ...duct-reference.ts => product.reference.ts} | 4 +-- .../apollo-federation-2/inventory/product.ts | 12 ++++---- .../apollo-federation-2/inventory/resolver.ts | 11 ++++--- examples/apollo-federation-2/products/data.ts | 6 ++-- .../apollo-federation-2/products/dining.ts | 8 ++--- .../apollo-federation-2/products/index.ts | 6 ++-- ...duct-reference.ts => product.reference.ts} | 2 +- .../apollo-federation-2/products/product.ts | 12 ++++---- .../apollo-federation-2/products/resolver.ts | 11 ++++--- .../apollo-federation-2/products/seating.ts | 8 ++--- examples/apollo-federation-2/reviews/index.ts | 12 ++++---- .../reviews/product/product.ts | 6 ++-- .../reviews/product/resolver.ts | 11 ++++--- .../reviews/review/data.ts | 30 +++++++++---------- .../reviews/review/resolver.ts | 11 ++++--- .../reviews/review/review.ts | 19 ++++++------ .../reviews/user/resolver.ts | 13 ++++---- .../apollo-federation-2/reviews/user/user.ts | 10 +++---- examples/apollo-federation/products/dining.ts | 9 ------ .../apollo-federation/products/seating.ts | 9 ------ 28 files changed, 121 insertions(+), 146 deletions(-) rename examples/apollo-federation-2/accounts/{user-reference.ts => user.reference.ts} (82%) rename examples/apollo-federation-2/inventory/{product-reference.ts => product.reference.ts} (84%) rename examples/apollo-federation-2/products/{product-reference.ts => product.reference.ts} (83%) delete mode 100644 examples/apollo-federation/products/dining.ts delete mode 100644 examples/apollo-federation/products/seating.ts diff --git a/examples/apollo-federation-2/accounts/data.ts b/examples/apollo-federation-2/accounts/data.ts index 0b4f3e096..fde6e39b3 100644 --- a/examples/apollo-federation-2/accounts/data.ts +++ b/examples/apollo-federation-2/accounts/data.ts @@ -1,4 +1,8 @@ -import User from "./user"; +import { User } from "./user"; + +function createUser(userData: Partial) { + return Object.assign(new User(), userData); +} export const users: User[] = [ createUser({ @@ -14,7 +18,3 @@ export const users: User[] = [ username: "@complete", }), ]; - -function createUser(userData: Partial) { - return Object.assign(new User(), userData); -} diff --git a/examples/apollo-federation-2/accounts/index.ts b/examples/apollo-federation-2/accounts/index.ts index 1a9020964..ca29e999a 100644 --- a/examples/apollo-federation-2/accounts/index.ts +++ b/examples/apollo-federation-2/accounts/index.ts @@ -1,8 +1,8 @@ import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; -import AccountsResolver from "./resolver"; -import User from "./user"; -import { resolveUserReference } from "./user-reference"; +import { AccountsResolver } from "./resolver"; +import { User } from "./user"; +import { resolveUserReference } from "./user.reference"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { diff --git a/examples/apollo-federation-2/accounts/resolver.ts b/examples/apollo-federation-2/accounts/resolver.ts index cd6a9f257..1f92764f5 100644 --- a/examples/apollo-federation-2/accounts/resolver.ts +++ b/examples/apollo-federation-2/accounts/resolver.ts @@ -1,11 +1,10 @@ -import { Resolver, Query } from "../../../src"; - -import User from "./user"; +import { Query, Resolver } from "type-graphql"; import { users } from "./data"; +import { User } from "./user"; -@Resolver(of => User) -export default class AccountsResolver { - @Query(returns => User) +@Resolver(_of => User) +export class AccountsResolver { + @Query(_returns => User) me(): User { return users[0]; } diff --git a/examples/apollo-federation-2/accounts/user-reference.ts b/examples/apollo-federation-2/accounts/user.reference.ts similarity index 82% rename from examples/apollo-federation-2/accounts/user-reference.ts rename to examples/apollo-federation-2/accounts/user.reference.ts index aed680677..3d7112253 100644 --- a/examples/apollo-federation-2/accounts/user-reference.ts +++ b/examples/apollo-federation-2/accounts/user.reference.ts @@ -1,5 +1,5 @@ import { users } from "./data"; -import User from "./user"; +import { type User } from "./user"; export async function resolveUserReference(reference: Pick): Promise { return users.find(u => u.id === reference.id)!; diff --git a/examples/apollo-federation-2/accounts/user.ts b/examples/apollo-federation-2/accounts/user.ts index 9e1cbaa1c..ea9e6c423 100644 --- a/examples/apollo-federation-2/accounts/user.ts +++ b/examples/apollo-federation-2/accounts/user.ts @@ -1,18 +1,18 @@ -import { Field, ObjectType, Directive, ID } from "../../../src"; +import { Directive, Field, ID, ObjectType } from "type-graphql"; @Directive(`@key(fields: "id")`) @ObjectType() -export default class User { - @Field(type => ID) - id: string; +export class User { + @Field(_type => ID) + id!: string; @Directive("@shareable") @Field() - username: string; + username!: string; @Field() - name: string; + name!: string; @Field() - birthDate: string; + birthDate!: string; } diff --git a/examples/apollo-federation-2/helpers/buildFederatedSchema.ts b/examples/apollo-federation-2/helpers/buildFederatedSchema.ts index d96d3ebff..a303dfb33 100644 --- a/examples/apollo-federation-2/helpers/buildFederatedSchema.ts +++ b/examples/apollo-federation-2/helpers/buildFederatedSchema.ts @@ -1,8 +1,8 @@ -import { buildSchema, BuildSchemaOptions, createResolversMap } from "../../../src"; +import { buildSubgraphSchema } from "@apollo/subgraph"; +import { type IResolvers, printSchemaWithDirectives } from "@graphql-tools/utils"; import gql from "graphql-tag"; import deepMerge from "lodash.merge"; -import { buildSubgraphSchema } from "@apollo/subgraph"; -import { IResolvers, printSchemaWithDirectives } from "@graphql-tools/utils"; +import { type BuildSchemaOptions, buildSchema, createResolversMap } from "type-graphql"; export async function buildFederatedSchema( options: Omit, diff --git a/examples/apollo-federation-2/index.ts b/examples/apollo-federation-2/index.ts index 154affba6..e13b587c3 100644 --- a/examples/apollo-federation-2/index.ts +++ b/examples/apollo-federation-2/index.ts @@ -3,11 +3,11 @@ import path from "path"; import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway"; import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; +import { emitSchemaDefinitionFile } from "type-graphql"; import * as accounts from "./accounts"; import * as inventory from "./inventory"; import * as products from "./products"; import * as reviews from "./reviews"; -import { emitSchemaDefinitionFile } from "../../src"; const startGraph = async (name: string, urlOrPromise: string | Promise) => { const url = await urlOrPromise; diff --git a/examples/apollo-federation-2/inventory/index.ts b/examples/apollo-federation-2/inventory/index.ts index 05ed75790..ad8cdfb1d 100644 --- a/examples/apollo-federation-2/inventory/index.ts +++ b/examples/apollo-federation-2/inventory/index.ts @@ -1,8 +1,8 @@ import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; -import Product from "./product"; -import { resolveProductReference } from "./product-reference"; -import InventoryResolver from "./resolver"; +import { Product } from "./product"; +import { resolveProductReference } from "./product.reference"; +import { InventoryResolver } from "./resolver"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { diff --git a/examples/apollo-federation-2/inventory/product-reference.ts b/examples/apollo-federation-2/inventory/product.reference.ts similarity index 84% rename from examples/apollo-federation-2/inventory/product-reference.ts rename to examples/apollo-federation-2/inventory/product.reference.ts index 7cc998250..31798d5f8 100644 --- a/examples/apollo-federation-2/inventory/product-reference.ts +++ b/examples/apollo-federation-2/inventory/product.reference.ts @@ -1,5 +1,5 @@ import { inventory } from "./data"; -import Product from "./product"; +import { Product } from "./product"; export async function resolveProductReference( reference: Pick, @@ -7,7 +7,7 @@ export async function resolveProductReference( const found = inventory.find(i => i.upc === reference.upc); if (!found) { - return; + return undefined; } return Object.assign(new Product(), { diff --git a/examples/apollo-federation-2/inventory/product.ts b/examples/apollo-federation-2/inventory/product.ts index 58b175436..c581bb2ae 100644 --- a/examples/apollo-federation-2/inventory/product.ts +++ b/examples/apollo-federation-2/inventory/product.ts @@ -1,22 +1,22 @@ -import { ObjectType, Directive, Field } from "../../../src"; +import { Directive, Field, ObjectType } from "type-graphql"; @ObjectType() @Directive("@extends") @Directive("@interfaceObject") @Directive(`@key(fields: "upc")`) -export default class Product { +export class Product { @Field() @Directive("@external") - upc: string; + upc!: string; @Field() @Directive("@external") - weight: number; + weight!: number; @Field() @Directive("@external") - price: number; + price!: number; @Field() - inStock: boolean; + inStock!: boolean; } diff --git a/examples/apollo-federation-2/inventory/resolver.ts b/examples/apollo-federation-2/inventory/resolver.ts index 7f1612a84..cf593d874 100644 --- a/examples/apollo-federation-2/inventory/resolver.ts +++ b/examples/apollo-federation-2/inventory/resolver.ts @@ -1,11 +1,10 @@ -import { Resolver, FieldResolver, Directive, Root } from "../../../src"; +import { Directive, FieldResolver, Resolver, Root } from "type-graphql"; +import { Product } from "./product"; -import Product from "./product"; - -@Resolver(of => Product) -export default class InventoryResolver { +@Resolver(_of => Product) +export class InventoryResolver { @Directive(`@requires(fields: "price weight")`) - @FieldResolver(returns => Number) + @FieldResolver(_returns => Number) async shippingEstimate(@Root() product: Product): Promise { // free for expensive items if (product.price > 1000) { diff --git a/examples/apollo-federation-2/products/data.ts b/examples/apollo-federation-2/products/data.ts index 05682e2b6..93de15818 100644 --- a/examples/apollo-federation-2/products/data.ts +++ b/examples/apollo-federation-2/products/data.ts @@ -1,6 +1,6 @@ -import Seating from "./seating"; -import Dining from "./dining"; -import Product from "./product"; +import { Dining } from "./dining"; +import { type Product } from "./product"; +import { Seating } from "./seating"; export const products: Product[] = [ Object.assign(new Dining(), { diff --git a/examples/apollo-federation-2/products/dining.ts b/examples/apollo-federation-2/products/dining.ts index 7638a4d6e..251781c49 100644 --- a/examples/apollo-federation-2/products/dining.ts +++ b/examples/apollo-federation-2/products/dining.ts @@ -1,9 +1,9 @@ -import { Directive, Field, ObjectType } from "../../../src"; -import Product from "./product"; +import { Directive, Field, ObjectType } from "type-graphql"; +import { Product } from "./product"; @Directive(`@key(fields: "upc")`) @ObjectType({ implements: Product }) -export default class Dining extends Product { +export class Dining extends Product { @Field() - height: string; + height!: string; } diff --git a/examples/apollo-federation-2/products/index.ts b/examples/apollo-federation-2/products/index.ts index b064660a0..b5c31911f 100644 --- a/examples/apollo-federation-2/products/index.ts +++ b/examples/apollo-federation-2/products/index.ts @@ -1,8 +1,8 @@ import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; -import Product from "./product"; -import { resolveProductReference } from "./product-reference"; -import ProductsResolver from "./resolver"; +import { Product } from "./product"; +import { resolveProductReference } from "./product.reference"; +import { ProductsResolver } from "./resolver"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { diff --git a/examples/apollo-federation-2/products/product-reference.ts b/examples/apollo-federation-2/products/product.reference.ts similarity index 83% rename from examples/apollo-federation-2/products/product-reference.ts rename to examples/apollo-federation-2/products/product.reference.ts index 1d9d9b53b..e6488b6c5 100644 --- a/examples/apollo-federation-2/products/product-reference.ts +++ b/examples/apollo-federation-2/products/product.reference.ts @@ -1,5 +1,5 @@ import { products } from "./data"; -import Product from "./product"; +import { type Product } from "./product"; export async function resolveProductReference( reference: Pick, diff --git a/examples/apollo-federation-2/products/product.ts b/examples/apollo-federation-2/products/product.ts index dcb0f2418..bcdb56180 100644 --- a/examples/apollo-federation-2/products/product.ts +++ b/examples/apollo-federation-2/products/product.ts @@ -1,17 +1,17 @@ -import { Directive, Field, InterfaceType } from "../../../src"; +import { Directive, Field, InterfaceType } from "type-graphql"; @Directive(`@key(fields: "upc")`) @InterfaceType() -export default abstract class Product { +export abstract class Product { @Field() - upc: string; + upc!: string; @Field() - name: string; + name!: string; @Field() - price: number; + price!: number; @Field() - weight: number; + weight!: number; } diff --git a/examples/apollo-federation-2/products/resolver.ts b/examples/apollo-federation-2/products/resolver.ts index 70d60c306..047cf52cb 100644 --- a/examples/apollo-federation-2/products/resolver.ts +++ b/examples/apollo-federation-2/products/resolver.ts @@ -1,11 +1,10 @@ -import { Resolver, Query, Arg } from "../../../src"; - -import Product from "./product"; +import { Arg, Query, Resolver } from "type-graphql"; import { products } from "./data"; +import { Product } from "./product"; -@Resolver(of => Product) -export default class ProductsResolver { - @Query(returns => [Product]) +@Resolver(_of => Product) +export class ProductsResolver { + @Query(_returns => [Product]) async topProducts( @Arg("first", { defaultValue: 5 }) first: number, diff --git a/examples/apollo-federation-2/products/seating.ts b/examples/apollo-federation-2/products/seating.ts index a333c7118..8f76c4e58 100644 --- a/examples/apollo-federation-2/products/seating.ts +++ b/examples/apollo-federation-2/products/seating.ts @@ -1,9 +1,9 @@ -import { Directive, Field, ObjectType } from "../../../src"; -import Product from "./product"; +import { Directive, Field, ObjectType } from "type-graphql"; +import { Product } from "./product"; @Directive(`@key(fields: "upc")`) @ObjectType({ implements: Product }) -export default class Seating extends Product { +export class Seating extends Product { @Field() - seats: number; + seats!: number; } diff --git a/examples/apollo-federation-2/reviews/index.ts b/examples/apollo-federation-2/reviews/index.ts index e5e09d703..876531428 100644 --- a/examples/apollo-federation-2/reviews/index.ts +++ b/examples/apollo-federation-2/reviews/index.ts @@ -1,11 +1,11 @@ import { ApolloServer } from "@apollo/server"; import { startStandaloneServer } from "@apollo/server/standalone"; -import Product from "./product/product"; -import ProductReviewsResolver from "./product/resolver"; -import ReviewsResolver from "./review/resolver"; -import Review from "./review/review"; -import UserReviewsResolver from "./user/resolver"; -import User from "./user/user"; +import { Product } from "./product/product"; +import { ProductReviewsResolver } from "./product/resolver"; +import { ReviewsResolver } from "./review/resolver"; +import { Review } from "./review/review"; +import { UserReviewsResolver } from "./user/resolver"; +import { User } from "./user/user"; import { buildFederatedSchema } from "../helpers/buildFederatedSchema"; export async function listen(port: number): Promise { diff --git a/examples/apollo-federation-2/reviews/product/product.ts b/examples/apollo-federation-2/reviews/product/product.ts index 6dfc75125..22f1588dc 100644 --- a/examples/apollo-federation-2/reviews/product/product.ts +++ b/examples/apollo-federation-2/reviews/product/product.ts @@ -1,11 +1,11 @@ -import { ObjectType, Directive, Field } from "../../../../src"; +import { Directive, Field, ObjectType } from "type-graphql"; @Directive("@extends") @Directive(`@key(fields: "upc")`) @Directive("@interfaceObject") @ObjectType() -export default class Product { +export class Product { @Directive("@external") @Field() - upc: string; + upc!: string; } diff --git a/examples/apollo-federation-2/reviews/product/resolver.ts b/examples/apollo-federation-2/reviews/product/resolver.ts index d60de4224..15f800ee1 100644 --- a/examples/apollo-federation-2/reviews/product/resolver.ts +++ b/examples/apollo-federation-2/reviews/product/resolver.ts @@ -1,11 +1,10 @@ -import { Resolver, FieldResolver, Root } from "../../../../src"; - -import Product from "./product"; +import { FieldResolver, Resolver, Root } from "type-graphql"; +import { Product } from "./product"; import { reviews } from "../review/data"; -import Review from "../review/review"; +import { Review } from "../review/review"; -@Resolver(of => Product) -export default class ProductReviewsResolver { +@Resolver(_of => Product) +export class ProductReviewsResolver { @FieldResolver(() => [Review]) async reviews(@Root() product: Product): Promise { return reviews.filter(review => review.product.upc === product.upc); diff --git a/examples/apollo-federation-2/reviews/review/data.ts b/examples/apollo-federation-2/reviews/review/data.ts index 51ffd7c91..c6e9ed0fc 100644 --- a/examples/apollo-federation-2/reviews/review/data.ts +++ b/examples/apollo-federation-2/reviews/review/data.ts @@ -1,6 +1,18 @@ -import Review from "./review"; -import User from "../user/user"; -import Product from "../product/product"; +import { Review } from "./review"; +import { Product } from "../product/product"; +import { User } from "../user/user"; + +function createReview(reviewData: Partial) { + return Object.assign(new Review(), reviewData); +} + +function createUser(userData: Partial) { + return Object.assign(new User(), userData); +} + +function createProduct(productData: Partial) { + return Object.assign(new Product(), productData); +} export const reviews: Review[] = [ createReview({ @@ -48,15 +60,3 @@ export const reviews: Review[] = [ body: "Prefer something else.", }), ]; - -function createReview(reviewData: Partial) { - return Object.assign(new Review(), reviewData); -} - -function createUser(userData: Partial) { - return Object.assign(new User(), userData); -} - -function createProduct(productData: Partial) { - return Object.assign(new Product(), productData); -} diff --git a/examples/apollo-federation-2/reviews/review/resolver.ts b/examples/apollo-federation-2/reviews/review/resolver.ts index c20b8a2ba..eb59bf84b 100644 --- a/examples/apollo-federation-2/reviews/review/resolver.ts +++ b/examples/apollo-federation-2/reviews/review/resolver.ts @@ -1,11 +1,10 @@ -import { Resolver, FieldResolver } from "../../../../src"; - -import Review from "./review"; +import { FieldResolver, Resolver } from "type-graphql"; import { reviews } from "./data"; +import { Review } from "./review"; -@Resolver(of => Review) -export default class ReviewsResolver { - @FieldResolver(returns => [Review]) +@Resolver(_of => Review) +export class ReviewsResolver { + @FieldResolver(_returns => [Review]) async reviews(): Promise { return reviews; } diff --git a/examples/apollo-federation-2/reviews/review/review.ts b/examples/apollo-federation-2/reviews/review/review.ts index bf1ea52c5..24c74e7ab 100644 --- a/examples/apollo-federation-2/reviews/review/review.ts +++ b/examples/apollo-federation-2/reviews/review/review.ts @@ -1,21 +1,20 @@ -import { Directive, ObjectType, Field, ID } from "../../../../src"; - -import User from "../user/user"; -import Product from "../product/product"; +import { Directive, Field, ID, ObjectType } from "type-graphql"; +import { Product } from "../product/product"; +import { User } from "../user/user"; @Directive(`@key(fields: "id")`) @ObjectType() -export default class Review { - @Field(type => ID) - id: string; +export class Review { + @Field(_type => ID) + id!: string; @Field() - body: string; + body!: string; @Directive(`@provides(fields: "username")`) @Field() - author: User; + author!: User; @Field() - product: Product; + product!: Product; } diff --git a/examples/apollo-federation-2/reviews/user/resolver.ts b/examples/apollo-federation-2/reviews/user/resolver.ts index cc9de8d4b..ae1858d3d 100644 --- a/examples/apollo-federation-2/reviews/user/resolver.ts +++ b/examples/apollo-federation-2/reviews/user/resolver.ts @@ -1,12 +1,11 @@ -import { Resolver, FieldResolver, Root } from "../../../../src"; - -import User from "./user"; -import Review from "../review/review"; +import { FieldResolver, Resolver, Root } from "type-graphql"; +import { User } from "./user"; import { reviews } from "../review/data"; +import { Review } from "../review/review"; -@Resolver(of => User) -export default class UserReviewsResolver { - @FieldResolver(returns => [Review]) +@Resolver(_of => User) +export class UserReviewsResolver { + @FieldResolver(_returns => [Review]) async reviews(@Root() user: User): Promise { return reviews.filter(review => review.author.id === user.id); } diff --git a/examples/apollo-federation-2/reviews/user/user.ts b/examples/apollo-federation-2/reviews/user/user.ts index cecb0358e..f159bf5f6 100644 --- a/examples/apollo-federation-2/reviews/user/user.ts +++ b/examples/apollo-federation-2/reviews/user/user.ts @@ -1,14 +1,14 @@ -import { Directive, ObjectType, Field, ID } from "../../../../src"; +import { Directive, Field, ID, ObjectType } from "type-graphql"; @Directive("@extends") @Directive(`@key(fields: "id")`) @ObjectType() -export default class User { +export class User { @Directive("@external") - @Field(type => ID) - id: string; + @Field(_type => ID) + id!: string; @Directive("@external") @Field() - username: string; + username!: string; } diff --git a/examples/apollo-federation/products/dining.ts b/examples/apollo-federation/products/dining.ts deleted file mode 100644 index 7638a4d6e..000000000 --- a/examples/apollo-federation/products/dining.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, Field, ObjectType } from "../../../src"; -import Product from "./product"; - -@Directive(`@key(fields: "upc")`) -@ObjectType({ implements: Product }) -export default class Dining extends Product { - @Field() - height: string; -} diff --git a/examples/apollo-federation/products/seating.ts b/examples/apollo-federation/products/seating.ts deleted file mode 100644 index a333c7118..000000000 --- a/examples/apollo-federation/products/seating.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Directive, Field, ObjectType } from "../../../src"; -import Product from "./product"; - -@Directive(`@key(fields: "upc")`) -@ObjectType({ implements: Product }) -export default class Seating extends Product { - @Field() - seats: number; -} From cd283f5b9e22ff46d8b935dee48a4f0f7f185cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Wed, 4 Oct 2023 14:25:50 +0200 Subject: [PATCH 7/8] Add link in examples page in website --- docs/examples.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/examples.md b/docs/examples.md index 4ea50125b..a501f1d02 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -41,7 +41,8 @@ Each subdirectory contains a `examples.graphql` file with predefined GraphQL que - [TypeORM (automatic, lazy relations) \*](https://github.com/MichalLytek/type-graphql/tree/master/examples/typeorm-lazy-relations) - [MikroORM \*](https://github.com/MichalLytek/type-graphql/tree/master/examples/mikro-orm) - [Typegoose \*](https://github.com/MichalLytek/type-graphql/tree/master/examples/typegoose) -- [Apollo federation](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation) +- [Apollo Federation](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation) +- [Apollo Federation 2](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation-2) - [Apollo Cache Control](https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-cache) - [GraphQL Scalars](https://github.com/MichalLytek/type-graphql/tree/master/examples/graphql-scalars) - [TSyringe](https://github.com/MichalLytek/type-graphql/tree/master/examples/tsyringe) From bb7d3527844d88a3e3938fb8ab424ff03a4dd9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Wed, 4 Oct 2023 14:41:20 +0200 Subject: [PATCH 8/8] Adjust examples to show federation v2 capabilities --- examples/apollo-federation-2/examples.graphql | 3 +++ examples/apollo-federation-2/reviews/user/user.ts | 2 -- examples/apollo-federation-2/schema.graphql | 2 +- examples/apollo-federation/examples.graphql | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/apollo-federation-2/examples.graphql b/examples/apollo-federation-2/examples.graphql index 45dca094f..c1c39f816 100644 --- a/examples/apollo-federation-2/examples.graphql +++ b/examples/apollo-federation-2/examples.graphql @@ -1,5 +1,6 @@ query { topProducts { + __typename name price shippingEstimate @@ -11,8 +12,10 @@ query { birthDate reviews { product { + __typename name } + body } } } diff --git a/examples/apollo-federation-2/reviews/user/user.ts b/examples/apollo-federation-2/reviews/user/user.ts index f159bf5f6..8a1eb5eef 100644 --- a/examples/apollo-federation-2/reviews/user/user.ts +++ b/examples/apollo-federation-2/reviews/user/user.ts @@ -1,10 +1,8 @@ import { Directive, Field, ID, ObjectType } from "type-graphql"; -@Directive("@extends") @Directive(`@key(fields: "id")`) @ObjectType() export class User { - @Directive("@external") @Field(_type => ID) id!: string; diff --git a/examples/apollo-federation-2/schema.graphql b/examples/apollo-federation-2/schema.graphql index 9da36f9d9..65ba384e2 100644 --- a/examples/apollo-federation-2/schema.graphql +++ b/examples/apollo-federation-2/schema.graphql @@ -54,4 +54,4 @@ type User { name: String! reviews: [Review!]! username: String! -} \ No newline at end of file +} diff --git a/examples/apollo-federation/examples.graphql b/examples/apollo-federation/examples.graphql index 45dca094f..38474e680 100644 --- a/examples/apollo-federation/examples.graphql +++ b/examples/apollo-federation/examples.graphql @@ -13,6 +13,7 @@ query { product { name } + body } } }