diff --git a/package-lock.json b/package-lock.json index c717450..a224cc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -466,6 +466,62 @@ "node": ">=6.9.0" } }, + "node_modules/@envelop/core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@envelop/core/-/core-5.0.1.tgz", + "integrity": "sha512-wxA8EyE1fPnlbP0nC/SFI7uU8wSNf4YjxZhAPu0P63QbgIvqHtHsH4L3/u+rsTruzhk3OvNRgQyLsMfaR9uzAQ==", + "license": "MIT", + "dependencies": { + "@envelop/types": "5.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@envelop/on-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@envelop/on-resolve/-/on-resolve-4.1.0.tgz", + "integrity": "sha512-2AXxf8jbBIepBUiY0KQtyCO6gnT7LKBEYdaARZBJ7ujy1+iQHQPORKvAwl51kIdD6v5x38eldZnm7A19jFimOg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@envelop/core": "^5.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@envelop/prometheus": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@envelop/prometheus/-/prometheus-9.4.0.tgz", + "integrity": "sha512-r+BoQhDl/7qV8iZ0jOAumuMw3zi+IiFC5NFtgPSWwf3YFDH9PG1Y4WTh2qi+2GAFp/Z6CPv1TWePs3DHyz3v4w==", + "license": "MIT", + "dependencies": { + "@envelop/on-resolve": "^4.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@envelop/core": "^5.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", + "prom-client": "^15.0.0" + } + }, + "node_modules/@envelop/types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@envelop/types/-/types-5.0.0.tgz", + "integrity": "sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.19.12", "cpu": [ @@ -668,11 +724,12 @@ } }, "node_modules/@graphql-tools/executor": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.1.tgz", - "integrity": "sha512-BP5UI1etbNOXmTSt7q4NL1+zsURFgh2pG+Hyt9K/xO0LlsfbSx59L5dHLerqZP7Js0xI6GYqrUQ4m29rUwUHJg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.8.tgz", + "integrity": "sha512-0qZs/iuRiYRir7bBkA7oN+21wwmSMPQuFK8WcAcxUYJZRhvnlrJ8Nid++PN4OCzTgHPV70GNFyXOajseVCCffA==", + "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.2.3", "@graphql-typed-document-node/core": "3.2.0", "@repeaterjs/repeater": "^3.0.4", "tslib": "^2.4.0", @@ -718,9 +775,10 @@ } }, "node_modules/@graphql-tools/utils": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.0.tgz", - "integrity": "sha512-wLPqhgeZ9BZJPRoaQbsDN/CtJDPd/L4qmmtPkjI3NuYJ39x+Eqz1Sh34EAGMuDh+xlOHqBwHczkZUpoK9tvzjw==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.3.1.tgz", + "integrity": "sha512-Yhk1F0MNk4/ctgl3d0DKq++ZPovvZuh1ixWuUEVAxrFloYOAVwJ+rvGI1lsopArdJly8QXClT9lkvOxQszMw/w==", + "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "cross-inspect": "1.0.0", @@ -742,6 +800,63 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/@graphql-yoga/logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/logger/-/logger-2.0.0.tgz", + "integrity": "sha512-Mg8psdkAp+YTG1OGmvU+xa6xpsAmSir0hhr3yFYPyLNwzUj95DdIwsMpKadDj9xDpYgJcH3Hp/4JMal9DhQimA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-yoga/plugin-prometheus": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/plugin-prometheus/-/plugin-prometheus-4.2.0.tgz", + "integrity": "sha512-EK3HddOwXJRjOnIke1nOk0NWjYhSAi8F1zZm117INdwYwfBjirbC7gxEJpWBW26wBuudIaRHM+6R87EOM9q2Kw==", + "license": "MIT", + "dependencies": { + "@envelop/prometheus": "^9.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0", + "graphql-yoga": "^5.3.0", + "prom-client": "^15.0.0" + } + }, + "node_modules/@graphql-yoga/subscription": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-5.0.1.tgz", + "integrity": "sha512-1wCB1DfAnaLzS+IdoOzELGGnx1ODEg9nzQXFh4u2j02vAnne6d+v4A7HIH9EqzVdPLoAaMKXCZUUdKs+j3z1fg==", + "license": "MIT", + "dependencies": { + "@graphql-yoga/typed-event-target": "^3.0.0", + "@repeaterjs/repeater": "^3.0.4", + "@whatwg-node/events": "^0.1.0", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@graphql-yoga/typed-event-target": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.0.tgz", + "integrity": "sha512-w+liuBySifrstuHbFrHoHAEyVnDFVib+073q8AeAJ/qqJfvFvAwUPLLtNohR/WDVRgSasfXtl3dcNuVJWN+rjg==", + "license": "MIT", + "dependencies": { + "@repeaterjs/repeater": "^3.0.4", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -883,7 +998,8 @@ "node_modules/@kamilkisiela/fast-url-parser": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", - "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==" + "integrity": "sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==", + "license": "MIT" }, "node_modules/@microsoft/api-extractor": { "version": "7.39.0", @@ -945,6 +1061,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1443,11 +1568,12 @@ } }, "node_modules/@whatwg-node/fetch": { - "version": "0.9.16", - "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.16.tgz", - "integrity": "sha512-mqasZiUNquRe3ea9+aCAuo81BR6vq5opUKprPilIHTnrg8a21Z1T1OrI+KiMFX8OmwO5HUJe/vro47lpj2JPWQ==", + "version": "0.9.18", + "resolved": "https://registry.npmjs.org/@whatwg-node/fetch/-/fetch-0.9.18.tgz", + "integrity": "sha512-hqoz6StCW+AjV/3N+vg0s1ah82ptdVUb9nH2ttj3UbySOXUvytWw2yqy8c1cKzyRk6mDD00G47qS3fZI9/gMjg==", + "license": "MIT", "dependencies": { - "@whatwg-node/node-fetch": "^0.5.5", + "@whatwg-node/node-fetch": "^0.5.7", "urlpattern-polyfill": "^10.0.0" }, "engines": { @@ -1455,9 +1581,10 @@ } }, "node_modules/@whatwg-node/node-fetch": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.6.tgz", - "integrity": "sha512-cmAsGMHoI0S3AHi3CmD3ma1Q234ZI2JNmXyDyM9rLtbXejBKxU3ZWdhS+mzRIAyUxZCMGlFW1tHmROv0MDdxpw==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", + "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "license": "MIT", "dependencies": { "@kamilkisiela/fast-url-parser": "^1.1.4", "@whatwg-node/events": "^0.1.0", @@ -1469,6 +1596,19 @@ "node": ">=16.0.0" } }, + "node_modules/@whatwg-node/server": { + "version": "0.9.36", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.36.tgz", + "integrity": "sha512-KT9qKLmbuWSuFv0Vg4JyK2vN2+vSuQPeEa25xpndYFROAIZntYe7e2BlWAk9l7IrgnV+M4bCVhjrAwwRsaCeiA==", + "license": "MIT", + "dependencies": { + "@whatwg-node/fetch": "^0.9.17", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "license": "BSD-2-Clause" @@ -1569,6 +1709,12 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "license": "MIT", @@ -2072,7 +2218,8 @@ "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -2086,6 +2233,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", "dependencies": { "fast-decode-uri-component": "^1.0.1" } @@ -2282,6 +2430,40 @@ "resolved": "packages/graphql-mesh", "link": true }, + "node_modules/graphql-yoga": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.6.0.tgz", + "integrity": "sha512-MqzHRPmiMSilYLDbJtAnXN7oyggd446a4F9dyj/H4gCmM/3YllCYw3vtKcmsykorsfiSKCYpCf5CimNXIVaHHg==", + "license": "MIT", + "dependencies": { + "@envelop/core": "^5.0.0", + "@graphql-tools/executor": "^1.2.5", + "@graphql-tools/schema": "^10.0.0", + "@graphql-tools/utils": "^10.1.0", + "@graphql-yoga/logger": "^2.0.0", + "@graphql-yoga/subscription": "^5.0.1", + "@whatwg-node/fetch": "^0.9.17", + "@whatwg-node/server": "^0.9.33", + "dset": "^3.1.1", + "lru-cache": "^10.0.0", + "tslib": "^2.5.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0" + } + }, + "node_modules/graphql-yoga/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -2736,6 +2918,18 @@ "ufo": "^1.3.2" } }, + "node_modules/monitor-envelop": { + "resolved": "packages/graphql-mesh/custom-plugins/monitor-envelop.ts", + "link": true + }, + "node_modules/monitor-fetch": { + "resolved": "packages/graphql-mesh/custom-plugins/monitor-fetch.ts", + "link": true + }, + "node_modules/monitor-yoga": { + "resolved": "packages/graphql-mesh/custom-plugins/monitor-yoga.ts", + "link": true + }, "node_modules/ms": { "version": "2.1.2", "license": "MIT" @@ -3106,6 +3300,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/punycode": { "version": "2.3.1", "license": "MIT", @@ -3523,6 +3730,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3691,6 +3907,19 @@ "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/validator": { "version": "13.11.0", "dev": true, @@ -4095,6 +4324,7 @@ "@graphql-mesh/graphql": "^0.96.2", "@graphql-mesh/merger-stitching": "^0.96.2", "@graphql-mesh/openapi": "^0.97.4", + "@graphql-mesh/plugin-prometheus": "^0.96.2", "@graphql-mesh/transform-filter-schema": "^0.96.2", "@graphql-mesh/transform-naming-convention": "^0.96.2", "@graphql-mesh/transform-rename": "^0.96.2", @@ -4106,8 +4336,13 @@ "glob": "^10.3.10", "graphql": "^16.8.1", "inject-additional-transforms": "file:./local-pkg/inject-additional-transforms-1.0.0.tgz", + "monitor-envelop": "file:./custom-plugins/monitor-envelop.ts", + "monitor-fetch": "file:./custom-plugins/monitor-fetch.ts", + "monitor-yoga": "file:./custom-plugins/monitor-yoga.ts", "patch-package": "^8.0.0", - "sucrase": "^3.35.0" + "prom-client": "^15.1.3", + "sucrase": "^3.35.0", + "uuid": "^10.0.0" }, "devDependencies": { "@graphql-mesh/types": "^0.96.3", @@ -4115,6 +4350,9 @@ } }, "packages/graphql-mesh/custom-plugins/filter-null.ts": {}, + "packages/graphql-mesh/custom-plugins/monitor-envelop.ts": {}, + "packages/graphql-mesh/custom-plugins/monitor-fetch.ts": {}, + "packages/graphql-mesh/custom-plugins/monitor-yoga.ts": {}, "packages/graphql-mesh/node_modules/@apollo/client": { "version": "3.8.10", "license": "MIT", @@ -4717,17 +4955,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "packages/graphql-mesh/node_modules/@envelop/core": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@envelop/types": "5.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/graphql-mesh/node_modules/@envelop/extended-validation": { "version": "4.0.0", "license": "MIT", @@ -4759,16 +4986,6 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, - "packages/graphql-mesh/node_modules/@envelop/types": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/graphql-mesh/node_modules/@graphql-codegen/core": { "version": "4.0.0", "license": "MIT", @@ -5241,6 +5458,26 @@ "tslib": "^2.4.0" } }, + "packages/graphql-mesh/node_modules/@graphql-mesh/plugin-prometheus": { + "version": "0.96.7", + "resolved": "https://registry.npmjs.org/@graphql-mesh/plugin-prometheus/-/plugin-prometheus-0.96.7.tgz", + "integrity": "sha512-ELxukJutPyw0FEMEEtVsCqDItkh4yz5tHSxsL47wyQNsagSSCiFY2bAU5ChQ3lW2WKC91KJO3C7xS6FVcmr6lA==", + "license": "MIT", + "dependencies": { + "@graphql-yoga/plugin-prometheus": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@graphql-mesh/types": "^0.96.6", + "@graphql-mesh/utils": "^0.96.6", + "graphql": "*", + "graphql-yoga": "^4.0.5 || ^5.0.0", + "prom-client": "^13 || ^14.0.0 || ^15.0.0", + "tslib": "^2.4.0" + } + }, "packages/graphql-mesh/node_modules/@graphql-mesh/runtime": { "version": "0.97.4", "license": "MIT", @@ -5697,16 +5934,6 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "packages/graphql-mesh/node_modules/@graphql-yoga/logger": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "tslib": "^2.5.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/graphql-mesh/node_modules/@graphql-yoga/plugin-persisted-operations": { "version": "3.1.1", "license": "MIT", @@ -5719,30 +5946,6 @@ "graphql-yoga": "^5.1.1" } }, - "packages/graphql-mesh/node_modules/@graphql-yoga/subscription": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "@graphql-yoga/typed-event-target": "^3.0.0", - "@repeaterjs/repeater": "^3.0.4", - "@whatwg-node/events": "^0.1.0", - "tslib": "^2.5.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/graphql-mesh/node_modules/@graphql-yoga/typed-event-target": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@repeaterjs/repeater": "^3.0.4", - "tslib": "^2.5.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/graphql-mesh/node_modules/@json-schema-tools/meta-schema": { "version": "1.7.0", "license": "Apache-2.0" @@ -6054,17 +6257,6 @@ "@types/node": "*" } }, - "packages/graphql-mesh/node_modules/@whatwg-node/server": { - "version": "0.9.24", - "license": "MIT", - "dependencies": { - "@whatwg-node/fetch": "^0.9.10", - "tslib": "^2.3.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, "packages/graphql-mesh/node_modules/@wry/caches": { "version": "1.0.1", "license": "MIT", @@ -6741,36 +6933,6 @@ "graphql": ">=0.11 <=16" } }, - "packages/graphql-mesh/node_modules/graphql-yoga": { - "version": "5.1.1", - "license": "MIT", - "dependencies": { - "@envelop/core": "^5.0.0", - "@graphql-tools/executor": "^1.0.0", - "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.0.0", - "@graphql-yoga/logger": "^2.0.0", - "@graphql-yoga/subscription": "^5.0.0", - "@whatwg-node/fetch": "^0.9.7", - "@whatwg-node/server": "^0.9.1", - "dset": "^3.1.1", - "lru-cache": "^10.0.0", - "tslib": "^2.5.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "graphql": "^15.2.0 || ^16.0.0" - } - }, - "packages/graphql-mesh/node_modules/graphql-yoga/node_modules/lru-cache": { - "version": "10.2.0", - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } - }, "packages/graphql-mesh/node_modules/has-unicode": { "version": "2.0.1", "license": "ISC", diff --git a/package.json b/package.json index 1b06856..2578ad6 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "postinstall": "npm run postinstall -w graphql-mesh && patch-package && npm run generate:mesh:lock", "build:local:packages": "concurrently \"npm run pack -w directive-spl\" \"npm run pack -w inject-additional-transforms\"", "preinstall": "npm run build:local:packages", - "start": "npm start -w graphql-mesh" - }, + "start": "npm start -w graphql-mesh", + "startmesh": "npm run startmesh -w graphql-mesh", + "server": "npm run server -w graphql-mesh" + }, "devDependencies": { "concurrently": "^8.2.2", "patch-package": "^8.0.0" diff --git a/packages/graphql-mesh/custom-plugins/monitor-envelop.ts b/packages/graphql-mesh/custom-plugins/monitor-envelop.ts new file mode 100644 index 0000000..5ce854e --- /dev/null +++ b/packages/graphql-mesh/custom-plugins/monitor-envelop.ts @@ -0,0 +1,111 @@ +import { type Plugin } from '@envelop/core' +import { Logger } from '../utils/logger' +import { NoSchemaIntrospectionCustomRule } from 'graphql'; +import { GraphQLError } from 'graphql'; +/** + * monitor plugin in order to get event contextual log and add some security rules + * useful to + * - log the graphql Query event + * - add desabled introspection validation rule + * - remove suggestion message + * - log the execute result summary with executes duration + * - remove not allowed introspection in result + */ + +const formatter = (error: GraphQLError, mask: string): GraphQLError => { + if (error instanceof GraphQLError) { + error.message = error.message.replace(/Did you mean ".+"/g, mask); + } + return error as GraphQLError; +}; +export default ({ options }): Plugin => { + // not allow by default + // do not enabled allowIntrospection in production + const allowIntrospection = process.env['IS_PROUCTION_ENV'] != 'true' && (options?.introspection?.allow || process.env['ENABLED_INTROSPECTION'] || false) + // low info in log by default + const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : "low" + const denyIntrospectionHeaderName = options?.introspection?.denyHeaderName || null + const denyIntrospectionHeaderValue = options?.introspection?.denyHeaderValue || null + const allowIntrospectionHeaderName = options?.introspection?.allowHeaderName || null + const allowIntrospectionHeaderValue = options?.introspection?.allowHeaderValue || null + const isMaskSuggestion = options?.maskSuggestion?.enabled || false + const maskSuggestionMessage = options?.maskSuggestion?.message || "" + return { + onParse({ context }) { + if (options.logOnParse) { + Logger.graphqlQuery(context['request']['headers'], context['params']) + } + }, + + onValidate: ({ addValidationRule, context }) => { + const headers = context['request'].headers + let deny = true + /* + allowIntrospection=false : intropection deny for all + denyIntrospectionHeaderName : name of the header to check to deny introspection is deny ex plublic proxy header + allowIntrospectionHeaderName : name of the header allow if this header and value is presents + */ + // if introspection not allow + if (allowIntrospection) { + // intropection may be allow + deny = false + // is existed a header to deny introspection + if (denyIntrospectionHeaderName) { + if (headers.get(denyIntrospectionHeaderName)) { + if (headers.get(denyIntrospectionHeaderName).includes(denyIntrospectionHeaderValue)) { + Logger.denyIntrospection("onValidate", "deny by headers " + denyIntrospectionHeaderName + ": " + headers.get(denyIntrospectionHeaderName), headers) + deny = true + } + } + } + // is existed a header mandatory to allow introspection + if (allowIntrospectionHeaderName) { + deny = true + if (headers.get(allowIntrospectionHeaderName)) { + if (headers.get(allowIntrospectionHeaderName).includes(allowIntrospectionHeaderValue)) { + Logger.allowIntrospection("onValidate", "allow by headers " + allowIntrospectionHeaderName + ": " + headers.get(allowIntrospectionHeaderName).substring(0, 4) + "...", headers) + deny = false + } else { + Logger.denyIntrospection("onValidate", "deny by bad header value " + allowIntrospectionHeaderName + ": " + headers.get(allowIntrospectionHeaderName).substring(0, 4) + "...", headers) + } + } else { + Logger.denyIntrospection("onValidate", "deny by no header " + allowIntrospectionHeaderName, headers) + } + } + } + if (deny) { + addValidationRule(NoSchemaIntrospectionCustomRule) + } + + return function onValidateEnd({ valid, result, setResult }) { + if (isMaskSuggestion && !valid) { + setResult(result.map((error: GraphQLError) => formatter(error, maskSuggestionMessage))); + } + }; + }, + + onExecute(/*{ args, extendContext }*/) { + let timestampDebut = new Date().getTime() + return { + before() { + + timestampDebut = new Date().getTime() + }, + onExecuteDone({ result, args }) { + const timestampDone = new Date().getTime(); + // short cut to desabled introspection response in case of bad configuration rule + if (!allowIntrospection && args.contextValue['params'].query.includes('__schema')) { + result['data'] = {} + result['errors'] = [{ message: "Fordidden" }] + Logger.error('SECU', 'onExecute', 'Introspection query deteted not allowed', args.contextValue['params']) + } + if (options.loOnExecuteDone) { + Logger.endExec(args.contextValue['request']['headers'], result, timestampDone - timestampDebut, resultLogInfoLevel) + } + } + } + } + } +} + + diff --git a/packages/graphql-mesh/custom-plugins/monitor-fetch.ts b/packages/graphql-mesh/custom-plugins/monitor-fetch.ts new file mode 100644 index 0000000..26d7437 --- /dev/null +++ b/packages/graphql-mesh/custom-plugins/monitor-fetch.ts @@ -0,0 +1,65 @@ +import { type Plugin } from '@envelop/core'; + +import { Logger } from '../utils/logger' + +/** + * monitor fetch source + * use to : + * - add log event for each fetch like, url, response status, duration + */ + +export default ({ options }) => { + return { + onFetch({ context, info }) { + if (!info) { + Logger.warn("noFeychInfo", "onFetch", "no info in on fetch") + return; + } + const start = Date.now(); + let rawSource = context[info.sourceName] + let description = info.parentType._fields[info.path.key].description + + return (fetch: any) => { + if (options.logOnFetch) { + const duration = Date.now() - start; + let fetchInfo = {} + let httpStatus = null + let url = null + if (options.fullFetchInfo) { + fetchInfo = { + fieldName: info.fieldName, + sourceName: info.sourceName, + pathKey: info.path.key, + operation: info.operation.name, + variables: info.variables, + endpoint: rawSource.rawSource.handler.config.endpoint, + description: description + } + } else { + fetchInfo = { + fieldName: info.fieldName, + pathKey: info.path.key, + operation: info.operation.name, + variables: info.variableValues, + endpoint: rawSource.rawSource.handler.config.endpoint, + } + } + //const fetchResponseInfo = {} + if (fetch.response) { + + httpStatus = fetch.response.status + url = fetch.response.url + const options = fetch.response.options + if (options) { + fetchInfo['options'] = { + requestId: options.headers['x-request-id'], + server: options.headers['server'] + } + } + } + Logger.onFetch(context.request, url, httpStatus, duration, fetchInfo) + } + } + } + } +} diff --git a/packages/graphql-mesh/custom-plugins/monitor-yoga.ts b/packages/graphql-mesh/custom-plugins/monitor-yoga.ts new file mode 100644 index 0000000..dffe845 --- /dev/null +++ b/packages/graphql-mesh/custom-plugins/monitor-yoga.ts @@ -0,0 +1,118 @@ +import { Plugin } from 'graphql-yoga' +import { Logger } from '../utils/logger' +import { GraphQLError } from 'graphql' +import { v4 as uuidv4 } from 'uuid' +/** + * monitor plugin in order to get event contextual log and add some security rules + * useful to : + * - log new request comming event + * - add request timestamp in headers to get duration time + * - monitor instropection request + * - mask error in result is required ( ex in production ) + * - log response sumary event + * - remove a eventualy not allowed instropection data in result + */ + +export function useYagaMonitoring({ options }): Plugin { + const isMaskErrors = options?.maskError?.enabled || process.env['MASK_ERRORS'] || false + // filter error in production anyway + const isFilterError = options?.filterError?.enabled || process.env['FILTER_ERRORS'] == 'true' || process.env['IS_PROUCTION_ENV'] == 'true' || false + const errorMaskMessage = options?.maskError?.message ? options.maskError.message : "something goes wrong" + const responseLogInfoLevel = options?.responseLogInfoLevel ? options.responseLogInfoLevel : "low" + const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : "medium" + + return { + onRequest({ request/*, fetchAPI, endResponse */ }) { + if (options.LogOnRequest) { + // log only graphql request, avoid log other request like metric requests + if (request.url.includes("/graphql")) { + Logger.onRequest(request) + } + } + + // add resuestTimestamp in headers + const timestamp = new Date().getTime(); + request.headers.append("requestTimestamp", String(timestamp)) + + // add x-request-id in header if not present + if (!request.headers.get('x-request-id')) { + request.headers.append("x-request-id", uuidv4()) + } + + }, + onRequestParse(args) { + const beforeTimestamp = new Date().getTime(); + let requestHeaders = args.request.headers + return { + onRequestParseDone(nRequestParseDoneEventPayload) { + const timestamp = new Date().getTime(); + if (options.logOnRequestParseDone) { + Logger.onRequestParseDone(requestHeaders, nRequestParseDoneEventPayload.requestParserResult['query'], nRequestParseDoneEventPayload.requestParserResult['operationName'], nRequestParseDoneEventPayload.requestParserResult['variables'], timestamp - beforeTimestamp) + } + if (nRequestParseDoneEventPayload.requestParserResult['query'].includes('__schema')) { + Logger.introspection( requestHeaders, nRequestParseDoneEventPayload.requestParserResult['query']) + } + } + } + }, + onResultProcess(args) { + if (options.logOnResultProcess) { + // calculate duration from request timestamp + let requestTimestamp: number = 0 + if (args.request['headers']) { + const requestTimestampString = args.request['headers'].get('requesttimestamp') + if (requestTimestampString) { + requestTimestamp = parseInt(requestTimestampString) + } + } + const responseTimestamp = new Date().getTime(); + Logger.onResultProcess(args.request, args.result, requestTimestamp > 0 ? responseTimestamp - requestTimestamp : 0, resultLogInfoLevel) + } + // if we want to replace all message with a generic message + if (isMaskErrors) { + if (args.result['errors']) { + let errors = args.result['errors'] + for (let i = 0; i < errors.length; i++) { + errors[i] = errorMaskMessage + } + } + } else { + // if we want to filter error to only return the message, don't return extend information like stacktrace + if (isFilterError) { + if (args.result['errors']) { + let errors = args.result['errors'] + for (let i = 0; i < errors.length; i++) { + errors[i] = new GraphQLError(filterErrorMessage(errors[i]['message'])) + } + + } + + } + } + }, + + onResponse({ request, response }) { + // dont log options http + if (request.method != 'OPTIONS') { + if (options.logOnResponse) { + // only log graphql request don't log metrics or other requests + if (request.url.includes("/graphql")) { + Logger.onResponse(request, response, responseLogInfoLevel) + } + } + } + } + } +} + +/** filterErrorMessage + * use to filter error message : + * - remove disabled introspection + * todo: add other filter rules to remove non expecting message +*/ +function filterErrorMessage(message: string) { + if (message.includes("introspection has been disabled")) { + return "forbidden" + } + return message +} diff --git a/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz b/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz index 14325af..aa64f12 100644 Binary files a/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz differ diff --git a/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz b/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz index 93b007f..1f45b74 100644 Binary files a/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz differ diff --git a/packages/graphql-mesh/package-lock.json b/packages/graphql-mesh/package-lock.json index 663e1a7..79b2d12 100644 --- a/packages/graphql-mesh/package-lock.json +++ b/packages/graphql-mesh/package-lock.json @@ -11,6 +11,7 @@ "@graphql-mesh/graphql": "^0.96.2", "@graphql-mesh/merger-stitching": "^0.96.2", "@graphql-mesh/openapi": "^0.97.4", + "@graphql-mesh/plugin-prometheus": "^0.96.2", "@graphql-mesh/transform-filter-schema": "^0.96.2", "@graphql-mesh/transform-naming-convention": "^0.96.2", "@graphql-mesh/transform-rename": "^0.96.2", @@ -22,8 +23,13 @@ "glob": "^10.3.10", "graphql": "^16.8.1", "inject-additional-transforms": "file:./local-pkg/inject-additional-transforms-1.0.0.tgz", + "monitor-envelop": "file:./custom-plugins/monitor-envelop.ts", + "monitor-fetch": "file:./custom-plugins/monitor-fetch.ts", + "monitor-yoga": "file:./custom-plugins/monitor-yoga.ts", "patch-package": "^8.0.0", - "sucrase": "^3.35.0" + "prom-client": "^15.1.3", + "sucrase": "^3.35.0", + "uuid": "^10.0.0" }, "devDependencies": { "@graphql-mesh/types": "^0.96.3", @@ -31,6 +37,9 @@ } }, "custom-plugins/filter-null.ts": {}, + "custom-plugins/monitor-envelop.ts": {}, + "custom-plugins/monitor-fetch.ts": {}, + "custom-plugins/monitor-yoga.ts": {}, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1067,6 +1076,37 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/@envelop/on-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@envelop/on-resolve/-/on-resolve-4.1.0.tgz", + "integrity": "sha512-2AXxf8jbBIepBUiY0KQtyCO6gnT7LKBEYdaARZBJ7ujy1+iQHQPORKvAwl51kIdD6v5x38eldZnm7A19jFimOg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@envelop/core": "^5.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/@envelop/prometheus": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@envelop/prometheus/-/prometheus-9.4.0.tgz", + "integrity": "sha512-r+BoQhDl/7qV8iZ0jOAumuMw3zi+IiFC5NFtgPSWwf3YFDH9PG1Y4WTh2qi+2GAFp/Z6CPv1TWePs3DHyz3v4w==", + "license": "MIT", + "dependencies": { + "@envelop/on-resolve": "^4.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@envelop/core": "^5.0.0", + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0", + "prom-client": "^15.0.0" + } + }, "node_modules/@envelop/types": { "version": "5.0.0", "license": "MIT", @@ -1580,6 +1620,26 @@ "tslib": "^2.4.0" } }, + "node_modules/@graphql-mesh/plugin-prometheus": { + "version": "0.96.7", + "resolved": "https://registry.npmjs.org/@graphql-mesh/plugin-prometheus/-/plugin-prometheus-0.96.7.tgz", + "integrity": "sha512-ELxukJutPyw0FEMEEtVsCqDItkh4yz5tHSxsL47wyQNsagSSCiFY2bAU5ChQ3lW2WKC91KJO3C7xS6FVcmr6lA==", + "license": "MIT", + "dependencies": { + "@graphql-yoga/plugin-prometheus": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@graphql-mesh/types": "^0.96.6", + "@graphql-mesh/utils": "^0.96.6", + "graphql": "*", + "graphql-yoga": "^4.0.5 || ^5.0.0", + "prom-client": "^13 || ^14.0.0 || ^15.0.0", + "tslib": "^2.4.0" + } + }, "node_modules/@graphql-mesh/runtime": { "version": "0.97.4", "license": "MIT", @@ -1858,11 +1918,12 @@ } }, "node_modules/@graphql-tools/executor": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.1.tgz", - "integrity": "sha512-BP5UI1etbNOXmTSt7q4NL1+zsURFgh2pG+Hyt9K/xO0LlsfbSx59L5dHLerqZP7Js0xI6GYqrUQ4m29rUwUHJg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.8.tgz", + "integrity": "sha512-0qZs/iuRiYRir7bBkA7oN+21wwmSMPQuFK8WcAcxUYJZRhvnlrJ8Nid++PN4OCzTgHPV70GNFyXOajseVCCffA==", + "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.2.3", "@graphql-typed-document-node/core": "3.2.0", "@repeaterjs/repeater": "^3.0.4", "tslib": "^2.4.0", @@ -2141,9 +2202,10 @@ } }, "node_modules/@graphql-tools/utils": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.1.2.tgz", - "integrity": "sha512-fX13CYsDnX4yifIyNdiN0cVygz/muvkreWWem6BBw130+ODbRRgfiVveL0NizCEnKXkpvdeTy9Bxvo9LIKlhrw==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.3.1.tgz", + "integrity": "sha512-Yhk1F0MNk4/ctgl3d0DKq++ZPovvZuh1ixWuUEVAxrFloYOAVwJ+rvGI1lsopArdJly8QXClT9lkvOxQszMw/w==", + "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "cross-inspect": "1.0.0", @@ -2204,8 +2266,27 @@ "graphql-yoga": "^5.1.1" } }, + "node_modules/@graphql-yoga/plugin-prometheus": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/plugin-prometheus/-/plugin-prometheus-4.2.0.tgz", + "integrity": "sha512-EK3HddOwXJRjOnIke1nOk0NWjYhSAi8F1zZm117INdwYwfBjirbC7gxEJpWBW26wBuudIaRHM+6R87EOM9q2Kw==", + "license": "MIT", + "dependencies": { + "@envelop/prometheus": "^9.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "graphql": "^15.2.0 || ^16.0.0", + "graphql-yoga": "^5.3.0", + "prom-client": "^15.0.0" + } + }, "node_modules/@graphql-yoga/subscription": { - "version": "5.0.0", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@graphql-yoga/subscription/-/subscription-5.0.1.tgz", + "integrity": "sha512-1wCB1DfAnaLzS+IdoOzELGGnx1ODEg9nzQXFh4u2j02vAnne6d+v4A7HIH9EqzVdPLoAaMKXCZUUdKs+j3z1fg==", "license": "MIT", "dependencies": { "@graphql-yoga/typed-event-target": "^3.0.0", @@ -2219,6 +2300,8 @@ }, "node_modules/@graphql-yoga/typed-event-target": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@graphql-yoga/typed-event-target/-/typed-event-target-3.0.0.tgz", + "integrity": "sha512-w+liuBySifrstuHbFrHoHAEyVnDFVib+073q8AeAJ/qqJfvFvAwUPLLtNohR/WDVRgSasfXtl3dcNuVJWN+rjg==", "license": "MIT", "dependencies": { "@repeaterjs/repeater": "^3.0.4", @@ -2658,6 +2741,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2751,10 +2843,12 @@ } }, "node_modules/@whatwg-node/server": { - "version": "0.9.24", + "version": "0.9.36", + "resolved": "https://registry.npmjs.org/@whatwg-node/server/-/server-0.9.36.tgz", + "integrity": "sha512-KT9qKLmbuWSuFv0Vg4JyK2vN2+vSuQPeEa25xpndYFROAIZntYe7e2BlWAk9l7IrgnV+M4bCVhjrAwwRsaCeiA==", "license": "MIT", "dependencies": { - "@whatwg-node/fetch": "^0.9.10", + "@whatwg-node/fetch": "^0.9.17", "tslib": "^2.3.1" }, "engines": { @@ -3011,6 +3105,12 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.1", "license": "MIT", @@ -4086,17 +4186,19 @@ } }, "node_modules/graphql-yoga": { - "version": "5.1.1", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/graphql-yoga/-/graphql-yoga-5.6.0.tgz", + "integrity": "sha512-MqzHRPmiMSilYLDbJtAnXN7oyggd446a4F9dyj/H4gCmM/3YllCYw3vtKcmsykorsfiSKCYpCf5CimNXIVaHHg==", "license": "MIT", "dependencies": { "@envelop/core": "^5.0.0", - "@graphql-tools/executor": "^1.0.0", + "@graphql-tools/executor": "^1.2.5", "@graphql-tools/schema": "^10.0.0", - "@graphql-tools/utils": "^10.0.0", + "@graphql-tools/utils": "^10.1.0", "@graphql-yoga/logger": "^2.0.0", - "@graphql-yoga/subscription": "^5.0.0", - "@whatwg-node/fetch": "^0.9.7", - "@whatwg-node/server": "^0.9.1", + "@graphql-yoga/subscription": "^5.0.1", + "@whatwg-node/fetch": "^0.9.17", + "@whatwg-node/server": "^0.9.33", "dset": "^3.1.1", "lru-cache": "^10.0.0", "tslib": "^2.5.2" @@ -5000,6 +5102,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/monitor-envelop": { + "resolved": "custom-plugins/monitor-envelop.ts", + "link": true + }, + "node_modules/monitor-fetch": { + "resolved": "custom-plugins/monitor-fetch.ts", + "link": true + }, + "node_modules/monitor-yoga": { + "resolved": "custom-plugins/monitor-yoga.ts", + "link": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5701,6 +5815,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, "node_modules/promise": { "version": "7.3.1", "license": "MIT", @@ -6267,6 +6394,15 @@ "license": "ISC", "optional": true }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6571,6 +6707,19 @@ "license": "MIT", "optional": true }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/uWebSockets.js": { "version": "20.43.0", "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#1977b5039938ad863d42fc4958d48c17e5a1fa06", diff --git a/packages/graphql-mesh/package.json b/packages/graphql-mesh/package.json index 22150d2..4c850e4 100644 --- a/packages/graphql-mesh/package.json +++ b/packages/graphql-mesh/package.json @@ -6,12 +6,15 @@ "postinstall": "patch-package", "serve": "npm run build && mesh start", "start": "npm run downloadswaggers && mesh dev", - "test": "vitest" + "startmesh": "mesh build", + "server": "sucrase-node serve.ts", + "test": "vitest" }, "dependencies": { "@graphql-mesh/cache-localforage": "^0.96.2", "@graphql-mesh/cli": "^0.88.4", "@graphql-mesh/graphql": "^0.96.2", + "@graphql-mesh/plugin-prometheus": "^0.96.2", "@graphql-mesh/merger-stitching": "^0.96.2", "@graphql-mesh/openapi": "^0.97.4", "@graphql-mesh/transform-filter-schema": "^0.96.2", @@ -20,13 +23,18 @@ "@graphql-mesh/transform-type-merging": "^0.96.2", "@graphql-tools/schema": "^10.0.2", "@graphql-tools/utils": "^10.0.12", + "prom-client": "^15.1.3", "directive-spl": "file:./local-pkg/directive-spl-1.0.0.tgz", + "monitor-envelop": "file:./custom-plugins/monitor-envelop.ts", + "monitor-fetch": "file:./custom-plugins/monitor-fetch.ts", + "monitor-yoga": "file:./custom-plugins/monitor-yoga.ts", "filter-null-plugin": "file:./custom-plugins/filter-null.ts", "glob": "^10.3.10", "graphql": "^16.8.1", "inject-additional-transforms": "file:./local-pkg/inject-additional-transforms-1.0.0.tgz", "patch-package": "^8.0.0", - "sucrase": "^3.35.0" + "sucrase": "^3.35.0", + "uuid": "^10.0.0" }, "devDependencies": { "@graphql-mesh/types": "^0.96.3", diff --git a/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch b/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch index 3257901..cbfaf5f 100644 --- a/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch +++ b/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch @@ -1,15 +1,3 @@ -diff --git a/node_modules/@omnigraph/json-schema/cjs/addExecutionLogicToComposer.js b/node_modules/@omnigraph/json-schema/cjs/addExecutionLogicToComposer.js -index 9bbb265..491eb4c 100644 ---- a/node_modules/@omnigraph/json-schema/cjs/addExecutionLogicToComposer.js -+++ b/node_modules/@omnigraph/json-schema/cjs/addExecutionLogicToComposer.js -@@ -49,6 +49,7 @@ async function addExecutionDirectivesToComposer(name, { schemaComposer, logger, - >**Method**: \`${operationConfig.method}\` - >**Base URL**: \`${endpoint}\` - >**Path**: \`${operationConfig.path}\` -+>**Version**: \`${name.split('@')[1]}\` - ${operationConfig.description || ''} - `; - } diff --git a/node_modules/@omnigraph/json-schema/cjs/addRootFieldResolver.js b/node_modules/@omnigraph/json-schema/cjs/addRootFieldResolver.js old mode 100644 new mode 100755 diff --git a/packages/graphql-mesh/serve.ts b/packages/graphql-mesh/serve.ts new file mode 100644 index 0000000..aa1b9fb --- /dev/null +++ b/packages/graphql-mesh/serve.ts @@ -0,0 +1,13 @@ +import { createServer } from 'node:http' +import { getConfig } from './utils/parseYamlConfig' +import { Logger } from './utils/logger' +const mesh = process.env['MESH_ARTIFACTS_DIRNAME'] || ".mesh" +import(mesh) + .then(obj => { + const config = getConfig() + const PORT = config.serve?.port ?? 4000 + const HOSTNAME = config.serve?.hostname ?? 'http://0.0.0.0' + const server = createServer(obj.createBuiltMeshHTTPHandler()) + Logger.info('STARTUP', 'main', `🚀 Server ready at ${HOSTNAME}:${PORT}/graphql`) + server.listen(PORT) + }) diff --git a/packages/graphql-mesh/utils/logger.ts b/packages/graphql-mesh/utils/logger.ts new file mode 100644 index 0000000..c9b45ce --- /dev/null +++ b/packages/graphql-mesh/utils/logger.ts @@ -0,0 +1,523 @@ +/** + * Logger : Class static + * use to log event with much useful context information and some useful data + * + * TODO : split into specifiques loggers and core logger + */ +import { v4 as uuidv4 } from 'uuid'; + + +export class Logger { + + private static level: string = process.env["LogLevel"] || "INFO" + private static format: string = process.env["LogFormat"] || "HUMAN" // set JSON_STRING to have one line json string by log + private static bodyMaxLogSize = process.env["LogBodyMaxSize"] ? parseInt(process.env["LogBodyMaxSize"]) : 100 + private static maxSkackLogSize = process.env["LogStackTraceMaxSize"] ? parseInt(process.env["LogStackTraceMaxSize"]) : 100 + private static trackerOnly: boolean = process.env["LogTrackerHeadersOnly"] ? process.env["LogTrackerHeadersOnly"] == 'true' : false + private static envLog: string = process.env["LogEnvFieldToAdd"] // use to add env extra field in json log ex "app=graphql,env.name=production,env.site=Paris" + private static localDateCountry: string = process.env["LogLocalDateCountry"] || "fr-FR" + private static logHeaders = defineLogHeaders(process.env["LogHeaders"] || "headers.host=host,headers.origin,headers.user-agent=user-agent,headers.content-length=content-length,headers.authorization=authorization") + private static logContextHeaders = defineLogHeaders(process.env["LogContextHeaders"] || "ctx.requestId=x-request-id") + private static skipHealthCheck = process.env["skipHealthCheck"] ? process.env["skipHealthCheck"] == "true" : true + private static healthCheckHeaderName = process.env["healthCheckHeazder"] || "x-internal" + private static HealthCheckHeaderValue = process.env["healthCheckHeaderValue"] || "health-check" + private static MaxErrorStackSize = process.env["MaxErrorStackSize"] ? parseInt(process.env["MaxErrorStackSize"]) : 200 + constructor() { + + } + /** + * Core logger with Human format or machine in one line json string format + */ + private static log(level: string, typeEv: string, message: string, headers = null, onlyContextHeader: boolean, info: any = null, err = null) { + + const date = new Date(); + const timestamp = date.getTime(); + + // if machine friendly json string is required + if (Logger.format == 'JSON_STRING') { + const log = { + events: [{ + idEv: uuidv4(), + date: date.toLocaleString(this.localDateCountry), + level: level, + typeEv: typeEv, + timestamp: timestamp, + message: message + }] + } + try { + // add http headers + if (headers) { + this.addHeadersToContextLog(log, headers) + if (!onlyContextHeader) { + this.addHeadersToLog(log.events[0], headers) + } + } + // add extra information to log event + if (info) { + for (const key in info) { + log.events[0][key] = info[key] + } + } + // if exception error add message and an extract of stack trace + if (err) { + log.events[0]['exeception'] = {} + //console.log("Exception:", err) + + log.events[0]['exeception'] = { + message: err.message, + stack: err.stack?.substring(0, this.MaxErrorStackSize) + " ..." + } + + } + // if define add extra env field + if (this.envLog) { + addEnvFieldLog(this.envLog, log) + } + + } catch (e) { + log['error'] = "error whileg getting log event contextual info :" + e.message + } + console.log(JSON.stringify(log)) + } else { + if (info) { + if (err) { + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, info, err) + } else + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, info) + + } else { + if (err) { + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, err) + } else + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message) + + } + } + } + + public static error(typeEv: string, source: string, message: string, headers = null, info = null, e = null) { + if (Logger.level == 'ERROR' || Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') { + Logger.log('ERROR', typeEv, source + ":" + message, headers, false, info, e) + } + } + public static warn(typeEv: string, source: string, message: string, headers: any = null, info: any = null, e = null) { + if (Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') { + Logger.log('INFO', typeEv, source + ":" + message, headers, false, info, e) + } + } + + public static info(typeEv: string, source: string, message: string, headers: any = null, info: any = null, e = null) { + if (Logger.level == 'INFO' || Logger.level == 'DEBUG') { + Logger.log('INFO', typeEv, source + ":" + message, headers, true, info, e) + } + } + public static debug(typeEv: string, source: string, message: string, headers: any = null, info: any = null, e = null) { + if (Logger.level == 'DEBUG') { + Logger.log('DEBUG', typeEv, source + ":" + message, headers, true, info, e) + } + } + + + /** + * log the on parse event + */ + public static onParse(headers: any) { + try { + if (this.isEventToLog(headers)) { + Logger.log('INFO', "ON-PARSE", "Request", headers, true) + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'onParse logger', 'error during log generation', null, e) + + } + } + + /** + * log the on end exec done event + */ + public static endExec(headers: any, result: any, duration: number, resultLogInfo: any) { + try { + if (this.isEventToLog(headers)) { + const info = { + result: InfoResult(result, this.maxSkackLogSize, resultLogInfo), + duration: duration, + } + Logger.log('INFO', "endExecDone", "Request", headers, true, info) + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'endExec logger', 'error during log generation', headers, null, e) + } + } + + /** + * log the on result process event + */ + public static onResultProcess(request: any, result: any, duration: number, resultLogInfo: any) { + const headers = request['headers'] + try { + + if (this.isEventToLog(headers)) { + + const info = { + result: InfoResult(result, this.maxSkackLogSize, resultLogInfo), + duration: duration + } + Logger.log('INFO', "onResultProcess", "Result", headers, true, info) + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', headers, null, e) + } + } + + /** + * log the on request parse done event + */ + public static onRequestParseDone(headers: any, query: any, operation: string, variables: any, duration: number) { + if (this.isEventToLog(headers)) { + + const info = { + operation: operation, + query: query, + variables: variables, + parsingDuration: duration + } + Logger.log('INFO', "requestParseDone", "requestParse", headers, true, info) + } + } + /** + * log the on reponse event http respnse return + */ + public static onResponse(request: any, response: any, logResponseLevellevel: string) { + const headers = request['headers'] + try { + if (this.isEventToLog(headers)) { + // calculate duration from request timestamp + const requestTimestampString: string = headers.get('requesttimestamp') + let requestTimestamp: number; + if (requestTimestampString) { + requestTimestamp = parseInt(requestTimestampString) + } + const responseTimestamp = new Date().getTime(); + const info = { + request: { + url: request.url, + method: request.method + }, + response: { + status: response.status, + contentLength: response.contentLength, + }, + httpStatus: response.status, + duration: responseTimestamp - requestTimestamp + } + if (logResponseLevellevel != 'low') { + info.response['bodyExtract'] = extractBody(response.bodyInit, this.bodyMaxLogSize) + } + + Logger.log('INFO', "onResponse", responseSummary(response.bodyInit), headers, true, info) + } + } + catch (e) { + Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', headers, null, e) + } + } + + public static introspection(headers, query) { + try { + const info = { + query: query + } + Logger.warn('WARN_INTROSPECTION', "introspection", "introspection query", headers, info) + } + catch (e) { + Logger.error('LOGGER_ERROR', 'introspection logger', 'error during log generation', null, null, e) + } + } + + public static denyIntrospection(headers, info, message,) { + try { + Logger.warn('DENY_INTROSPECTION', info, message, headers) + } + catch (e) { + Logger.error('LOGGER_ERROR', 'denyIntrospection logger', 'error during log generation', headers, null, e) + } + } + + public static allowIntrospection(headers, info, message,) { + try { + Logger.warn('ALLOW_INTROSPECTION', info, message, headers) + } + catch (e) { + Logger.error('LOGGER_ERROR', 'denyIntrospection logger', 'error during log generation', headers, null, e) + } + } + public static onRequest(request: any) { + const headersInit = request['headers']['headersInit'] + const headers = new Map + + for (const header in headersInit) { + headers.set(header, headersInit[header]) + } + try { + if (this.isEventToLog(headers)) { + const info = { + url: request.url, + method: request.method, + //body: request.body + } + Logger.log('INFO', "onRequest", "request incomming", headers, false, info) + } + } + catch (e) { + Logger.error('LOGGER_ERROR', 'onRequest logger', 'error during log generation', headers, null, e) + } + } + + public static onFetch(request: any, url: string, httpStatus: string, duration: number, fetchInfo: any) { + try { + const headers = request['headers'] + const info = { + fetch: fetch, + duration: duration, + httpStatus: httpStatus, + url: url + } + Logger.log('INFO', "onFetch", "fetch", headers, true, info) + } catch (e) { + Logger.error('LOGGER_ERROR', 'onFetch logger', 'error during log generation', null, e) + } + } + + public static graphqlQuery(headers: any, params: any) { + try { + if (this.isEventToLog(headers)) { + const regex = / /gi; + const queryTolog = { + query: params['query'].replace(regex, ""), + operationName: params['operationName'], + variables: params['variables'] + } + Logger.log('INFO', "graphqlQuery", "GraphQL Query", headers, true, queryTolog) + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'graphql query logger', 'error during log generation', headers, null, e) + } + } + private static isEventToLog(headers): boolean { + if (this.skipHealthCheck && headers) { + if (headers.get(this.healthCheckHeaderName) == this.HealthCheckHeaderValue) { + return false + } + } + return true + } + private static addHeadersToContextLog(log: any, headers: any) { + + try { + let headerMap = null + if (headers["_map"]) { + headerMap = headers["_map"] + } else { + headerMap = headers + } + + if (headerMap && headerMap.get) { + if (this.logContextHeaders) { + for (const contextKey in this.logContextHeaders) { + const contextHeader = this.logContextHeaders[contextKey] + if (headerMap.get(contextHeader.header)) { + + if (contextHeader.header.toLowerCase() == "authorization") { + addLog(log, contextHeader.name, mask(headerMap.get('authorization'))) + } else { + addLog(log, contextHeader.name, headerMap.get(contextHeader.header)) + } + } + } + } + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'onheadersToLog', 'error during headers log generation', headers, null, e) + } + return log + } + + private static addHeadersToLog(log: any, headers: any) { + let headerMap = null + if (headers["_map"]) { + headerMap = headers["_map"] + } else { + headerMap = headers + } + try { + if (headerMap) { + if (this.logHeaders) { + + for (const logHeaderKey in this.logHeaders) { + const logHeader = this.logHeaders[logHeaderKey] + if (headerMap.get(logHeader.header)) { + if (logHeader.header.toLowerCase() == "authorization") { + addLog(log, logHeader.name, mask(headerMap.get('authorization'))) + } else { + addLog(log, logHeader.name, headerMap.get(logHeader.header)) + } + } + } + } + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'onheadersToLog', 'error during headers log generation', headers, null, e) + } + return log + } +} + +function mask(stringToMask: string) { + if (stringToMask) { + return "*******************" + stringToMask.substring(22) + } + return null; +} + +function InfoResult(result: any, maxStackLogSize: number, resultLogInfoLevel: string) { + let resultInfo = {} + let keys = null + let nbKeys = 0 + const maxKeys = 1 + const nbErrorsMaxToLog = 3 + if (result['errors']) { + resultInfo['nbErrors'] = result['errors'].length + if (resultLogInfoLevel != 'low') { + let nbErrors = 0 + resultInfo['errors'] = [] + for (const errorKey in result['errors']) { + const error = result['errors'][errorKey] + let logError = {} + if (error['message']) { + if (nbErrorsMaxToLog > nbErrors) { + logError['message'] = error['message'] + } + } + if (resultLogInfoLevel == 'hight') { + if (error['path']) { + logError['path'] = error['path'] + } + // no stack trace and extension in low trace level + if (error['stack']) { + logError['stack'] = error['stack'].substring(0, maxStackLogSize) + } + if (error['extensions']) { + logError['extensions'] = error['extensions'] + } + } + resultInfo['errors'].push(logError) + } + } + } else { + resultInfo['nbErrors'] = 0 + } + if (result['data']) { + let keys = null + for (const key in result['data']) { + if (result['data'].hasOwnProperty(key)) { + if (keys == null) { + keys = key + } else { + if (nbKeys < maxKeys) { + keys = keys + ',' + key + } + } + nbKeys = nbKeys + 1 + } + } + resultInfo['dataFields'] = keys + } + return resultInfo +} +function extractBody(body: String, bodyMaxLogSize: number) { + if (body != null) { + return body.substring(0, bodyMaxLogSize) + " ... " + } else { + return "" + } +} +function responseSummary(body: String) { + if (body != null) { + return "response :" + body.substring(0, 100) + " ... " + } else { + return "response : empty" + } +} + + +/** + * Use to add some environment fields to log, like env.name, app.name ... + * ex : logEnvInfoField : string = "app=graphql,env.name=production,env.site=Paris" + * -> log={ + * app: "graphql", + * env: + * name: "production", + * site: "Paris", + * ... // logs field + */ +function addEnvFieldLog(logEnvInfoField: string, log: any) { + + // split fields list witth dot serator + const fields: string[] = logEnvInfoField.split(",") + for (let idxField = 0; idxField < fields.length; idxField++) { + // split key, value + const kvField: string[] = fields[idxField].trim().split("=") + // split key in objects tree hierarchy + const levelField = kvField[0].trim().split(".") + let current = log + // create new object if does not exist for each level and set value for leaf level + for (let idxLevel = 0; idxLevel < levelField.length; idxLevel++) { + if (current[levelField[idxLevel]]) { + current = current[levelField[idxLevel]] + } else { + if (idxLevel < levelField.length - 1) { + current = current[levelField[idxLevel]] = {} + } else { + current[levelField[idxLevel]] = kvField[1] + } + } + } + } +} +function addLog(log: any, key: string, value: any) { + + const levelField = key.split(".") + let current = log + // create new object if does not exist for each level and set value for leaf level + for (let idxLevel = 0; idxLevel < levelField.length; idxLevel++) { + if (current[levelField[idxLevel]]) { + current = current[levelField[idxLevel]] + } else { + if (idxLevel < levelField.length - 1) { + current = current[levelField[idxLevel]] = {} + } else { + current[levelField[idxLevel]] = value + } + } + } +} +function defineLogHeaders(logHeaders: string) { + const headersList = [] + if (logHeaders) { + const headers = logHeaders.split(',') + + for (const headerKey in headers) { + const header = headers[headerKey] + const headerInfo = header.split('=') + headersList.push({ name: headerInfo[0], header: headerInfo[1] ? headerInfo[1] : headerInfo[0] }) + } + } + return headersList +} + + + + + + diff --git a/packages/graphql-mesh/utils/parseYamlConfig.ts b/packages/graphql-mesh/utils/parseYamlConfig.ts index d61e4ba..f4e606f 100644 --- a/packages/graphql-mesh/utils/parseYamlConfig.ts +++ b/packages/graphql-mesh/utils/parseYamlConfig.ts @@ -1,46 +1,81 @@ import { YamlConfig } from '@graphql-mesh/types' -import { DefaultLogger } from '@graphql-mesh/utils' +//import { DefaultLogger } from '@graphql-mesh/utils' import { load } from 'js-yaml' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' +import { Logger } from '../utils/logger' -const logger = new DefaultLogger() + +//const logger = new DefaultLogger() // Load the config.yaml file export const getConfig = (): YamlConfig.Config => { - logger.info('Loading config file') - let config: YamlConfig.Config + const configFile = process.env['ConfigFile'] || 'config.yaml' + Logger.info('CONFIG', 'getConfig', 'Loading config file ' + configFile) + let config: YamlConfig.Config + + try { + const configPath = resolve(configFile) + config = load(readFileSync(configPath, { encoding: 'utf-8' })) + } catch (e) { + Logger.error('CONFIG', 'getConfig', "Failed loading config " + configFile, e) + throw new Error('FAiles laoding config file ' + configFile, e) + } + + if (!config) { + Logger.error('CONFIG', 'getConfig', "No config loaded from " + configFile) + throw new Error('No configuration loaded from ' + configFile) + } - try { - const configPath = resolve('./config.yaml') - config = load(readFileSync(configPath, { encoding: 'utf-8' })) - } catch (e) {} + // if sources config is on a separate file define by SourcesConfigFile env var + const sourcesConfigFile = process.env['SourcesConfigFile'] + if (sourcesConfigFile) { + let sourcesConfig: YamlConfig.Config + Logger.info('CONFIG', 'getConfig', 'Loading sources config file ' + sourcesConfigFile) - if (!config) { - throw new Error('No config file found') - } + try { + const sourcesConfigPath = resolve(sourcesConfigFile) + let file = readFileSync(sourcesConfigPath, { encoding: 'utf-8' }) + sourcesConfig = load(readFileSync(sourcesConfigPath, { encoding: 'utf-8' })) + Logger.debug('CONFIG', 'getConfig', 'sources loading from ' + sourcesConfigPath , null, file) + } catch (e) { + Logger.error('CONFIG', 'getConfig', "Failed loading sources Config " + sourcesConfigFile, null, null, e) + throw new Error('FAiles laoding sources config file ' + sourcesConfigFile, e) + } + if (sourcesConfig.sources) { + config['sources'] = sourcesConfig.sources + Logger.info('CONFIG', 'getConfig', 'sources Config file loaded successfully with ' + sourcesConfig.sources.length + " sources") + } else { + Logger.error('CONFIG', 'getConfig', 'sources Config file loaded successfully but without sources') + } + - logger.info('Config file loaded successfully') - return config + } else { + if (!config.sources) { + Logger.error('CONFIG', 'getConfig', "No source defined in configuration file " + configFile) + throw new Error('No source defioned in configuration file ' + configFile) + } + } + return config } // Get the endpoint of a specific openapi source export const getSourceOpenapiEnpoint = ( - source: string, - config: YamlConfig.Config + source: string, + config: YamlConfig.Config ): string | undefined => { - const data = config.sources?.find((item) => source.includes(item.name)) - return data?.handler.openapi?.endpoint + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.handler.openapi?.endpoint } // Get the name of a specific source export const getSourceName = (source: string, config: YamlConfig.Config): string => { - const data = config.sources?.find((item) => source.includes(item.name)) - return data?.name + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.name } // Get the list of transforms of a specific source export const getSourceTransforms = (source: string, config: YamlConfig.Config) => { - const data = config.sources?.find((item) => source.includes(item.name)) - return data?.transforms + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.transforms }