diff --git a/Cargo.lock b/Cargo.lock index b64bcd3c6e..912fbfe5b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ dependencies = [ "proc-macro2", "quote", "strum", - "syn 2.0.77", + "syn 2.0.79", "thiserror", ] @@ -407,7 +407,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -475,7 +475,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -492,7 +492,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -912,7 +912,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1192,7 +1192,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1214,7 +1214,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1266,7 +1266,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1310,7 +1310,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1330,7 +1330,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "unicode-xid", ] @@ -1343,7 +1343,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1690,7 +1690,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2863,7 +2863,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.4", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3011,7 +3011,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3022,7 +3022,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3616,7 +3616,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3693,7 +3693,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3821,7 +3821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3928,7 +3928,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -3942,7 +3942,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3955,7 +3955,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4604,7 +4604,7 @@ dependencies = [ "proc-macro2", "quote", "rquickjs-core", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4724,9 +4724,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -4800,7 +4800,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4918,7 +4918,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4929,7 +4929,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5249,7 +5249,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5271,9 +5271,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -5548,7 +5548,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5727,7 +5727,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5747,7 +5747,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5870,7 +5870,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6030,7 +6030,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6043,7 +6043,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6141,7 +6141,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6445,7 +6445,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -6479,7 +6479,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6513,7 +6513,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6714,7 +6714,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6725,7 +6725,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6747,7 +6747,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6758,7 +6758,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -7061,7 +7061,7 @@ dependencies = [ "async-trait", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -7089,7 +7089,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -7122,7 +7122,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index 9efc3d86a1..b6a224da46 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -35,6 +35,13 @@ directive @cache( Provides the ability to refer to multiple fields in the Query or Mutation root. """ directive @call( + """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean """ Steps are composed together to form a call. If you have multiple steps, the output of the previous step is passed as input to the next step. @@ -71,6 +78,13 @@ directive @graphQL( """ batch: Boolean! """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean + """ The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values. @@ -111,6 +125,13 @@ directive @grpc( """ body: JSON """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean + """ The `headers` parameter allows you to customize the headers of the HTTP request made by the `@grpc` operator. It is used by specifying a key-value map of header names and their values. Note: content-type is automatically set to application/grpc @@ -148,6 +169,13 @@ directive @http( """ body: String """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean + """ The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`. """ @@ -253,13 +281,6 @@ directive @server( introducing latency and complicating debugging. Use judiciously. @default `false`. """ batchRequests: Boolean - """ - Enables deduplication of IO operations to enhance performance.This flag prevents - duplicate IO requests from being executed concurrently, reducing resource load. Caution: - May lead to issues with APIs that expect unique results for identical inputs, such - as nonce-based APIs. - """ - dedupe: Boolean enableJIT: Boolean """ `globalResponseTimeout` sets the maximum query duration before termination, acting @@ -747,6 +768,13 @@ input GraphQL { """ batch: Boolean! """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean + """ The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values. @@ -787,6 +815,13 @@ input Grpc { """ body: JSON """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean + """ The `headers` parameter allows you to customize the headers of the HTTP request made by the `@grpc` operator. It is used by specifying a key-value map of header names and their values. Note: content-type is automatically set to application/grpc @@ -824,6 +859,13 @@ input Http { """ body: String """ + Enables deduplication of IO operations to enhance performance.This flag prevents + duplicate IO requests from being executed concurrently, reducing resource load. Caution: + May lead to issues with APIs that expect unique results for identical inputs, such + as nonce-based APIs. + """ + dedupe: Boolean + """ The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`. """ diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index 6f20d154bf..5043806da7 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -229,6 +229,13 @@ "steps" ], "properties": { + "dedupe": { + "description": "Enables deduplication of IO operations to enhance performance.\n\nThis flag prevents duplicate IO requests from being executed concurrently, reducing resource load. Caution: May lead to issues with APIs that expect unique results for identical inputs, such as nonce-based APIs.", + "type": [ + "boolean", + "null" + ] + }, "steps": { "description": "Steps are composed together to form a call. If you have multiple steps, the output of the previous step is passed as input to the next step.", "type": "array", @@ -541,6 +548,13 @@ "description": "If the upstream GraphQL server supports request batching, you can specify the 'batch' argument to batch several requests into a single batch request.\n\nMake sure you have also specified batch settings to the `@upstream` and to the `@graphQL` operator.", "type": "boolean" }, + "dedupe": { + "description": "Enables deduplication of IO operations to enhance performance.\n\nThis flag prevents duplicate IO requests from being executed concurrently, reducing resource load. Caution: May lead to issues with APIs that expect unique results for identical inputs, such as nonce-based APIs.", + "type": [ + "boolean", + "null" + ] + }, "headers": { "description": "The headers parameter allows you to customize the headers of the GraphQL request made by the `@graphQL` operator. It is used by specifying a key-value map of header names and their values.", "type": "array", @@ -579,6 +593,13 @@ "body": { "description": "This refers to the arguments of your gRPC call. You can pass it as a static object or use Mustache template for dynamic parameters. These parameters will be added in the body in `protobuf` format." }, + "dedupe": { + "description": "Enables deduplication of IO operations to enhance performance.\n\nThis flag prevents duplicate IO requests from being executed concurrently, reducing resource load. Caution: May lead to issues with APIs that expect unique results for identical inputs, such as nonce-based APIs.", + "type": [ + "boolean", + "null" + ] + }, "headers": { "description": "The `headers` parameter allows you to customize the headers of the HTTP request made by the `@grpc` operator. It is used by specifying a key-value map of header names and their values. Note: content-type is automatically set to application/grpc", "type": "array", @@ -669,6 +690,13 @@ "null" ] }, + "dedupe": { + "description": "Enables deduplication of IO operations to enhance performance.\n\nThis flag prevents duplicate IO requests from being executed concurrently, reducing resource load. Caution: May lead to issues with APIs that expect unique results for identical inputs, such as nonce-based APIs.", + "type": [ + "boolean", + "null" + ] + }, "encoding": { "description": "The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.", "allOf": [ @@ -1018,13 +1046,6 @@ "null" ] }, - "dedupe": { - "description": "Enables deduplication of IO operations to enhance performance.\n\nThis flag prevents duplicate IO requests from being executed concurrently, reducing resource load. Caution: May lead to issues with APIs that expect unique results for identical inputs, such as nonce-based APIs.", - "type": [ - "boolean", - "null" - ] - }, "enableJIT": { "type": [ "boolean", diff --git a/npm/gen-root.ts b/npm/gen-root.ts index 380e60353b..c5cfbd5ae2 100644 --- a/npm/gen-root.ts +++ b/npm/gen-root.ts @@ -1,9 +1,9 @@ import * as fs from "fs/promises" import {resolve, dirname} from "path" +import * as yml from "yaml" import {fileURLToPath} from "url" import {parse} from "ts-command-line-args" import {PackageJson as IPackageJSON} from "type-fest" -import YML from "yaml" const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -17,9 +17,9 @@ const options = parse({ name: {alias: "n", type: String}, }) -async function get_build_matrix() { +async function getBuildDefinitions(): Promise { const ciYMLPath = resolve(__dirname, "../.github/workflows/build_matrix.yml") - const ciYML = await fs.readFile(ciYMLPath, "utf8").then(YML.parse) + const ciYML = await fs.readFile(ciYMLPath, "utf8").then(yml.parse) const steps = ciYML.jobs["setup-matrix"].steps for (const step of steps) { @@ -27,19 +27,26 @@ async function get_build_matrix() { if (matrix) { // Parse yaml again since matrix is defined as string inside setup-matrix - return YML.parse(matrix) + return yml.parse(matrix).build } } throw new Error("Cannot find matrix definition in workflow file") } -async function genServerPackage() { +async function genServerPackage(buildDefinitions: string[]) { const packageVersion = options.version || "0.1.0" const name = options.name || "@tailcallhq/tailcall" console.log(`Generating package.json with version ${packageVersion}`) + // Construct the optionalDependencies object with the provided version + const optionalDependencies: Record = {} + + for (const buildDef of buildDefinitions) { + optionalDependencies[`@tailcallhq/core-${buildDef}`] = packageVersion + } + const packageJson = await fs.readFile(resolve(__dirname, "./package.json"), "utf8") const basePackage = JSON.parse(packageJson) as IPackageJSON const {description, license, repository, homepage, keywords} = basePackage @@ -53,6 +60,7 @@ async function genServerPackage() { name: name, type: "module", version: packageVersion, + optionalDependencies, scarfSettings: { defaultOptIn: true, allowTopLevel: true, @@ -60,42 +68,34 @@ async function genServerPackage() { dependencies: { "detect-libc": "^2.0.2", "@scarf/scarf": "^1.3.0", - yaml: "^2.3.3", - axios: "^1.7.4", }, scripts: { postinstall: "node ./scripts/post-install.js", preinstall: "node ./scripts/pre-install.js", }, - bin: { - tailcall: "bin/tailcall", // will replace with respective platform binary later. - }, } // Define the directory path where the package.json should be created const directoryPath = resolve(__dirname, "@tailcallhq/tailcall") const scriptsPath = resolve(directoryPath, "./scripts") - const binPath = resolve(directoryPath, "./bin") await fs.mkdir(scriptsPath, {recursive: true}) - await fs.mkdir(binPath, {recursive: true}) await fs.mkdir(directoryPath, {recursive: true}) const postInstallScript = await fs.readFile(resolve(__dirname, "./post-install.js"), "utf8") const preInstallScript = await fs.readFile(resolve(__dirname, "./pre-install.js"), "utf8") - const utilsScript = await fs.readFile(resolve(__dirname, "./utils.js"), "utf8") - const stringified_yaml = YML.stringify(await get_build_matrix()) const postInstallScriptContent = `const version = "${packageVersion}";\n${postInstallScript}` + const preInstallScriptContent = `const optionalDependencies = ${JSON.stringify( + optionalDependencies, + )};\n${preInstallScript}` await fs.writeFile(resolve(scriptsPath, "post-install.js"), postInstallScriptContent, "utf8") - await fs.writeFile(resolve(scriptsPath, "pre-install.js"), preInstallScript, "utf8") - await fs.writeFile(resolve(scriptsPath, "utils.js"), utilsScript, "utf8") - await fs.writeFile(resolve(directoryPath, "./build-matrix.yaml"), stringified_yaml, "utf8") - + await fs.writeFile(resolve(scriptsPath, "pre-install.js"), preInstallScriptContent, "utf8") await fs.writeFile(resolve(directoryPath, "./package.json"), JSON.stringify(tailcallPackage, null, 2), "utf8") await fs.copyFile(resolve(__dirname, "../README.md"), resolve(directoryPath, "./README.md")) } -await genServerPackage() +const buildDefinitions = await getBuildDefinitions() +await genServerPackage(buildDefinitions) diff --git a/npm/package-lock.json b/npm/package-lock.json index 0314b82f07..6dcf132c46 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -9,7 +9,8 @@ "dependencies": { "ts-command-line-args": "^2.5.1", "type-fest": "^4.7.1", - "yaml": "^2.3.3" + "yaml": "^2.3.3", + "yml": "^1.0.0" }, "devDependencies": { "tsx": "^4.1.0" @@ -437,6 +438,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/array-back": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", @@ -632,6 +641,18 @@ "node": ">=0.8.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/find-replace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", @@ -657,6 +678,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-tsconfig": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", @@ -678,11 +707,59 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "engines": { + "node": "*" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/node.extend": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.3.tgz", + "integrity": "sha512-xwADg/okH48PvBmRZyoX8i8GJaKuJ1CqlqotlZOhUio8egD1P5trJupHKBzcPjSF9ifK2gPcEICRBnkfPqQXZw==", + "dependencies": { + "hasown": "^2.0.0", + "is": "^3.3.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -700,6 +777,11 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "node_modules/string-format": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", @@ -831,6 +913,16 @@ "engines": { "node": ">= 14" } + }, + "node_modules/yml": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yml/-/yml-1.0.0.tgz", + "integrity": "sha512-x9KKVpZtKunwoi6J7fCtiAJkV+M4F5woMD8RQJ7wv3DkIBrIx70XOryhnSFbwXxpLsH/s1reCxSZ1CPHUr0PWg==", + "dependencies": { + "js-yaml": "^3.9.0", + "lodash": "^4.17.4", + "node.extend": "^2.0.0" + } } } } diff --git a/npm/package.json b/npm/package.json index 964ef3e926..82cebc7e53 100644 --- a/npm/package.json +++ b/npm/package.json @@ -29,7 +29,8 @@ "dependencies": { "ts-command-line-args": "^2.5.1", "type-fest": "^4.7.1", - "yaml": "^2.3.3" + "yaml": "^2.3.3", + "yml": "^1.0.0" }, "devDependencies": { "tsx": "^4.1.0" diff --git a/npm/post-install.js b/npm/post-install.js index d3f4d2edd4..365f6dfad2 100644 --- a/npm/post-install.js +++ b/npm/post-install.js @@ -2,69 +2,25 @@ import {familySync, GLIBC, MUSL} from "detect-libc" import {exec} from "child_process" import util from "util" -import get_matched_platform from "./utils.js" -import fs from "fs" -import axios from "axios" -import {resolve, dirname} from "path" -import {fileURLToPath} from "url" const execa = util.promisify(exec) -const os = process.platform +const platform = process.platform const arch = process.arch -const libcFamily = familySync() -let libc = "" -if (os === "win32") { - libc = "msvc" +const libcFamily = familySync() +let libc +if (platform === "win32") { + libc = "-msvc" } else { - libc = libcFamily === GLIBC ? "gnu" : libcFamily === MUSL ? "musl" : "" + libc = libcFamily === GLIBC ? "-gnu" : libcFamily === MUSL ? "-musl" : "" } -const matched_platform = get_matched_platform(os, arch, libc) -if (matched_platform != null) { - const targetPlatform = matched_platform - - let targetPlatformExt = "" - if (targetPlatform.get("ext") != undefined) { - targetPlatformExt = targetPlatform.get("ext") - } - - const pkg_download_base_url = "https://github.com/tailcallhq/tailcall/releases/download/" - const specific_url = `v${version}/tailcall-${targetPlatform.get("target")}${targetPlatformExt}` - const full_url = pkg_download_base_url + specific_url - - console.log(`Downloading Tailcall for ${targetPlatform.get("target")}${targetPlatformExt} ,\nUrl - ${full_url} ...`) - - const output_path = `bin/tailcall-${targetPlatform.get("target")}${targetPlatformExt}` - await download_binary(full_url, output_path) -} - -async function download_binary(full_url, output_path) { - try { - const file = fs.createWriteStream(output_path) - console.log("bin path -", output_path) - const response = await axios({ - url: full_url, - method: "GET", - responseType: "stream", - }) - - response.data.pipe(file) - response.data.on("error", (error) => { - console.error("Error with resp data - ", error) - }) +const pkg = `@tailcallhq/core-${platform}-${arch}${libc}` - file.on("close", async () => { - const packageJsonString = await fs.promises.readFile("package.json", "utf8") - const packageJson = JSON.parse(packageJsonString) - packageJson.bin = {tailcall: output_path} - await fs.promises.writeFile("package.json", JSON.stringify(packageJson, null, 2), "utf8") - console.log("Tailcall binary downloaded successfully") - }) - file.on("error", (error) => { - console.error("Error while writing to a file - ", error) - }) - } catch (error) { - console.error("Error downloading", error.message) - } +try { + // @ts-ignore + const {stdout, stderr} = await execa(`npm install ${pkg}@${version} --no-save`) + stderr ? console.log(stderr) : console.log(`Successfully installed optional dependency: ${pkg}`, stdout) +} catch (error) { + console.error(`Failed to install optional dependency: ${pkg}`, error.stderr) } diff --git a/npm/pre-install.js b/npm/pre-install.js index 005a8bab63..6d008b256b 100644 --- a/npm/pre-install.js +++ b/npm/pre-install.js @@ -1,23 +1,10 @@ -// @ts-check -import {familySync, GLIBC, MUSL} from "detect-libc" -import get_matched_platform from "./utils.js" - const os = process.platform const arch = process.arch -const libcFamily = familySync() - -let libc = "" -if (os === "win32") { - libc = "msvc" -} else { - libc = libcFamily === GLIBC ? "gnu" : libcFamily === MUSL ? "musl" : "" -} - -const matched_platform = get_matched_platform(os, arch, libc) -if (matched_platform == null) { +const dependency = Object.keys(optionalDependencies).find((name) => name.includes(`${os}-${arch}`)) +if (!dependency) { const redColor = "\x1b[31m" const resetColor = "\x1b[0m" - console.error(`${redColor} Tailcall does not support platform - ${os}, arch - ${arch}, libc - ${libc} ${resetColor}`) + console.error(`${redColor} Tailcall does not support platform ${os} arch ${arch} ${resetColor}`) process.exit(1) } diff --git a/npm/utils.js b/npm/utils.js deleted file mode 100644 index 20cf97c048..0000000000 --- a/npm/utils.js +++ /dev/null @@ -1,24 +0,0 @@ -import fs from "fs" -import {dirname, resolve} from "path" -import {fileURLToPath} from "url" -import YML from "yaml" - -const __dirname = dirname(fileURLToPath(import.meta.url)) - -export default function get_matched_platform(os, arch, libc) { - const directoryPath = resolve(__dirname, "../") - const file = fs.readFileSync(resolve(directoryPath, "./build-matrix.yaml"), "utf8") - const build_matrix = YML.parse(file, {mapAsMap: true}) - - let found = null - build_matrix.get("include").forEach((platform) => { - const split = platform.get("build").split("-") - const platform_arch = split.at(1) - const platform_os = split.at(0) - const platform_libc = split.at(-1) - if (platform_arch == arch && platform_os == os && platform_libc == libc) { - found = platform - } - }) - return found -} diff --git a/src/core/app_context.rs b/src/core/app_context.rs index fcc628a598..5331e3e1d1 100644 --- a/src/core/app_context.rs +++ b/src/core/app_context.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use async_graphql::dynamic::{self, DynamicRequest}; use async_graphql_value::ConstValue; -use hyper::body::Bytes; use crate::core::async_graphql_hyper::OperationId; use crate::core::auth::context::GlobalAuthContext; @@ -11,7 +10,7 @@ use crate::core::data_loader::{DataLoader, DedupeResult}; use crate::core::graphql::GraphqlDataLoader; use crate::core::grpc; use crate::core::grpc::data_loader::GrpcDataLoader; -use crate::core::http::{DataLoaderRequest, HttpDataLoader, Response}; +use crate::core::http::{DataLoaderRequest, HttpDataLoader}; use crate::core::ir::model::{DataLoaderId, IoId, IO, IR}; use crate::core::ir::Error; use crate::core::rest::{Checked, EndpointSet}; @@ -27,7 +26,7 @@ pub struct AppContext { pub endpoints: EndpointSet, pub auth_ctx: Arc, pub dedupe_handler: Arc>, - pub dedupe_operation_handler: DedupeResult, Error>, + pub dedupe_operation_handler: DedupeResult, Error>, } impl AppContext { @@ -48,9 +47,15 @@ impl AppContext { expr.modify(&mut |expr| match expr { IR::IO(io) => match io { IO::Http { - req_template, group_by, http_filter, is_list, .. + req_template, + group_by, + http_filter, + is_list, + dedupe, + .. } => { let is_list = *is_list; + let dedupe = *dedupe; let data_loader = HttpDataLoader::new( runtime.clone(), group_by.clone(), @@ -64,6 +69,7 @@ impl AppContext { dl_id: Some(DataLoaderId::new(http_data_loaders.len())), http_filter: http_filter.clone(), is_list, + dedupe, })); http_data_loaders.push(data_loader); @@ -71,7 +77,8 @@ impl AppContext { result } - IO::GraphQL { req_template, field_name, batch, .. } => { + IO::GraphQL { req_template, field_name, batch, dedupe, .. } => { + let dedupe = *dedupe; let graphql_data_loader = GraphqlDataLoader::new(runtime.clone(), *batch) .into_data_loader( @@ -83,6 +90,7 @@ impl AppContext { field_name: field_name.clone(), batch: *batch, dl_id: Some(DataLoaderId::new(gql_data_loaders.len())), + dedupe, })); gql_data_loaders.push(graphql_data_loader); @@ -90,7 +98,8 @@ impl AppContext { result } - IO::Grpc { req_template, group_by, .. } => { + IO::Grpc { req_template, group_by, dedupe, .. } => { + let dedupe = *dedupe; let data_loader = GrpcDataLoader { runtime: runtime.clone(), operation: req_template.operation.clone(), @@ -104,6 +113,7 @@ impl AppContext { req_template: req_template.clone(), group_by: group_by.clone(), dl_id: Some(DataLoaderId::new(grpc_data_loaders.len())), + dedupe, })); grpc_data_loaders.push(data_loader); diff --git a/src/core/auth/verification.rs b/src/core/auth/verification.rs index c1612c555a..024dca5e2c 100644 --- a/src/core/auth/verification.rs +++ b/src/core/auth/verification.rs @@ -3,7 +3,8 @@ use std::cmp::max; use super::error::Error; /// -/// Represents the result of the auth verification process. +/// Represents the result of the auth verification process. It can either +/// succeed or fail with an Error. #[derive(Clone, PartialEq, Debug)] pub enum Verification { Succeed, diff --git a/src/core/blueprint/operators/apollo_federation.rs b/src/core/blueprint/operators/apollo_federation.rs index a078e6fe8c..e780de7c5d 100644 --- a/src/core/blueprint/operators/apollo_federation.rs +++ b/src/core/blueprint/operators/apollo_federation.rs @@ -6,7 +6,7 @@ use async_graphql::parser::types::{SchemaDefinition, ServiceDocument, TypeSystem use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js}; use crate::core::blueprint::FieldDefinition; use crate::core::config::{ - ApolloFederation, Config, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, + ApolloFederation, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver, }; use crate::core::ir::model::IR; use crate::core::try_fold::TryFold; @@ -82,16 +82,8 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid Valid { let mut sdl = crate::core::document::print(filter_conflicting_directives(config.config().into())); - - writeln!(sdl).ok(); - // Add tailcall specific definitions to the sdl output - writeln!( - sdl, - "{}", - crate::core::document::print(filter_conflicting_directives(Config::graphql_schema())) - ) - .ok(); writeln!(sdl).ok(); + // Mark subgraph as Apollo federation v2 compatible according to [docs](https://www.apollographql.com/docs/apollo-server/using-federation/apollo-subgraph-setup/#2-opt-in-to-federation-2) // (borrowed from async_graphql) writeln!(sdl, "extend schema @link(").ok(); diff --git a/src/core/blueprint/operators/graphql.rs b/src/core/blueprint/operators/graphql.rs index 394b1ca524..39eca0f634 100644 --- a/src/core/blueprint/operators/graphql.rs +++ b/src/core/blueprint/operators/graphql.rs @@ -71,7 +71,8 @@ pub fn compile_graphql( .map(|req_template| { let field_name = graphql.name.clone(); let batch = graphql.batch; - IR::IO(IO::GraphQL { req_template, field_name, batch, dl_id: None }) + let dedupe = graphql.dedupe.unwrap_or_default(); + IR::IO(IO::GraphQL { req_template, field_name, batch, dl_id: None, dedupe }) }) } diff --git a/src/core/blueprint/operators/grpc.rs b/src/core/blueprint/operators/grpc.rs index abf21c9582..7b4f56c6af 100644 --- a/src/core/blueprint/operators/grpc.rs +++ b/src/core/blueprint/operators/grpc.rs @@ -160,6 +160,7 @@ pub fn compile_grpc(inputs: CompileGrpc) -> Valid { let field = inputs.field; let grpc = inputs.grpc; let validate_with_schema = inputs.validate_with_schema; + let dedupe = grpc.dedupe.unwrap_or_default(); Valid::from(GrpcMethod::try_from(grpc.method.as_str())) .and_then(|method| { @@ -201,9 +202,10 @@ pub fn compile_grpc(inputs: CompileGrpc) -> Valid { req_template, group_by: Some(GroupBy::new(grpc.batch_key.clone(), None)), dl_id: None, + dedupe, }) } else { - IR::IO(IO::Grpc { req_template, group_by: None, dl_id: None }) + IR::IO(IO::Grpc { req_template, group_by: None, dl_id: None, dedupe }) } }) } diff --git a/src/core/blueprint/operators/http.rs b/src/core/blueprint/operators/http.rs index 62ff58f2e1..a03133e553 100644 --- a/src/core/blueprint/operators/http.rs +++ b/src/core/blueprint/operators/http.rs @@ -13,6 +13,8 @@ pub fn compile_http( http: &config::Http, is_list: bool, ) -> Valid { + let dedupe = http.dedupe.unwrap_or_default(); + Valid::<(), String>::fail("GroupBy is only supported for GET requests".to_string()) .when(|| !http.batch_key.is_empty() && http.method != Method::GET) .and( @@ -81,6 +83,7 @@ pub fn compile_http( dl_id: None, http_filter, is_list, + dedupe, }) } else { IR::IO(IO::Http { @@ -89,6 +92,7 @@ pub fn compile_http( dl_id: None, http_filter, is_list, + dedupe, }) } }) diff --git a/src/core/blueprint/server.rs b/src/core/blueprint/server.rs index df56169784..6e944da5a1 100644 --- a/src/core/blueprint/server.rs +++ b/src/core/blueprint/server.rs @@ -37,7 +37,6 @@ pub struct Server { pub cors: Option, pub experimental_headers: HashSet, pub auth: Option, - pub dedupe: bool, pub routes: Routes, } @@ -154,7 +153,6 @@ impl TryFrom for Server { script, cors, auth, - dedupe: config_server.get_dedupe(), routes: config_server.get_routes(), } }, diff --git a/src/core/blueprint/snapshots/tailcall__core__blueprint__index__test__from_blueprint.snap b/src/core/blueprint/snapshots/tailcall__core__blueprint__index__test__from_blueprint.snap index 1f46ab6ccc..fdce076bb1 100644 --- a/src/core/blueprint/snapshots/tailcall__core__blueprint__index__test__from_blueprint.snap +++ b/src/core/blueprint/snapshots/tailcall__core__blueprint__index__test__from_blueprint.snap @@ -70,6 +70,7 @@ Index { dl_id: None, http_filter: None, is_list: false, + dedupe: false, }, ), ), @@ -138,6 +139,7 @@ Index { dl_id: None, http_filter: None, is_list: false, + dedupe: false, }, ), ), @@ -214,6 +216,7 @@ Index { dl_id: None, http_filter: None, is_list: false, + dedupe: false, }, ), ), @@ -294,6 +297,7 @@ Index { dl_id: None, http_filter: None, is_list: false, + dedupe: false, }, ), ), @@ -690,6 +694,7 @@ Index { dl_id: None, http_filter: None, is_list: true, + dedupe: false, }, ), ), @@ -752,6 +757,7 @@ Index { dl_id: None, http_filter: None, is_list: false, + dedupe: false, }, ), ), @@ -851,6 +857,7 @@ Index { dl_id: None, http_filter: None, is_list: true, + dedupe: false, }, ), ), @@ -925,6 +932,7 @@ Index { dl_id: None, http_filter: None, is_list: false, + dedupe: false, }, ), ), diff --git a/src/core/config/directives/call.rs b/src/core/config/directives/call.rs index 250f7bebf2..377b711833 100644 --- a/src/core/config/directives/call.rs +++ b/src/core/config/directives/call.rs @@ -44,4 +44,12 @@ pub struct Call { /// If you have multiple steps, the output of the previous step is passed as /// input to the next step. pub steps: Vec, + #[serde(default, skip_serializing_if = "is_default")] + /// Enables deduplication of IO operations to enhance performance. + /// + /// This flag prevents duplicate IO requests from being executed + /// concurrently, reducing resource load. Caution: May lead to issues + /// with APIs that expect unique results for identical inputs, such as + /// nonce-based APIs. + pub dedupe: Option, } diff --git a/src/core/config/directives/graphql.rs b/src/core/config/directives/graphql.rs index e9bef555d1..63ff2355aa 100644 --- a/src/core/config/directives/graphql.rs +++ b/src/core/config/directives/graphql.rs @@ -50,4 +50,12 @@ pub struct GraphQL { /// is received for this field, Tailcall requests data from the /// corresponding upstream field. pub name: String, + #[serde(default, skip_serializing_if = "is_default")] + /// Enables deduplication of IO operations to enhance performance. + /// + /// This flag prevents duplicate IO requests from being executed + /// concurrently, reducing resource load. Caution: May lead to issues + /// with APIs that expect unique results for identical inputs, such as + /// nonce-based APIs. + pub dedupe: Option, } diff --git a/src/core/config/directives/grpc.rs b/src/core/config/directives/grpc.rs index 9ee9a6a126..95155ceb3d 100644 --- a/src/core/config/directives/grpc.rs +++ b/src/core/config/directives/grpc.rs @@ -51,4 +51,12 @@ pub struct Grpc { /// This refers to the gRPC method you're going to call. For instance /// `GetAllNews`. pub method: String, + #[serde(default, skip_serializing_if = "is_default")] + /// Enables deduplication of IO operations to enhance performance. + /// + /// This flag prevents duplicate IO requests from being executed + /// concurrently, reducing resource load. Caution: May lead to issues + /// with APIs that expect unique results for identical inputs, such as + /// nonce-based APIs. + pub dedupe: Option, } diff --git a/src/core/config/directives/http.rs b/src/core/config/directives/http.rs index 35e4ff3ca4..74fa72b3e5 100644 --- a/src/core/config/directives/http.rs +++ b/src/core/config/directives/http.rs @@ -90,4 +90,12 @@ pub struct Http { /// first parameter referencing a field in the current value using mustache /// syntax is automatically selected as the batching parameter. pub query: Vec, + #[serde(default, skip_serializing_if = "is_default")] + /// Enables deduplication of IO operations to enhance performance. + /// + /// This flag prevents duplicate IO requests from being executed + /// concurrently, reducing resource load. Caution: May lead to issues + /// with APIs that expect unique results for identical inputs, such as + /// nonce-based APIs. + pub dedupe: Option, } diff --git a/src/core/config/server.rs b/src/core/config/server.rs index 12087e9cb7..90ee979302 100644 --- a/src/core/config/server.rs +++ b/src/core/config/server.rs @@ -48,15 +48,6 @@ pub struct Server { /// debugging. Use judiciously. @default `false`. pub batch_requests: Option, - #[serde(default, skip_serializing_if = "is_default")] - /// Enables deduplication of IO operations to enhance performance. - /// - /// This flag prevents duplicate IO requests from being executed - /// concurrently, reducing resource load. Caution: May lead to issues - /// with APIs that expect unique results for identical inputs, such as - /// nonce-based APIs. - pub dedupe: Option, - #[serde(default, skip_serializing_if = "is_default")] /// `headers` contains key-value pairs that are included as default headers /// in server responses, allowing for consistent header management across @@ -268,9 +259,6 @@ impl Server { self.pipeline_flush.unwrap_or(true) } - pub fn get_dedupe(&self) -> bool { - self.dedupe.unwrap_or(false) - } pub fn enable_jit(&self) -> bool { self.enable_jit.unwrap_or(true) } diff --git a/src/core/config/transformer/subgraph.rs b/src/core/config/transformer/subgraph.rs index 3ef4126ee2..a60db5fc4e 100644 --- a/src/core/config/transformer/subgraph.rs +++ b/src/core/config/transformer/subgraph.rs @@ -470,6 +470,7 @@ mod tests { .collect(), ..Default::default() }], + dedupe: None, }; let resolver = Resolver::Call(call); diff --git a/src/core/generator/from_proto.rs b/src/core/generator/from_proto.rs index ee52d01a78..b334fcbcca 100644 --- a/src/core/generator/from_proto.rs +++ b/src/core/generator/from_proto.rs @@ -372,6 +372,7 @@ impl Context { batch_key: vec![], headers: vec![], method: field_name.id(), + dedupe: None, })); let method_path = diff --git a/src/core/http/request_handler.rs b/src/core/http/request_handler.rs index 3208a16cb7..a0adf79054 100644 --- a/src/core/http/request_handler.rs +++ b/src/core/http/request_handler.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use anyhow::Result; use async_graphql::ServerError; use hyper::header::{self, HeaderValue, CONTENT_TYPE}; +use hyper::http::request::Parts; use hyper::http::Method; use hyper::{Body, HeaderMap, Request, Response, StatusCode}; use opentelemetry::trace::SpanKind; @@ -108,22 +109,9 @@ pub async fn graphql_request( let bytes = hyper::body::to_bytes(body).await?; let graphql_request = serde_json::from_slice::(&bytes); match graphql_request { - Ok(mut request) => { - if !(app_ctx.blueprint.server.dedupe && request.is_query()) { - Ok(execute_query(app_ctx, &req_ctx, request).await?) - } else { - let operation_id = request.operation_id(&req.headers); - let out = app_ctx - .dedupe_operation_handler - .dedupe(&operation_id, || { - Box::pin(async move { - let resp = execute_query(app_ctx, &req_ctx, request).await?; - Ok(crate::core::http::Response::from_hyper(resp).await?) - }) - }) - .await?; - Ok(hyper::Response::from(out)) - } + Ok(request) => { + let resp = execute_query(app_ctx, &req_ctx, request, req).await?; + Ok(resp) } Err(err) => { tracing::error!( @@ -144,11 +132,19 @@ pub async fn graphql_request( async fn execute_query( app_ctx: &Arc, req_ctx: &Arc, - request: T, + mut request: T, + req: Parts, ) -> anyhow::Result> { let mut response = if app_ctx.blueprint.server.enable_jit { + let is_query = request.is_query(); + let operation_id = request.operation_id(&req.headers); request - .execute(&JITExecutor::new(app_ctx.clone(), req_ctx.clone())) + .execute(&JITExecutor::new( + app_ctx.clone(), + req_ctx.clone(), + is_query, + operation_id, + )) .await } else { request.data(req_ctx.clone()).execute(&app_ctx.schema).await diff --git a/src/core/ir/eval_io.rs b/src/core/ir/eval_io.rs index ce71df7550..3a29d1daff 100644 --- a/src/core/ir/eval_io.rs +++ b/src/core/ir/eval_io.rs @@ -20,7 +20,9 @@ where { // Note: Handled the case separately for performance reasons. It avoids cache // key generation when it's not required - if !ctx.request_ctx.server.dedupe || !ctx.is_query() { + let dedupe = io.dedupe(); + + if !dedupe || !ctx.is_query() { return eval_io_inner(io, ctx).await; } if let Some(key) = io.cache_key(ctx) { diff --git a/src/core/ir/model.rs b/src/core/ir/model.rs index 3473d47d96..ecab192987 100644 --- a/src/core/ir/model.rs +++ b/src/core/ir/model.rs @@ -47,23 +47,37 @@ pub enum IO { dl_id: Option, http_filter: Option, is_list: bool, + dedupe: bool, }, GraphQL { req_template: graphql::RequestTemplate, field_name: String, batch: bool, dl_id: Option, + dedupe: bool, }, Grpc { req_template: grpc::RequestTemplate, group_by: Option, dl_id: Option, + dedupe: bool, }, Js { name: String, }, } +impl IO { + pub fn dedupe(&self) -> bool { + match self { + IO::Http { dedupe, .. } => *dedupe, + IO::GraphQL { dedupe, .. } => *dedupe, + IO::Grpc { dedupe, .. } => *dedupe, + IO::Js { .. } => false, + } + } +} + #[derive(Clone, Copy, Debug)] pub struct DataLoaderId(usize); diff --git a/src/core/jit/exec_const.rs b/src/core/jit/exec_const.rs index fac7b626a6..213825a05a 100644 --- a/src/core/jit/exec_const.rs +++ b/src/core/jit/exec_const.rs @@ -19,7 +19,7 @@ pub struct ConstValueExecutor { } impl ConstValueExecutor { - pub fn new(request: &Request, app_ctx: Arc) -> Result { + pub fn new(request: &Request, app_ctx: &Arc) -> Result { Ok(Self { plan: request.create_plan(&app_ctx.blueprint)? }) } diff --git a/src/core/jit/graphql_executor.rs b/src/core/jit/graphql_executor.rs index 06ab3eaf98..f4bf32dffa 100644 --- a/src/core/jit/graphql_executor.rs +++ b/src/core/jit/graphql_executor.rs @@ -2,11 +2,12 @@ use std::collections::BTreeMap; use std::future::Future; use std::sync::Arc; -use async_graphql::{Data, Executor, Response, Value}; -use async_graphql_value::Extensions; +use async_graphql::{Data, Executor, Response, ServerError, Value}; +use async_graphql_value::{ConstValue, Extensions}; use futures_util::stream::BoxStream; use crate::core::app_context::AppContext; +use crate::core::async_graphql_hyper::OperationId; use crate::core::http::RequestContext; use crate::core::jit; use crate::core::jit::ConstValueExecutor; @@ -16,11 +17,39 @@ use crate::core::merge_right::MergeRight; pub struct JITExecutor { app_ctx: Arc, req_ctx: Arc, + is_query: bool, + operation_id: OperationId, } impl JITExecutor { - pub fn new(app_ctx: Arc, req_ctx: Arc) -> Self { - Self { app_ctx, req_ctx } + pub fn new( + app_ctx: Arc, + req_ctx: Arc, + is_query: bool, + operation_id: OperationId, + ) -> Self { + Self { app_ctx, req_ctx, is_query, operation_id } + } + async fn exec( + &self, + exec: ConstValueExecutor, + jit_request: jit::Request, + ) -> Response { + let is_introspection_query = self.app_ctx.blueprint.server.get_enable_introspection() + && exec.plan.is_introspection_query; + + let jit_resp = exec + .execute(&self.req_ctx, &jit_request) + .await + .into_async_graphql(); + + if is_introspection_query { + let async_req = async_graphql::Request::from(jit_request).only_introspection(); + let async_resp = self.app_ctx.execute(async_req).await; + jit_resp.merge_right(async_resp) + } else { + jit_resp + } } } @@ -45,25 +74,29 @@ impl Executor for JITExecutor { fn execute(&self, request: async_graphql::Request) -> impl Future + Send { let jit_request = jit::Request::from(request); - async { - match ConstValueExecutor::new(&jit_request, self.app_ctx.clone()) { + async move { + match ConstValueExecutor::new(&jit_request, &self.app_ctx) { Ok(exec) => { - let is_introspection_query = - self.app_ctx.blueprint.server.get_enable_introspection() - && exec.plan.is_introspection_query; - - let jit_resp = exec - .execute(&self.req_ctx, &jit_request) - .await - .into_async_graphql(); - - if is_introspection_query { - let async_req = - async_graphql::Request::from(jit_request).only_introspection(); - let async_resp = self.app_ctx.execute(async_req).await; - jit_resp.merge_right(async_resp) + if self.is_query && exec.plan.dedupe { + let out = self + .app_ctx + .dedupe_operation_handler + .dedupe(&self.operation_id, || { + Box::pin(async move { + let resp = self.exec(exec, jit_request).await; + Ok(Arc::new(resp)) + }) + }) + .await; + let val = out.unwrap_or_default(); + Arc::into_inner(val).unwrap_or_else(|| { + Response::from_errors(vec![ServerError::new( + "Deduplication failed", + None, + )]) + }) } else { - jit_resp + self.exec(exec, jit_request).await } } Err(error) => Response::from_errors(vec![error.into()]), diff --git a/src/core/jit/model.rs b/src/core/jit/model.rs index dbec574db6..de644a5f9a 100644 --- a/src/core/jit/model.rs +++ b/src/core/jit/model.rs @@ -352,6 +352,7 @@ pub struct OperationPlan { // TODO: drop index from here. Embed all the necessary information in each field of the plan. pub index: Arc, pub is_introspection_query: bool, + pub dedupe: bool, } impl std::fmt::Debug for OperationPlan { @@ -386,6 +387,7 @@ impl OperationPlan { nested, index: self.index, is_introspection_query: self.is_introspection_query, + dedupe: self.dedupe, }) } } @@ -408,6 +410,14 @@ impl OperationPlan { .filter(|f| f.extensions.is_none()) .map(|f| f.into_nested(&fields)) .collect::>(); + let dedupe = fields.iter().filter(|v| v.ir.is_none()).all(|v| { + v.ir.as_ref() + .map(|v| match v { + IR::IO(io) => io.dedupe(), + _ => false, + }) + .unwrap_or_default() + }); Self { root_name: root_name.to_string(), @@ -416,6 +426,7 @@ impl OperationPlan { operation_type, index, is_introspection_query, + dedupe, } } diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 90babc2fb4..d13eb68912 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -1967,9 +1967,9 @@ } }, "node_modules/wrangler": { - "version": "3.78.10", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.78.10.tgz", - "integrity": "sha512-Q8Ia0xz0RCzj5X7TMIEQ/EbADSG2cWPmTDRaulGSWnYqfIlFyKoxl7Zx1XXCo1EkDcKfSpX6TZa22pCDmtl4LA==", + "version": "3.78.12", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.78.12.tgz", + "integrity": "sha512-a/xk/N04IvOGk9J+BLkiFg42GDyPS+0BiJimbrHsbX+CDr8Iqq3HNMEyQld+6zbmq01u/gmc8S7GKVR9vDx4+g==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", @@ -3678,9 +3678,9 @@ } }, "wrangler": { - "version": "3.78.10", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.78.10.tgz", - "integrity": "sha512-Q8Ia0xz0RCzj5X7TMIEQ/EbADSG2cWPmTDRaulGSWnYqfIlFyKoxl7Zx1XXCo1EkDcKfSpX6TZa22pCDmtl4LA==", + "version": "3.78.12", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.78.12.tgz", + "integrity": "sha512-a/xk/N04IvOGk9J+BLkiFg42GDyPS+0BiJimbrHsbX+CDr8Iqq3HNMEyQld+6zbmq01u/gmc8S7GKVR9vDx4+g==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.3.4", diff --git a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap index 43b1b97459..bee132e1ed 100644 --- a/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities-batch.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.id}}\"}]) @key(fields: \"id\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @graphQL(args: [{key: \"id\", value: \"{{.value.id}}\"}], baseURL: \"http://upstream/graphql\", batch: true, name: \"post\") @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @http(batchKey: [\"id\"], path: \"/users\", query: [{key: \"id\", value: \"{{.value.id}}\"}]) @key(fields: \"id\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } diff --git a/tests/core/snapshots/apollo-federation-entities.md_1.snap b/tests/core/snapshots/apollo-federation-entities.md_1.snap index c577ad095d..abf35d2907 100644 --- a/tests/core/snapshots/apollo-federation-entities.md_1.snap +++ b/tests/core/snapshots/apollo-federation-entities.md_1.snap @@ -10,7 +10,7 @@ expression: response "body": { "data": { "_service": { - "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.id}}\"}}]) @key(fields: \"id\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\n\"\"\"\nThe @addField operator simplifies data structures and queries by adding a field that \ninlines or flattens a nested field or node within your schema. more info [here](https://tailcall.run/docs/guides/operators/#addfield)\n\"\"\"\ndirective @addField(\n \"\"\"\n Name of the new field to be added\n \"\"\"\n name: String!\n \"\"\"\n Path of the data where the field should point to\n \"\"\"\n path: [String!]\n) repeatable on OBJECT\n\n\"\"\"\nThe @alias directive indicates that aliases of one enum value.\n\"\"\"\ndirective @alias(\n options: [String!]\n) on ENUM_VALUE\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ndirective @cache(\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n) on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nProvides the ability to refer to multiple fields in the Query or Mutation root.\n\"\"\"\ndirective @call(\n \"\"\"\n Steps are composed together to form a call. If you have multiple steps, the output \n of the previous step is passed as input to the next step.\n \"\"\"\n steps: [Step]\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ndirective @expr(\n body: JSON\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ndirective @graphQL(\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ndirective @grpc(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n) on FIELD_DEFINITION | OBJECT\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ndirective @http(\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n) on FIELD_DEFINITION | OBJECT\n\ndirective @js(\n name: String!\n) on FIELD_DEFINITION | OBJECT\n\ndirective @modify(\n name: String\n omit: Boolean\n) on FIELD_DEFINITION\n\n\"\"\"\nUsed to omit a field from public consumption.\n\"\"\"\ndirective @omit on FIELD_DEFINITION\n\ndirective @protected on OBJECT | FIELD_DEFINITION\n\n\"\"\"\nThe `@server` directive, when applied at the schema level, offers a comprehensive \nset of server configurations. It dictates how the server behaves and helps tune tailcall \nfor various use-cases.\n\"\"\"\ndirective @server(\n \"\"\"\n `apolloTracing` exposes GraphQL query performance data, including execution time \n of queries and individual resolvers.\n \"\"\"\n apolloTracing: Boolean\n \"\"\"\n `batchRequests` combines multiple requests into one, improving performance but potentially \n introducing latency and complicating debugging. Use judiciously. @default `false`.\n \"\"\"\n batchRequests: Boolean\n \"\"\"\n Enables deduplication of IO operations to enhance performance.This flag prevents \n duplicate IO requests from being executed concurrently, reducing resource load. Caution: \n May lead to issues with APIs that expect unique results for identical inputs, such \n as nonce-based APIs.\n \"\"\"\n dedupe: Boolean\n enableJIT: Boolean\n \"\"\"\n `globalResponseTimeout` sets the maximum query duration before termination, acting \n as a safeguard against long-running queries.\n \"\"\"\n globalResponseTimeout: Int\n \"\"\"\n `headers` contains key-value pairs that are included as default headers in server \n responses, allowing for consistent header management across all responses.\n \"\"\"\n headers: Headers\n \"\"\"\n `hostname` sets the server hostname.\n \"\"\"\n hostname: String\n \"\"\"\n `introspection` allows clients to fetch schema information directly, aiding tools \n and applications in understanding available types, fields, and operations. @default \n `true`.\n \"\"\"\n introspection: Boolean\n \"\"\"\n `pipelineFlush` allows to control flushing behavior of the server pipeline.\n \"\"\"\n pipelineFlush: Boolean\n \"\"\"\n `port` sets the Tailcall running port. @default `8000`.\n \"\"\"\n port: Int\n \"\"\"\n `queryValidation` checks incoming GraphQL queries against the schema, preventing \n errors from invalid queries. Can be disabled for performance. @default `false`.\n \"\"\"\n queryValidation: Boolean\n \"\"\"\n `responseValidation` Tailcall automatically validates responses from upstream services \n using inferred schema. @default `false`.\n \"\"\"\n responseValidation: Boolean\n \"\"\"\n `routes` allows customization of server endpoint paths. It provides options to change \n the default paths for status and GraphQL endpoints. Default values are: - status: \n \"/status\" - graphQL: \"/graphql\" If not specified, these default values will be used.\n \"\"\"\n routes: Routes\n \"\"\"\n A link to an external JS file that listens on every HTTP request response event.\n \"\"\"\n script: ScriptOptions\n \"\"\"\n `showcase` enables the /showcase/graphql endpoint.\n \"\"\"\n showcase: Boolean\n \"\"\"\n This configuration defines local variables for server operations. Useful for storing \n constant configurations, secrets, or shared information.\n \"\"\"\n vars: [KeyValue]\n \"\"\"\n `version` sets the HTTP version for the server. Options are `HTTP1` and `HTTP2`. \n @default `HTTP1`.\n \"\"\"\n version: HttpVersion\n \"\"\"\n `workers` sets the number of worker threads. @default the number of system cores.\n \"\"\"\n workers: Int\n) on SCHEMA\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ndirective @telemetry(\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n) on SCHEMA\n\n\"\"\"\nThe `upstream` directive allows you to control various aspects of the upstream server \nconnection. This includes settings like connection timeouts, keep-alive intervals, \nand more. If not specified, default values are used.\n\"\"\"\ndirective @upstream(\n \"\"\"\n `allowedHeaders` defines the HTTP headers allowed to be forwarded to upstream services. \n If not set, no headers are forwarded, enhancing security but possibly limiting data \n flow.\n \"\"\"\n allowedHeaders: [String!]\n \"\"\"\n This refers to the default base URL for your APIs. If it's not explicitly mentioned \n in the `@upstream` operator, then each [@http](#http) operator must specify its own \n `baseURL`. If neither `@upstream` nor [@http](#http) provides a `baseURL`, it results \n in a compilation error.\n \"\"\"\n baseURL: String\n \"\"\"\n An object that specifies the batch settings, including `maxSize` (the maximum size \n of the batch), `delay` (the delay in milliseconds between each batch), and `headers` \n (an array of HTTP headers to be included in the batch).\n \"\"\"\n batch: Batch\n \"\"\"\n The time in seconds that the connection will wait for a response before timing out.\n \"\"\"\n connectTimeout: Int\n \"\"\"\n The `http2Only` setting allows you to specify whether the client should always issue \n HTTP2 requests, without checking if the server supports it or not. By default it \n is set to `false` for all HTTP requests made by the server, but is automatically \n set to true for GRPC.\n \"\"\"\n http2Only: Boolean\n \"\"\"\n Providing httpCache size enables Tailcall's HTTP caching, adhering to the [HTTP Caching \n RFC](https://tools.ietf.org/html/rfc7234), to enhance performance by minimizing redundant \n data fetches. Defaults to `0` if unspecified.\n \"\"\"\n httpCache: Int\n \"\"\"\n The time in seconds between each keep-alive message sent to maintain the connection.\n \"\"\"\n keepAliveInterval: Int\n \"\"\"\n The time in seconds that the connection will wait for a keep-alive message before \n closing.\n \"\"\"\n keepAliveTimeout: Int\n \"\"\"\n A boolean value that determines whether keep-alive messages should be sent while \n the connection is idle.\n \"\"\"\n keepAliveWhileIdle: Boolean\n \"\"\"\n onRequest field gives the ability to specify the global request interception handler.\n \"\"\"\n onRequest: String\n \"\"\"\n The time in seconds that the connection pool will wait before closing idle connections.\n \"\"\"\n poolIdleTimeout: Int\n \"\"\"\n The maximum number of idle connections that will be maintained per host.\n \"\"\"\n poolMaxIdlePerHost: Int\n \"\"\"\n The `proxy` setting defines an intermediary server through which the upstream requests \n will be routed before reaching their intended endpoint. By specifying a proxy URL, \n you introduce an additional layer, enabling custom routing and security policies.\n \"\"\"\n proxy: Proxy\n \"\"\"\n The time in seconds between each TCP keep-alive message sent to maintain the connection.\n \"\"\"\n tcpKeepAlive: Int\n \"\"\"\n The maximum time in seconds that the connection will wait for a response.\n \"\"\"\n timeout: Int\n \"\"\"\n The User-Agent header value to be used in HTTP requests. @default `Tailcall/1.0`\n \"\"\"\n userAgent: String\n) on SCHEMA\n\n\"\"\"\nField whose value is a sequence of bytes.\n\"\"\"\nscalar Bytes\n\n\"\"\"\nField whose value conforms to the standard date format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339).\n\"\"\"\nscalar Date\n\n\"\"\"\nField whose value conforms to the standard internet email address format as specified \nin HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address.\n\"\"\"\nscalar Email\n\n\"\"\"\nEmpty scalar type represents an empty value.\n\"\"\"\nscalar Empty\n\n\"\"\"\nField whose value is a 128-bit signed integer.\n\"\"\"\nscalar Int128\n\n\"\"\"\nField whose value is a 16-bit signed integer.\n\"\"\"\nscalar Int16\n\n\"\"\"\nField whose value is a 32-bit signed integer.\n\"\"\"\nscalar Int32\n\n\"\"\"\nField whose value is a 64-bit signed integer.\n\"\"\"\nscalar Int64\n\n\"\"\"\nField whose value is an 8-bit signed integer.\n\"\"\"\nscalar Int8\n\n\"\"\"\nField whose value conforms to the standard JSON format as specified in RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259).\n\"\"\"\nscalar JSON\n\n\"\"\"\nField whose value conforms to the standard E.164 format as specified in E.164 specification \n(https://en.wikipedia.org/wiki/E.164).\n\"\"\"\nscalar PhoneNumber\n\n\"\"\"\nField whose value is a 128-bit unsigned integer.\n\"\"\"\nscalar UInt128\n\n\"\"\"\nField whose value is a 16-bit unsigned integer.\n\"\"\"\nscalar UInt16\n\n\"\"\"\nField whose value is a 32-bit unsigned integer.\n\"\"\"\nscalar UInt32\n\n\"\"\"\nField whose value is a 64-bit unsigned integer.\n\"\"\"\nscalar UInt64\n\n\"\"\"\nField whose value is an 8-bit unsigned integer.\n\"\"\"\nscalar UInt8\n\n\"\"\"\nField whose value conforms to the standard URL format as specified in RFC 3986 (https://datatracker.ietf.org/doc/html/rfc3986).\n\"\"\"\nscalar Url\n\n\"\"\"\nProvides the ability to refer to a field defined in the root Query or Mutation.\n\"\"\"\ninput Step {\n \"\"\"\n The arguments that will override the actual arguments of the field.\n \"\"\"\n args: JSON\n \"\"\"\n The name of the field on the `Mutation` type that you want to call.\n \"\"\"\n mutation: String\n \"\"\"\n The name of the field on the `Query` type that you want to call.\n \"\"\"\n query: String\n}\n\ninput KeyValue {\n key: String!\n value: String!\n}\n\n\"\"\"\nThe URLQuery input type represents a query parameter to be included in a URL.\n\"\"\"\ninput URLQuery {\n \"\"\"\n The key or name of the query parameter.\n \"\"\"\n key: String!\n \"\"\"\n Determines whether to ignore query parameters with empty values.\n \"\"\"\n skipEmpty: Boolean\n \"\"\"\n The actual value or a mustache template to resolve the value dynamically for the \n query parameter.\n \"\"\"\n value: String!\n}\n\ninput Schema {\n Obj: JSON\n Arr: Schema\n Opt: Schema\n Enum: [String!]\n}\n\n\"\"\"\nType to configure Cross-Origin Resource Sharing (CORS) for a server.\n\"\"\"\ninput Cors {\n \"\"\"\n Indicates whether the server allows credentials (e.g., cookies, authorization headers) \n to be sent in cross-origin requests.\n \"\"\"\n allowCredentials: Boolean\n \"\"\"\n A list of allowed headers in cross-origin requests. This can be used to specify custom \n headers that are allowed to be included in cross-origin requests.\n \"\"\"\n allowHeaders: [String!]\n \"\"\"\n A list of allowed HTTP methods in cross-origin requests. These methods specify the \n actions that are permitted in cross-origin requests.\n \"\"\"\n allowMethods: [Method]\n \"\"\"\n A list of origins that are allowed to access the server's resources in cross-origin \n requests. An origin can be a domain, a subdomain, or even 'null' for local file schemes.\n \"\"\"\n allowOrigins: [String!]\n \"\"\"\n Indicates whether requests from private network addresses are allowed in cross-origin \n requests. Private network addresses typically include IP addresses reserved for internal \n networks.\n \"\"\"\n allowPrivateNetwork: Boolean\n \"\"\"\n A list of headers that the server exposes to the browser in cross-origin responses. \n Exposing certain headers allows the client-side code to access them in the response.\n \"\"\"\n exposeHeaders: [String!]\n \"\"\"\n The maximum time (in seconds) that the client should cache preflight OPTIONS requests \n in order to avoid sending excessive requests to the server.\n \"\"\"\n maxAge: Int\n \"\"\"\n A list of header names that indicate the values of which might cause the server's \n response to vary, potentially affecting caching.\n \"\"\"\n vary: [String!]\n}\n\ninput Headers {\n \"\"\"\n `cacheControl` sends `Cache-Control` headers in responses when activated. The `max-age` \n value is the least of the values received from upstream services. @default `false`.\n \"\"\"\n cacheControl: Boolean\n \"\"\"\n `cors` allows Cross-Origin Resource Sharing (CORS) for a server.\n \"\"\"\n cors: Cors\n \"\"\"\n `headers` are key-value pairs included in every server response. Useful for setting \n headers like `Access-Control-Allow-Origin` for cross-origin requests or additional \n headers for downstream services.\n \"\"\"\n custom: [KeyValue]\n \"\"\"\n `experimental` allows the use of `X-*` experimental headers in the response. @default \n `[]`.\n \"\"\"\n experimental: [String!]\n \"\"\"\n `setCookies` when enabled stores `set-cookie` headers and all the response will be \n sent with the headers.\n \"\"\"\n setCookies: Boolean\n}\n\ninput Routes {\n graphQL: String!\n status: String!\n}\n\ninput ScriptOptions {\n timeout: Int\n}\n\ninput Apollo {\n \"\"\"\n Setting `apiKey` for Apollo.\n \"\"\"\n apiKey: String!\n \"\"\"\n Setting `graphRef` for Apollo in the format @.\n \"\"\"\n graphRef: String!\n \"\"\"\n Setting `platform` for Apollo.\n \"\"\"\n platform: String\n \"\"\"\n Setting `userVersion` for Apollo.\n \"\"\"\n userVersion: String\n \"\"\"\n Setting `version` for Apollo.\n \"\"\"\n version: String\n}\n\n\"\"\"\nOutput the opentelemetry data to otlp collector\n\"\"\"\ninput OtlpExporter {\n headers: [KeyValue]\n url: String!\n}\n\n\"\"\"\nOutput the telemetry metrics data to prometheus server\n\"\"\"\ninput PrometheusExporter {\n format: PrometheusFormat\n path: String!\n}\n\n\"\"\"\nOutput the opentelemetry data to the stdout. Mostly used for debug purposes\n\"\"\"\ninput StdoutExporter {\n \"\"\"\n Output to stdout in pretty human-readable format\n \"\"\"\n pretty: Boolean!\n}\n\ninput TelemetryExporter {\n stdout: StdoutExporter\n otlp: OtlpExporter\n prometheus: PrometheusExporter\n apollo: Apollo\n}\n\ninput Batch {\n delay: Int!\n headers: [String!]\n maxSize: Int\n}\n\ninput Proxy {\n url: String!\n}\n\n\"\"\"\nThe @graphQL operator allows to specify GraphQL API server request to fetch data \nfrom.\n\"\"\"\ninput GraphQL {\n \"\"\"\n Named arguments for the requested field. More info [here](https://tailcall.run/docs/guides/operators/#args)\n \"\"\"\n args: [KeyValue]\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n If the upstream GraphQL server supports request batching, you can specify the 'batch' \n argument to batch several requests into a single batch request.Make sure you have \n also specified batch settings to the `@upstream` and to the `@graphQL` operator.\n \"\"\"\n batch: Boolean!\n \"\"\"\n The headers parameter allows you to customize the headers of the GraphQL request \n made by the `@graphQL` operator. It is used by specifying a key-value map of header \n names and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Specifies the root field on the upstream to request data from. This maps a field \n in your schema to a field in the upstream schema. When a query is received for this \n field, Tailcall requests data from the corresponding upstream field.\n \"\"\"\n name: String!\n}\n\n\"\"\"\nThe @grpc operator indicates that a field or node is backed by a gRPC API.For instance, \nif you add the @grpc operator to the `users` field of the Query type with a service \nargument of `NewsService` and method argument of `GetAllNews`, it signifies that \nthe `users` field is backed by a gRPC API. The `service` argument specifies the name \nof the gRPC service. The `method` argument specifies the name of the gRPC method. \nIn this scenario, the GraphQL server will make a gRPC request to the gRPC endpoint \nspecified when the `users` field is queried.\n\"\"\"\ninput Grpc {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n This refers to the arguments of your gRPC call. You can pass it as a static object \n or use Mustache template for dynamic parameters. These parameters will be added in \n the body in `protobuf` format.\n \"\"\"\n body: JSON\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@grpc` operator. It is used by specifying a key-value map of header names \n and their values. Note: content-type is automatically set to application/grpc\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n This refers to the gRPC method you're going to call. For instance `GetAllNews`.\n \"\"\"\n method: String!\n}\n\n\"\"\"\nThe @http operator indicates that a field or node is backed by a REST API.For instance, \nif you add the @http operator to the `users` field of the Query type with a path \nargument of `\"/users\"`, it signifies that the `users` field is backed by a REST API. \nThe path argument specifies the path of the REST API. In this scenario, the GraphQL \nserver will make a GET request to the API endpoint specified when the `users` field \nis queried.\n\"\"\"\ninput Http {\n \"\"\"\n This refers to the base URL of the API. If not specified, the default base URL is \n the one specified in the `@upstream` operator.\n \"\"\"\n baseURL: String\n \"\"\"\n The `batchKey` dictates the path Tailcall will follow to group the returned items \n from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).\n \"\"\"\n batchKey: [String!]\n \"\"\"\n The body of the API call. It's used for methods like POST or PUT that send data to \n the server. You can pass it as a static object or use a Mustache template to substitute \n variables from the GraphQL variables.\n \"\"\"\n body: String\n \"\"\"\n The `encoding` parameter specifies the encoding of the request body. It can be `ApplicationJson` \n or `ApplicationXWwwFormUrlEncoded`. @default `ApplicationJson`.\n \"\"\"\n encoding: Encoding\n \"\"\"\n The `headers` parameter allows you to customize the headers of the HTTP request made \n by the `@http` operator. It is used by specifying a key-value map of header names \n and their values.\n \"\"\"\n headers: [KeyValue]\n \"\"\"\n Schema of the input of the API call. It is automatically inferred in most cases.\n \"\"\"\n input: Schema\n \"\"\"\n This refers to the HTTP method of the API call. Commonly used methods include `GET`, \n `POST`, `PUT`, `DELETE` etc. @default `GET`.\n \"\"\"\n method: Method\n \"\"\"\n onRequest field in @http directive gives the ability to specify the request interception \n handler.\n \"\"\"\n onRequest: String\n \"\"\"\n Schema of the output of the API call. It is automatically inferred in most cases.\n \"\"\"\n output: Schema\n \"\"\"\n This refers to the API endpoint you're going to call. For instance `https://jsonplaceholder.typicode.com/users`.For \n dynamic segments in your API endpoint, use Mustache templates for variable substitution. \n For instance, to fetch a specific user, use `/users/{{args.id}}`.\n \"\"\"\n path: String!\n \"\"\"\n This represents the query parameters of your API call. You can pass it as a static \n object or use Mustache template for dynamic parameters. These parameters will be \n added to the URL. NOTE: Query parameter order is critical for batching in Tailcall. \n The first parameter referencing a field in the current value using mustache syntax \n is automatically selected as the batching parameter.\n \"\"\"\n query: [URLQuery]\n}\n\n\"\"\"\nThe `@expr` operators allows you to specify an expression that can evaluate to a \nvalue. The expression can be a static value or built form a Mustache template. schema.\n\"\"\"\ninput Expr {\n body: JSON\n}\n\ninput JS {\n name: String!\n}\n\ninput Modify {\n name: String\n omit: Boolean\n}\n\n\"\"\"\nThe @cache operator enables caching for the query, field or type it is applied to.\n\"\"\"\ninput Cache {\n \"\"\"\n Specifies the duration, in milliseconds, of how long the value has to be stored in \n the cache.\n \"\"\"\n maxAge: Int!\n}\n\n\"\"\"\nThe @telemetry directive facilitates seamless integration with OpenTelemetry, enhancing \nthe observability of your GraphQL services powered by Tailcall. By leveraging this \ndirective, developers gain access to valuable insights into the performance and behavior \nof their applications.\n\"\"\"\ninput Telemetry {\n export: TelemetryExporter\n \"\"\"\n The list of headers that will be sent as additional attributes to telemetry exporters \n Be careful about **leaking sensitive information** from requests when enabling the \n headers that may contain sensitive data\n \"\"\"\n requestHeaders: [String!]\n}\n\nenum Encoding {\n ApplicationJson\n ApplicationXWwwFormUrlencoded\n}\n\nenum Method {\n GET\n POST\n PUT\n PATCH\n DELETE\n HEAD\n OPTIONS\n CONNECT\n TRACE\n}\n\nenum LinkType {\n Config\n Protobuf\n Script\n Cert\n Key\n Operation\n Htpasswd\n Jwks\n Grpc\n}\n\nenum HttpVersion {\n HTTP1\n HTTP2\n}\n\n\"\"\"\nOutput format for prometheus data\n\"\"\"\nenum PrometheusFormat {\n text\n protobuf\n}\n\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" + "sdl": "schema @server(port: 8000) @upstream(baseURL: \"http://jsonplaceholder.typicode.com\", batch: {delay: 100, headers: []}, httpCache: 42) {\n query: Query\n}\n\nscalar _Any\n\nunion _Entity = Post | User\n\ntype Post @expr(body: {id: \"{{.value.id}}\", title: \"post-title-{{.value.id}}\"}) @key(fields: \"id\") {\n id: Int!\n title: String!\n}\n\ntype Query {\n \"\"\"\n Apollo federation Query._entities resolver\n \"\"\"\n _entities(representations: [_Any!]!): [_Entity]!\n \"\"\"\n Apollo federation Query._service resolver\n \"\"\"\n _service: _Service!\n user(id: Int!): User @http(path: \"/users/{{.args.id}}\")\n}\n\ntype User @call(steps: [{query: \"user\", args: {id: \"{{.value.id}}\"}}]) @key(fields: \"id\") {\n id: Int!\n name: String!\n}\n\ntype _Service {\n sdl: String\n}\nextend schema @link(\n\turl: \"https://specs.apollo.dev/federation/v2.3\",\n\timport: [\"@key\", \"@tag\", \"@shareable\", \"@inaccessible\", \"@override\", \"@external\", \"@provides\", \"@requires\", \"@composeDirective\", \"@interfaceObject\"]\n)\n" } } } diff --git a/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap index fc93f85386..586d3b1807 100644 --- a/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap +++ b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap @@ -2,23 +2,21 @@ source: tests/core/spec.rs expression: formatter --- -schema - @server(dedupe: true, port: 8000, queryValidation: false) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } type Post { body: String id: Int! - taggedUsers: [User] @http(path: "/taggedUsers/{{.value.id}}") + taggedUsers: [User] @http(path: "/taggedUsers/{{.value.id}}", dedupe: true) title: String - user: User @http(path: "/users/{{.value.userId}}") + user: User @http(path: "/users/{{.value.userId}}", dedupe: true) userId: Int! } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type User { diff --git a/tests/core/snapshots/async-cache-enabled.md_merged.snap b/tests/core/snapshots/async-cache-enabled.md_merged.snap index 5c670d71b6..6bc4cb7cd4 100644 --- a/tests/core/snapshots/async-cache-enabled.md_merged.snap +++ b/tests/core/snapshots/async-cache-enabled.md_merged.snap @@ -2,9 +2,7 @@ source: tests/core/spec.rs expression: formatter --- -schema - @server(dedupe: true, port: 8000, queryValidation: false) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } @@ -12,12 +10,12 @@ type Post { body: String id: Int title: String - user: User @http(path: "/users/{{.value.userId}}") + user: User @http(path: "/users/{{.value.userId}}", dedupe: true) userId: Int! } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type User { diff --git a/tests/core/snapshots/async-cache-global.md_merged.snap b/tests/core/snapshots/async-cache-global.md_merged.snap index cbf1b19790..8a9869d62e 100644 --- a/tests/core/snapshots/async-cache-global.md_merged.snap +++ b/tests/core/snapshots/async-cache-global.md_merged.snap @@ -2,9 +2,7 @@ source: tests/core/spec.rs expression: formatter --- -schema - @server(dedupe: true, port: 8000, queryValidation: false) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } @@ -16,7 +14,7 @@ type Post { } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type User { diff --git a/tests/core/snapshots/async-cache-inflight-request.md_merged.snap b/tests/core/snapshots/async-cache-inflight-request.md_merged.snap index 5c670d71b6..6bc4cb7cd4 100644 --- a/tests/core/snapshots/async-cache-inflight-request.md_merged.snap +++ b/tests/core/snapshots/async-cache-inflight-request.md_merged.snap @@ -2,9 +2,7 @@ source: tests/core/spec.rs expression: formatter --- -schema - @server(dedupe: true, port: 8000, queryValidation: false) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } @@ -12,12 +10,12 @@ type Post { body: String id: Int title: String - user: User @http(path: "/users/{{.value.userId}}") + user: User @http(path: "/users/{{.value.userId}}", dedupe: true) userId: Int! } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type User { diff --git a/tests/core/snapshots/dedupe_batch_query_execution.md_merged.snap b/tests/core/snapshots/dedupe_batch_query_execution.md_merged.snap index cbf1b19790..8a9869d62e 100644 --- a/tests/core/snapshots/dedupe_batch_query_execution.md_merged.snap +++ b/tests/core/snapshots/dedupe_batch_query_execution.md_merged.snap @@ -2,9 +2,7 @@ source: tests/core/spec.rs expression: formatter --- -schema - @server(dedupe: true, port: 8000, queryValidation: false) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } @@ -16,7 +14,7 @@ type Post { } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type User { diff --git a/tests/execution/async-cache-enable-multiple-resolvers.md b/tests/execution/async-cache-enable-multiple-resolvers.md index 74e0bcc410..8722629446 100644 --- a/tests/execution/async-cache-enable-multiple-resolvers.md +++ b/tests/execution/async-cache-enable-multiple-resolvers.md @@ -1,14 +1,12 @@ # Async Cache Enabled ```graphql @config -schema - @server(port: 8000, queryValidation: false, dedupe: true) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type Post { @@ -16,8 +14,8 @@ type Post { title: String body: String userId: Int! - user: User @http(path: "/users/{{.value.userId}}") - taggedUsers: [User] @http(path: "/taggedUsers/{{.value.id}}") + user: User @http(path: "/users/{{.value.userId}}", dedupe: true) + taggedUsers: [User] @http(path: "/taggedUsers/{{.value.id}}", dedupe: true) } type User { diff --git a/tests/execution/async-cache-enabled.md b/tests/execution/async-cache-enabled.md index 622b4334e3..f0a2544751 100644 --- a/tests/execution/async-cache-enabled.md +++ b/tests/execution/async-cache-enabled.md @@ -1,14 +1,12 @@ # Async Cache Enabled ```graphql @config -schema - @server(port: 8000, queryValidation: false, dedupe: true) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type Post { @@ -16,7 +14,7 @@ type Post { title: String body: String userId: Int! - user: User @http(path: "/users/{{.value.userId}}") + user: User @http(path: "/users/{{.value.userId}}", dedupe: true) } type User { diff --git a/tests/execution/async-cache-global.md b/tests/execution/async-cache-global.md index 95d21ea045..ee9744100d 100644 --- a/tests/execution/async-cache-global.md +++ b/tests/execution/async-cache-global.md @@ -1,14 +1,12 @@ # Async Cache Inflight Enabled ```graphql @config -schema - @server(port: 8000, queryValidation: false, dedupe: true) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type Post { diff --git a/tests/execution/async-cache-inflight-request.md b/tests/execution/async-cache-inflight-request.md index 7b9aeb61f2..0f10e19fb6 100644 --- a/tests/execution/async-cache-inflight-request.md +++ b/tests/execution/async-cache-inflight-request.md @@ -1,14 +1,12 @@ # Async Cache Inflight and InRequest ```graphql @config -schema - @server(port: 8000, queryValidation: false, dedupe: true) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type Post { @@ -16,7 +14,7 @@ type Post { title: String body: String userId: Int! - user: User @http(path: "/users/{{.value.userId}}") + user: User @http(path: "/users/{{.value.userId}}", dedupe: true) } type User { diff --git a/tests/execution/dedupe_batch_query_execution.md b/tests/execution/dedupe_batch_query_execution.md index 95d21ea045..ee9744100d 100644 --- a/tests/execution/dedupe_batch_query_execution.md +++ b/tests/execution/dedupe_batch_query_execution.md @@ -1,14 +1,12 @@ # Async Cache Inflight Enabled ```graphql @config -schema - @server(port: 8000, queryValidation: false, dedupe: true) - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } type Query { - posts: [Post] @http(path: "/posts?id=1") + posts: [Post] @http(path: "/posts?id=1", dedupe: true) } type Post { diff --git a/tests/jit_spec.rs b/tests/jit_spec.rs index cd78055f68..ae342112ee 100644 --- a/tests/jit_spec.rs +++ b/tests/jit_spec.rs @@ -33,7 +33,7 @@ mod tests { &self, request: Request, ) -> anyhow::Result> { - let executor = ConstValueExecutor::new(&request, self.app_ctx.clone())?; + let executor = ConstValueExecutor::new(&request, &self.app_ctx)?; Ok(executor.execute(&self.req_ctx, &request).await) }