@graphoria/server 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/src/analyzeQuery/depthLimit.d.ts +1 -13
- package/dist/src/analyzeQuery/depthLimit.d.ts.map +1 -1
- package/dist/src/authentication/capabilities.d.ts +1 -1
- package/dist/src/authentication/capabilities.d.ts.map +1 -1
- package/dist/src/authentication/tokenRepository.d.ts.map +1 -1
- package/dist/src/config/types/db.d.ts +25 -0
- package/dist/src/config/types/db.d.ts.map +1 -1
- package/dist/src/configuration/getSchemas/index.d.ts +40 -0
- package/dist/src/configuration/getSchemas/index.d.ts.map +1 -1
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts +20 -0
- package/dist/src/configuration/getSchemas/mergeEntities/index.d.ts.map +1 -1
- package/dist/src/configuration/gql/handleGraphQLRequestFactory.d.ts.map +1 -1
- package/dist/src/configuration/index.d.ts +30 -0
- package/dist/src/configuration/index.d.ts.map +1 -1
- package/dist/src/console/api.d.ts.map +1 -1
- package/dist/src/cron/index.d.ts.map +1 -1
- package/dist/src/databases/common.d.ts +20 -6
- package/dist/src/databases/common.d.ts.map +1 -1
- package/dist/src/databases/core/executor.d.ts +4 -6
- package/dist/src/databases/core/executor.d.ts.map +1 -1
- package/dist/src/databases/core/function-mapping.d.ts +1 -2
- package/dist/src/databases/core/function-mapping.d.ts.map +1 -1
- package/dist/src/databases/core/query-builder.d.ts +5 -0
- package/dist/src/databases/core/query-builder.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/getStructure.d.ts +5 -0
- package/dist/src/databases/engines/mssql/getStructure.d.ts.map +1 -1
- package/dist/src/databases/engines/mssql/query/index.d.ts +2 -2
- package/dist/src/databases/engines/mssql/query/index.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/getStructure.d.ts +5 -0
- package/dist/src/databases/engines/mysql/getStructure.d.ts.map +1 -1
- package/dist/src/databases/engines/mysql/query/index.d.ts +2 -2
- package/dist/src/databases/engines/mysql/query/index.d.ts.map +1 -1
- package/dist/src/databases/engines/postgresql/getStructure.d.ts +5 -0
- package/dist/src/databases/engines/postgresql/getStructure.d.ts.map +1 -1
- package/dist/src/databases/engines/postgresql/query/index.d.ts +2 -2
- package/dist/src/databases/engines/postgresql/query/index.d.ts.map +1 -1
- package/dist/src/databases/high-level-operations.d.ts +5 -0
- package/dist/src/databases/high-level-operations.d.ts.map +1 -1
- package/dist/src/databases/metadata/structure.d.ts +5 -0
- package/dist/src/databases/metadata/structure.d.ts.map +1 -1
- package/dist/src/databases/transformers/data-transformers.d.ts +30 -0
- package/dist/src/databases/transformers/data-transformers.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logging/slowQuery.d.ts +29 -0
- package/dist/src/logging/slowQuery.d.ts.map +1 -0
- package/dist/src/logging/slowQuery.test.d.ts +2 -0
- package/dist/src/logging/slowQuery.test.d.ts.map +1 -0
- package/dist/src/observability/health.d.ts +23 -0
- package/dist/src/observability/health.d.ts.map +1 -0
- package/dist/src/observability/health.test.d.ts +2 -0
- package/dist/src/observability/health.test.d.ts.map +1 -0
- package/dist/src/observability/httpMetrics.d.ts +7 -0
- package/dist/src/observability/httpMetrics.d.ts.map +1 -0
- package/dist/src/observability/httpMetrics.test.d.ts +2 -0
- package/dist/src/observability/httpMetrics.test.d.ts.map +1 -0
- package/dist/src/observability/httpTracing.d.ts +11 -0
- package/dist/src/observability/httpTracing.d.ts.map +1 -0
- package/dist/src/observability/httpTracing.test.d.ts +2 -0
- package/dist/src/observability/httpTracing.test.d.ts.map +1 -0
- package/dist/src/observability/metrics.d.ts +81 -0
- package/dist/src/observability/metrics.d.ts.map +1 -0
- package/dist/src/observability/metrics.perf.test.d.ts +2 -0
- package/dist/src/observability/metrics.perf.test.d.ts.map +1 -0
- package/dist/src/observability/metrics.test.d.ts +2 -0
- package/dist/src/observability/metrics.test.d.ts.map +1 -0
- package/dist/src/observability/metricsRoute.d.ts +19 -0
- package/dist/src/observability/metricsRoute.d.ts.map +1 -0
- package/dist/src/observability/metricsRoute.test.d.ts +2 -0
- package/dist/src/observability/metricsRoute.test.d.ts.map +1 -0
- package/dist/src/observability/tracing.d.ts +92 -0
- package/dist/src/observability/tracing.d.ts.map +1 -0
- package/dist/src/observability/tracing.perf.test.d.ts +2 -0
- package/dist/src/observability/tracing.perf.test.d.ts.map +1 -0
- package/dist/src/observability/tracing.test.d.ts +2 -0
- package/dist/src/observability/tracing.test.d.ts.map +1 -0
- package/dist/src/queues/kafka.d.ts +6 -0
- package/dist/src/queues/kafka.d.ts.map +1 -1
- package/dist/src/queues/kafka.test.d.ts +2 -0
- package/dist/src/queues/kafka.test.d.ts.map +1 -0
- package/dist/src/queues/rabbitmq.d.ts.map +1 -1
- package/dist/src/remoteREST/proxy.d.ts.map +1 -1
- package/dist/src/remoteSchemas/proxy.d.ts.map +1 -1
- package/dist/src/singletons/cache/redisClient.d.ts +2 -2
- package/dist/src/singletons/cache/redisClient.d.ts.map +1 -1
- package/dist/src/singletons/databases.d.ts +1 -0
- package/dist/src/singletons/databases.d.ts.map +1 -1
- package/dist/src/singletons/env.d.ts +29 -0
- package/dist/src/singletons/env.d.ts.map +1 -1
- package/dist/src/singletons/queues.d.ts +1 -0
- package/dist/src/singletons/queues.d.ts.map +1 -1
- package/dist/src/subscriptions/strategies/database.d.ts.map +1 -1
- package/dist/src/subscriptions/utils/polling.d.ts +1 -0
- package/dist/src/subscriptions/utils/polling.d.ts.map +1 -1
- package/dist/src/subscriptions/utils/polling.test.d.ts +2 -0
- package/dist/src/subscriptions/utils/polling.test.d.ts.map +1 -0
- package/dist/src/types/env.d.ts +89 -0
- package/dist/src/types/env.d.ts.map +1 -1
- package/dist/src/types/zod/configuration.d.ts +25 -0
- package/dist/src/types/zod/configuration.d.ts.map +1 -1
- package/dist/src/types/zod/db.d.ts +85 -0
- package/dist/src/types/zod/db.d.ts.map +1 -1
- package/dist/src/utils/rateLimit.d.ts.map +1 -1
- package/dist/src/utils/redis.d.ts +17 -0
- package/dist/src/utils/redis.d.ts.map +1 -0
- package/dist/src/utils/redis.test.d.ts +2 -0
- package/dist/src/utils/redis.test.d.ts.map +1 -0
- package/dist/src/utils/responses.d.ts +3 -0
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -1
- package/playgrounds/console/index.html +8 -8
- package/playgrounds/graphiql/index.html +114 -114
- package/playgrounds/scalar/index.html +1 -1
- package/src/analyzeQuery/depthLimit.ts +15 -5
- package/src/authentication/capabilities.ts +3 -1
- package/src/authentication/tokenRepository.ts +2 -3
- package/src/config/types/db.ts +16 -2
- package/src/configuration/gql/handleGraphQLRequestFactory.ts +233 -152
- package/src/console/api.ts +1 -6
- package/src/cron/index.ts +59 -36
- package/src/databases/common.ts +109 -10
- package/src/databases/core/executor.ts +96 -18
- package/src/databases/core/function-mapping.ts +1 -1
- package/src/databases/engines/mssql/query/index.ts +4 -1
- package/src/databases/engines/mysql/query/index.ts +5 -2
- package/src/databases/engines/postgresql/query/index.ts +5 -2
- package/src/databases/high-level-operations.ts +44 -2
- package/src/index.ts +147 -62
- package/src/logging/slowQuery.ts +59 -0
- package/src/observability/health.ts +67 -0
- package/src/observability/httpMetrics.ts +37 -0
- package/src/observability/httpTracing.ts +48 -0
- package/src/observability/metrics.ts +243 -0
- package/src/observability/metricsRoute.ts +33 -0
- package/src/observability/tracing.ts +421 -0
- package/src/queues/kafka.ts +45 -2
- package/src/queues/rabbitmq.ts +37 -1
- package/src/remoteREST/proxy.ts +17 -0
- package/src/remoteSchemas/proxy.ts +17 -0
- package/src/singletons/cache/redisClient.ts +4 -5
- package/src/singletons/databases.ts +5 -0
- package/src/singletons/queues.ts +9 -3
- package/src/subscriptions/strategies/database.ts +2 -0
- package/src/subscriptions/utils/polling.ts +80 -21
- package/src/types/env.ts +27 -0
- package/src/utils/rateLimit.ts +6 -1
- package/src/utils/redis.ts +71 -0
- package/src/utils/responses.ts +6 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0NiIgZmlsbD0ibm9uZSIgdmlld0JveD0iMCAwIDQ4IDQ2Ij48cGF0aCBmaWxsPSIjODYzYmZmIiBkPSJNMjUuOTQ2IDQ0LjkzOGMtLjY2NC44NDUtMi4wMjEuMzc1LTIuMDIxLS42OThWMzMuOTM3YTIuMjYgMi4yNiAwIDAgMC0yLjI2Mi0yLjI2MkgxMC4yODdjLS45MiAwLTEuNDU2LTEuMDQtLjkyLTEuNzg4bDcuNDgtMTAuNDcxYzEuMDctMS40OTcgMC0zLjU3OC0xLjg0Mi0zLjU3OEgxLjIzN2MtLjkyIDAtMS40NTYtMS4wNC0uOTItMS43ODhMMTAuMDEzLjQ3NGMuMjE0LS4yOTcuNTU2LS40NzQuOTItLjQ3NGgyOC44OTRjLjkyIDAgMS40NTYgMS4wNC45MiAxLjc4OGwtNy40OCAxMC40NzFjLTEuMDcgMS40OTggMCAzLjU3OSAxLjg0MiAzLjU3OWgxMS4zNzdjLjk0MyAwIDEuNDczIDEuMDg4Ljg5IDEuODNMMjUuOTQ3IDQ0Ljk0eiIgc3R5bGU9ImZpbGw6Izg2M2JmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjUyNTIgLjIzIDEpO2ZpbGwtb3BhY2l0eToxIi8+PG1hc2sgaWQ9ImEiIHdpZHRoPSI0OCIgaGVpZ2h0PSI0NiIgeD0iMCIgeT0iMCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgc3R5bGU9Im1hc2stdHlwZTphbHBoYSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTI1Ljg0MiA0NC45MzhjLS42NjQuODQ0LTIuMDIxLjM3NS0yLjAyMS0uNjk4VjMzLjkzN2EyLjI2IDIuMjYgMCAwIDAtMi4yNjItMi4yNjJIMTAuMTgzYy0uOTIgMC0xLjQ1Ni0xLjA0LS45Mi0xLjc4OGw3LjQ4LTEwLjQ3MWMxLjA3LTEuNDk4IDAtMy41NzktMS44NDItMy41NzlIMS4xMzNjLS45MiAwLTEuNDU2LTEuMDQtLjkyLTEuNzg3TDkuOTEuNDczYy4yMTQtLjI5Ny41NTYtLjQ3NC45Mi0uNDc0aDI4Ljg5NGMuOTIgMCAxLjQ1NiAxLjA0LjkyIDEuNzg4bC03LjQ4IDEwLjQ3MWMtMS4wNyAxLjQ5OCAwIDMuNTc4IDEuODQyIDMuNTc4aDExLjM3N2MuOTQzIDAgMS40NzMgMS4wODguODkgMS44MzJMMjUuODQzIDQ0Ljk0eiIgc3R5bGU9ImZpbGw6IzAwMDtmaWxsLW9wYWNpdHk6MSIvPjwvbWFzaz48ZyBtYXNrPSJ1cmwoI2EpIj48ZyBmaWx0ZXI9InVybCgjYikiPjxlbGxpcHNlIGN4PSI1LjUwOCIgY3k9IjE0LjcwNCIgZmlsbD0iI2VkZTZmZiIgcng9IjUuNTA4IiByeT0iMTQuNzA0IiBzdHlsZT0iZmlsbDojZWRlNmZmO2ZpbGw6Y29sb3IoZGlzcGxheS1wMyAuOTI3NSAuOTAzMyAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwMzI0IDEgMSAtLjAwMzI0IC00LjQ3IDMxLjUxNikiLz48L2c+PGcgZmlsdGVyPSJ1cmwoI2MpIj48ZWxsaXBzZSBjeD0iMTAuMzk5IiBjeT0iMjkuODUxIiBmaWxsPSIjZWRlNmZmIiByeD0iMTAuMzk5IiByeT0iMjkuODUxIiBzdHlsZT0iZmlsbDojZWRlNmZmO2ZpbGw6Y29sb3IoZGlzcGxheS1wMyAuOTI3NSAuOTAzMyAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJtYXRyaXgoLjAwMzI0IDEgMSAtLjAwMzI0IC0zOS4zMjggNy44ODMpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNkKSI+PGVsbGlwc2UgY3g9IjUuNTA4IiBjeT0iMzAuNDg3IiBmaWxsPSIjN2UxNGZmIiByeD0iNS41MDgiIHJ5PSIzMC40ODciIHN0eWxlPSJmaWxsOiM3ZTE0ZmY7ZmlsbDpjb2xvcihkaXNwbGF5LXAzIC40OTIyIC4wNzY3IDEpO2ZpbGwtb3BhY2l0eToxIiB0cmFuc2Zvcm09InJvdGF0ZSg4OS44MTQgLTI1LjkxMyAtMTQuNjM5KXNjYWxlKDEgLTEpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNlKSI+PGVsbGlwc2UgY3g9IjUuNTA4IiBjeT0iMzAuNTk5IiBmaWxsPSIjN2UxNGZmIiByeD0iNS41MDgiIHJ5PSIzMC41OTkiIHN0eWxlPSJmaWxsOiM3ZTE0ZmY7ZmlsbDpjb2xvcihkaXNwbGF5LXAzIC40OTIyIC4wNzY3IDEpO2ZpbGwtb3BhY2l0eToxIiB0cmFuc2Zvcm09InJvdGF0ZSg4OS44MTQgLTMyLjY0NCAtMy4zMzQpc2NhbGUoMSAtMSkiLz48L2c+PGcgZmlsdGVyPSJ1cmwoI2YpIj48ZWxsaXBzZSBjeD0iNS41MDgiIGN5PSIzMC41OTkiIGZpbGw9IiM3ZTE0ZmYiIHJ4PSI1LjUwOCIgcnk9IjMwLjU5OSIgc3R5bGU9ImZpbGw6IzdlMTRmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjQ5MjIgLjA3NjcgMSk7ZmlsbC1vcGFjaXR5OjEiIHRyYW5zZm9ybT0ibWF0cml4KC4wMDMyNCAxIDEgLS4wMDMyNCAtMzQuMzQgMzAuNDcpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNnKSI+PGVsbGlwc2UgY3g9IjE0LjA3MiIgY3k9IjIyLjA3OCIgZmlsbD0iI2VkZTZmZiIgcng9IjE0LjA3MiIgcnk9IjIyLjA3OCIgc3R5bGU9ImZpbGw6I2VkZTZmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjkyNzUgLjkwMzMgMSk7ZmlsbC1vcGFjaXR5OjEiIHRyYW5zZm9ybT0icm90YXRlKDkzLjM1IDI0LjUwNiA0OC40OTMpc2NhbGUoLTEgMSkiLz48L2c+PGcgZmlsdGVyPSJ1cmwoI2gpIj48ZWxsaXBzZSBjeD0iMy40NyIgY3k9IjIxLjUwMSIgZmlsbD0iIzdlMTRmZiIgcng9IjMuNDciIHJ5PSIyMS41MDEiIHN0eWxlPSJmaWxsOiM3ZTE0ZmY7ZmlsbDpjb2xvcihkaXNwbGF5LXAzIC40OTIyIC4wNzY3IDEpO2ZpbGwtb3BhY2l0eToxIiB0cmFuc2Zvcm09InJvdGF0ZSg4OS4wMDkgMjguNzA4IDQ3LjU5KXNjYWxlKC0xIDEpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNpKSI+PGVsbGlwc2UgY3g9IjMuNDciIGN5PSIyMS41MDEiIGZpbGw9IiM3ZTE0ZmYiIHJ4PSIzLjQ3IiByeT0iMjEuNTAxIiBzdHlsZT0iZmlsbDojN2UxNGZmO2ZpbGw6Y29sb3IoZGlzcGxheS1wMyAuNDkyMiAuMDc2NyAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJyb3RhdGUoODkuMDA5IDI4LjcwOCA0Ny41OSlzY2FsZSgtMSAxKSIvPjwvZz48ZyBmaWx0ZXI9InVybCgjaikiPjxlbGxpcHNlIGN4PSIuMzg3IiBjeT0iOC45NzIiIGZpbGw9IiM3ZTE0ZmYiIHJ4PSI0LjQwNyIgcnk9IjI5LjEwOCIgc3R5bGU9ImZpbGw6IzdlMTRmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjQ5MjIgLjA3NjcgMSk7ZmlsbC1vcGFjaXR5OjEiIHRyYW5zZm9ybT0icm90YXRlKDM5LjUxIC4zODcgOC45NzIpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNrKSI+PGVsbGlwc2UgY3g9IjQ3LjUyMyIgY3k9Ii02LjA5MiIgZmlsbD0iIzdlMTRmZiIgcng9IjQuNDA3IiByeT0iMjkuMTA4IiBzdHlsZT0iZmlsbDojN2UxNGZmO2ZpbGw6Y29sb3IoZGlzcGxheS1wMyAuNDkyMiAuMDc2NyAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJyb3RhdGUoMzcuODkyIDQ3LjUyMyAtNi4wOTIpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNsKSI+PGVsbGlwc2UgY3g9IjQxLjQxMiIgY3k9IjYuMzMzIiBmaWxsPSIjNDdiZmZmIiByeD0iNS45NzEiIHJ5PSI5LjY2NSIgc3R5bGU9ImZpbGw6IzQ3YmZmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjI3OTkgLjc0OCAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJyb3RhdGUoMzcuODkyIDQxLjQxMiA2LjMzMykiLz48L2c+PGcgZmlsdGVyPSJ1cmwoI20pIj48ZWxsaXBzZSBjeD0iLTEuODc5IiBjeT0iMzguMzMyIiBmaWxsPSIjN2UxNGZmIiByeD0iNC40MDciIHJ5PSIyOS4xMDgiIHN0eWxlPSJmaWxsOiM3ZTE0ZmY7ZmlsbDpjb2xvcihkaXNwbGF5LXAzIC40OTIyIC4wNzY3IDEpO2ZpbGwtb3BhY2l0eToxIiB0cmFuc2Zvcm09InJvdGF0ZSgzNy44OTIgLTEuODggMzguMzMyKSIvPjwvZz48ZyBmaWx0ZXI9InVybCgjbikiPjxlbGxpcHNlIGN4PSItMS44NzkiIGN5PSIzOC4zMzIiIGZpbGw9IiM3ZTE0ZmYiIHJ4PSI0LjQwNyIgcnk9IjI5LjEwOCIgc3R5bGU9ImZpbGw6IzdlMTRmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjQ5MjIgLjA3NjcgMSk7ZmlsbC1vcGFjaXR5OjEiIHRyYW5zZm9ybT0icm90YXRlKDM3Ljg5MiAtMS44OCAzOC4zMzIpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNvKSI+PGVsbGlwc2UgY3g9IjM1LjY1MSIgY3k9IjI5LjkwNyIgZmlsbD0iIzdlMTRmZiIgcng9IjQuNDA3IiByeT0iMjkuMTA4IiBzdHlsZT0iZmlsbDojN2UxNGZmO2ZpbGw6Y29sb3IoZGlzcGxheS1wMyAuNDkyMiAuMDc2NyAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJyb3RhdGUoMzcuODkyIDM1LjY1MSAyOS45MDcpIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNwKSI+PGVsbGlwc2UgY3g9IjM4LjQxOCIgY3k9IjMyLjQiIGZpbGw9IiM0N2JmZmYiIHJ4PSI1Ljk3MSIgcnk9IjE1LjI5NyIgc3R5bGU9ImZpbGw6IzQ3YmZmZjtmaWxsOmNvbG9yKGRpc3BsYXktcDMgLjI3OTkgLjc0OCAxKTtmaWxsLW9wYWNpdHk6MSIgdHJhbnNmb3JtPSJyb3RhdGUoMzcuODkyIDM4LjQxOCAzMi40KSIvPjwvZz48L2c+PGRlZnM+PGZpbHRlciBpZD0iYiIgd2lkdGg9IjYwLjA0NSIgaGVpZ2h0PSI0MS42NTQiIHg9Ii0xOS43NyIgeT0iMTYuMTQ5IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzIwMDJfMTcxNTgiIHN0ZERldmlhdGlvbj0iNy42NTkiLz48L2ZpbHRlcj48ZmlsdGVyIGlkPSJjIiB3aWR0aD0iOTAuMzQiIGhlaWdodD0iNTEuNDM3IiB4PSItNTQuNjEzIiB5PSItNy41MzMiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPjxmZUdhdXNzaWFuQmx1ciByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMjAwMl8xNzE1OCIgc3RkRGV2aWF0aW9uPSI3LjY1OSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImQiIHdpZHRoPSI3OS4zNTUiIGhlaWdodD0iMjkuNCIgeD0iLTQ5LjY0IiB5PSIyLjAzIiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzIwMDJfMTcxNTgiIHN0ZERldmlhdGlvbj0iNC41OTYiLz48L2ZpbHRlcj48ZmlsdGVyIGlkPSJlIiB3aWR0aD0iNzkuNTc5IiBoZWlnaHQ9IjI5LjQiIHg9Ii00NS4wNDUiIHk9IjIwLjAyOSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5kQmx1cl8yMDAyXzE3MTU4IiBzdGREZXZpYXRpb249IjQuNTk2Ii8+PC9maWx0ZXI+PGZpbHRlciBpZD0iZiIgd2lkdGg9Ijc5LjU3OSIgaGVpZ2h0PSIyOS40IiB4PSItNDMuNTEzIiB5PSIyMS4xNzgiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPjxmZUdhdXNzaWFuQmx1ciByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMjAwMl8xNzE1OCIgc3RkRGV2aWF0aW9uPSI0LjU5NiIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImciIHdpZHRoPSI3NC43NDkiIGhlaWdodD0iNTguODUyIiB4PSIxNS43NTYiIHk9Ii0xNy45MDEiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPjxmZUdhdXNzaWFuQmx1ciByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMjAwMl8xNzE1OCIgc3RkRGV2aWF0aW9uPSI3LjY1OSIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImgiIHdpZHRoPSI2MS4zNzciIGhlaWdodD0iMjUuMzYyIiB4PSIyMy41NDgiIHk9IjIuMjg0IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzIwMDJfMTcxNTgiIHN0ZERldmlhdGlvbj0iNC41OTYiLz48L2ZpbHRlcj48ZmlsdGVyIGlkPSJpIiB3aWR0aD0iNjEuMzc3IiBoZWlnaHQ9IjI1LjM2MiIgeD0iMjMuNTQ4IiB5PSIyLjI4NCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5kQmx1cl8yMDAyXzE3MTU4IiBzdGREZXZpYXRpb249IjQuNTk2Ii8+PC9maWx0ZXI+PGZpbHRlciBpZD0iaiIgd2lkdGg9IjU2LjA0NSIgaGVpZ2h0PSI2My42NDkiIHg9Ii0yNy42MzYiIHk9Ii0yMi44NTMiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPjxmZUdhdXNzaWFuQmx1ciByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMjAwMl8xNzE1OCIgc3RkRGV2aWF0aW9uPSI0LjU5NiIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImsiIHdpZHRoPSI1NC44MTQiIGhlaWdodD0iNjQuNjQ2IiB4PSIyMC4xMTYiIHk9Ii0zOC40MTUiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPjxmZUdhdXNzaWFuQmx1ciByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMjAwMl8xNzE1OCIgc3RkRGV2aWF0aW9uPSI0LjU5NiIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9ImwiIHdpZHRoPSIzMy41NDEiIGhlaWdodD0iMzUuMzEzIiB4PSIyNC42NDEiIHk9Ii0xMS4zMjMiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48ZmVGbG9vZCBmbG9vZC1vcGFjaXR5PSIwIiByZXN1bHQ9IkJhY2tncm91bmRJbWFnZUZpeCIvPjxmZUJsZW5kIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJzaGFwZSIvPjxmZUdhdXNzaWFuQmx1ciByZXN1bHQ9ImVmZmVjdDFfZm9yZWdyb3VuZEJsdXJfMjAwMl8xNzE1OCIgc3RkRGV2aWF0aW9uPSI0LjU5NiIvPjwvZmlsdGVyPjxmaWx0ZXIgaWQ9Im0iIHdpZHRoPSI1NC44MTQiIGhlaWdodD0iNjQuNjQ2IiB4PSItMjkuMjg2IiB5PSI2LjAwOSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5kQmx1cl8yMDAyXzE3MTU4IiBzdGREZXZpYXRpb249IjQuNTk2Ii8+PC9maWx0ZXI+PGZpbHRlciBpZD0ibiIgd2lkdGg9IjU0LjgxNCIgaGVpZ2h0PSI2NC42NDYiIHg9Ii0yOS4yODYiIHk9IjYuMDA5IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzIwMDJfMTcxNTgiIHN0ZERldmlhdGlvbj0iNC41OTYiLz48L2ZpbHRlcj48ZmlsdGVyIGlkPSJvIiB3aWR0aD0iNTQuODE0IiBoZWlnaHQ9IjY0LjY0NiIgeD0iOC4yNDQiIHk9Ii0yLjQxNiIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiPjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+PGZlQmxlbmQgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+PGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iZWZmZWN0MV9mb3JlZ3JvdW5kQmx1cl8yMDAyXzE3MTU4IiBzdGREZXZpYXRpb249IjQuNTk2Ii8+PC9maWx0ZXI+PGZpbHRlciBpZD0icCIgd2lkdGg9IjM5LjQwOSIgaGVpZ2h0PSI0My42MjMiIHg9IjE4LjcxMyIgeT0iMTAuNTg4IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJlZmZlY3QxX2ZvcmVncm91bmRCbHVyXzIwMDJfMTcxNTgiIHN0ZERldmlhdGlvbj0iNC41OTYiLz48L2ZpbHRlcj48L2RlZnM+PC9zdmc+" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0IiBmaWxsPSJub25lIiByb2xlPSJpbWciIGFyaWEtbGFiZWw9IkdyYXBob3JpYSI+CiAgPHN0eWxlPi5ibGFkZXtzdHJva2U6IzBGMTcyMH1AbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOmRhcmspey5ibGFkZXtzdHJva2U6I0ZCRkNGQ319PC9zdHlsZT4KICA8ZyBzdHJva2U9IiMxMkEzQkYiIHN0cm9rZS13aWR0aD0iNC42IiBzdHJva2UtbGluZWNhcD0icm91bmQiPgogICAgPHBhdGggZD0iTTM5LjMxIDguMDkgQTI1IDI1IDAgMCAxIDU2LjM2IDM3LjYzIj48L3BhdGg+CiAgICA8cGF0aCBkPSJNNDkuMDUgNTAuMjkgQTI1IDI1IDAgMCAxIDE0Ljk1IDUwLjI5Ij48L3BhdGg+CiAgICA8cGF0aCBkPSJNNy42NCAzNy42MyBBMjUgMjUgMCAwIDEgMjQuNjkgOC4wOSI+PC9wYXRoPgogIDwvZz4KICA8ZyBjbGFzcz0iYmxhZGUiIHN0cm9rZS13aWR0aD0iNC44IiBzdHJva2UtbGluZWNhcD0icm91bmQiPgogICAgPHBhdGggZD0iTTM0LjA2IDIzLjc1IEEyNCAyNCAwIDAgMSAzMS4zNyAxNC4wMSI+PC9wYXRoPgogICAgPHBhdGggZD0iTTM4LjExIDM3LjkxIEEyNCAyNCAwIDAgMSA0Ny44OSA0MC40NSI+PC9wYXRoPgogICAgPHBhdGggZD0iTTIzLjgzIDM0LjM0IEEyNCAyNCAwIDAgMSAxNi43NCA0MS41NCI+PC9wYXRoPgogIDwvZz4KICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjciIHI9IjUuMiIgZmlsbD0iIzEyQTNCRiI+PC9jaXJjbGU+CiAgPGNpcmNsZSBjeD0iNTMuNjUiIGN5PSI0NC41IiByPSI1LjIiIGZpbGw9IiMxMkEzQkYiPjwvY2lyY2xlPgogIDxjaXJjbGUgY3g9IjEwLjM1IiBjeT0iNDQuNSIgcj0iNS4yIiBmaWxsPSIjMTJBM0JGIj48L2NpcmNsZT4KICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjMyIiByPSI3IiBmaWxsPSIjMTRCMzg5Ij48L2NpcmNsZT4KPC9zdmc+" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Graphoria - Scalar Playground</title>
|
|
8
8
|
<style>html,body{height:100%;margin:0;padding:0}#scalar-client{width:100%!important}.missing-endpoint{padding:32px;font-family:monospace}.token-banner{position:fixed;color:#fff;z-index:10001;animation:3s forwards fadeOut;background:#22c55e;border-radius:6px;padding:8px 16px;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:14px;top:10px;right:10px}@keyframes fadeOut{0%,70%{opacity:1}to{opacity:0;visibility:hidden}}
|
|
@@ -77,6 +77,16 @@ function computeDepth(
|
|
|
77
77
|
* const errors = validate(schema, parse(query), [depthLimitRule(10)]);
|
|
78
78
|
* ```
|
|
79
79
|
*/
|
|
80
|
+
/**
|
|
81
|
+
* Identity, not message matching: a caller classifying a rejection needs to
|
|
82
|
+
* tell this rule's error from an ordinary validation error, and the message is
|
|
83
|
+
* not a contract. Weak so a held error never pins the entry.
|
|
84
|
+
*/
|
|
85
|
+
const depthErrors = new WeakSet<GraphQLError>();
|
|
86
|
+
|
|
87
|
+
export const isDepthLimitError = (error: unknown): boolean =>
|
|
88
|
+
error instanceof GraphQLError && depthErrors.has(error);
|
|
89
|
+
|
|
80
90
|
export const depthLimitRule =
|
|
81
91
|
(maxDepth: number) =>
|
|
82
92
|
(context: ValidationContext): ASTVisitor => {
|
|
@@ -92,12 +102,12 @@ export const depthLimitRule =
|
|
|
92
102
|
if (depth > maxDepth) {
|
|
93
103
|
const operationName = def.name?.value ?? "anonymous";
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{ nodes: [def] },
|
|
99
|
-
),
|
|
105
|
+
const error = new GraphQLError(
|
|
106
|
+
`Query depth of ${depth} exceeds the maximum allowed depth of ${maxDepth} (operation: "${operationName}"). Raise MAX_QUERY_DEPTH to allow deeper queries.`,
|
|
107
|
+
{ nodes: [def] },
|
|
100
108
|
);
|
|
109
|
+
depthErrors.add(error);
|
|
110
|
+
context.reportError(error);
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
},
|
|
@@ -3,7 +3,7 @@ import type { Env } from "../types/env";
|
|
|
3
3
|
import { logger } from "../logging";
|
|
4
4
|
import { matchesAnySecret } from "./secrets";
|
|
5
5
|
|
|
6
|
-
export type Capability = "console:read" | "console:write" | "ai" | "mcp";
|
|
6
|
+
export type Capability = "console:read" | "console:write" | "ai" | "mcp" | "metrics";
|
|
7
7
|
|
|
8
8
|
/** `superset` is true when the admin secret, not a scoped credential, matched. */
|
|
9
9
|
export type CapabilityGrant = { superset: boolean };
|
|
@@ -27,6 +27,8 @@ const scopedSecretsFor = (env: Env, capability: Capability): string[][] => {
|
|
|
27
27
|
return [env.ai.secrets];
|
|
28
28
|
case "mcp":
|
|
29
29
|
return [env.ai.mcp.secrets];
|
|
30
|
+
case "metrics":
|
|
31
|
+
return [env.metrics.secrets];
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { RedisClient as ValkeyClient } from "bun";
|
|
2
|
-
|
|
3
1
|
import { parseDurationToMs } from "./duration";
|
|
4
2
|
import { logger } from "../logging";
|
|
3
|
+
import { createRedisClient } from "../utils/redis";
|
|
5
4
|
|
|
6
5
|
export type TokenRepository = {
|
|
7
6
|
saveJti(jti: string, expiresIn: string): Promise<void>;
|
|
@@ -80,4 +79,4 @@ export const createTokenRepository = (redisUrl: string): TokenRepository =>
|
|
|
80
79
|
// Bun's RedisClient exposes the same hset/hmget/expire surface we need but
|
|
81
80
|
// its declared types are wider than TokenRepositoryClient. The cast is the
|
|
82
81
|
// structural-typing bridge and is intentional.
|
|
83
|
-
createTokenRepositoryWithClient(
|
|
82
|
+
createTokenRepositoryWithClient(createRedisClient(redisUrl) as unknown as TokenRepositoryClient);
|
package/src/config/types/db.ts
CHANGED
|
@@ -51,14 +51,28 @@ export const RelationshipConditionZod = z
|
|
|
51
51
|
operator: z.enum(RELATIONSHIP_CONDITION_OPERATORS).optional().default("eq"),
|
|
52
52
|
/** Literal compared against the column (omit for `is_null` / `is_not_null`) */
|
|
53
53
|
value: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
54
|
+
/**
|
|
55
|
+
* Column on an ancestor table, compared against this condition's column
|
|
56
|
+
* instead of a literal. The ancestor must sit above the relationship in the
|
|
57
|
+
* query path; the nearest match wins, and a query that does not traverse it
|
|
58
|
+
* is rejected rather than joined without the predicate.
|
|
59
|
+
*/
|
|
60
|
+
ancestor: z
|
|
61
|
+
.strictObject({ schema: z.string(), name: z.string(), column: z.string() })
|
|
62
|
+
.optional(),
|
|
54
63
|
})
|
|
55
64
|
.refine((cond) => (cond.source === undefined) !== (cond.target === undefined), {
|
|
56
65
|
message: 'Relationship condition must set exactly one of "source" or "target"',
|
|
57
66
|
})
|
|
58
67
|
.refine(
|
|
59
68
|
(cond) =>
|
|
60
|
-
cond.operator === "is_null" || cond.operator === "is_not_null"
|
|
61
|
-
|
|
69
|
+
cond.operator === "is_null" || cond.operator === "is_not_null"
|
|
70
|
+
? cond.ancestor === undefined
|
|
71
|
+
: (cond.value === undefined) !== (cond.ancestor === undefined),
|
|
72
|
+
{
|
|
73
|
+
message:
|
|
74
|
+
'Relationship condition requires exactly one of "value" or "ancestor", and neither operand may be an "ancestor" when operator is is_null/is_not_null',
|
|
75
|
+
},
|
|
62
76
|
);
|
|
63
77
|
|
|
64
78
|
export type RelationshipCondition = z.input<typeof RelationshipConditionZod>;
|
|
@@ -12,7 +12,7 @@ import type { SessionContext } from "../../utils/sessionVariables";
|
|
|
12
12
|
|
|
13
13
|
import { analyzeQuery } from "../../analyzeQuery";
|
|
14
14
|
import { checkQueryCost } from "../../analyzeQuery/costLimit";
|
|
15
|
-
import { depthLimitRule } from "../../analyzeQuery/depthLimit";
|
|
15
|
+
import { depthLimitRule, isDepthLimitError } from "../../analyzeQuery/depthLimit";
|
|
16
16
|
import { resolveVariableRef, resolveVariables } from "../../analyzeQuery/resolveVariables";
|
|
17
17
|
import { callStoredProcedure, executeQueryJSON, generateSQL } from "../../databases";
|
|
18
18
|
import { proxyRemoteField } from "../../remoteSchemas/proxy";
|
|
@@ -25,6 +25,8 @@ import { EntitySource } from "../../types/resolver";
|
|
|
25
25
|
import { filterResultBySelection } from "../../utils/selection";
|
|
26
26
|
import { handleAuthMeQuery, handleAuthMutation } from "./gqlAuthOperations";
|
|
27
27
|
import { logger } from "../../logging";
|
|
28
|
+
import { incMetric, isMetricsEnabled, observeMetric } from "../../observability/metrics";
|
|
29
|
+
import { startSpan, withActiveSpan } from "../../observability/tracing";
|
|
28
30
|
|
|
29
31
|
// Handle GraphQL query
|
|
30
32
|
export const handleGraphQLRequestFactory = (
|
|
@@ -107,7 +109,7 @@ export const handleGraphQLRequestFactory = (
|
|
|
107
109
|
};
|
|
108
110
|
},
|
|
109
111
|
|
|
110
|
-
[EntitySource.STORED_PROCEDURE]: async (field, variables) => {
|
|
112
|
+
[EntitySource.STORED_PROCEDURE]: async (field, variables, queryAnalysis, _req, session) => {
|
|
111
113
|
const sp = entities.mutationsMap[field.name];
|
|
112
114
|
|
|
113
115
|
if (!sp) {
|
|
@@ -124,6 +126,14 @@ export const handleGraphQLRequestFactory = (
|
|
|
124
126
|
const result = await callStoredProcedure(
|
|
125
127
|
sp,
|
|
126
128
|
argumentsReplaced as Record<string, string | number | boolean | null>,
|
|
129
|
+
{
|
|
130
|
+
operation: {
|
|
131
|
+
type: "mutation",
|
|
132
|
+
name: queryAnalysis.operations[0]?.name ?? null,
|
|
133
|
+
fields: [field.name],
|
|
134
|
+
},
|
|
135
|
+
role: session?.role,
|
|
136
|
+
},
|
|
127
137
|
);
|
|
128
138
|
|
|
129
139
|
return {
|
|
@@ -236,6 +246,7 @@ export const handleGraphQLRequestFactory = (
|
|
|
236
246
|
) => {
|
|
237
247
|
const enforceDepthLimit = options?.enforceDepthLimit ?? true;
|
|
238
248
|
const enforceCostLimit = options?.enforceCostLimit ?? true;
|
|
249
|
+
let costRejected = false;
|
|
239
250
|
const entry = getCacheEntry(query);
|
|
240
251
|
// Unparseable queries aren't cached; let parse surface the syntax error
|
|
241
252
|
const document = entry?.document ?? parse(query);
|
|
@@ -263,7 +274,20 @@ export const handleGraphQLRequestFactory = (
|
|
|
263
274
|
env.maxQueryCost,
|
|
264
275
|
);
|
|
265
276
|
|
|
266
|
-
if (costError)
|
|
277
|
+
if (costError) {
|
|
278
|
+
validationErrors = [costError];
|
|
279
|
+
costRejected = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (validationErrors.length > 0) {
|
|
284
|
+
incMetric("graphoria_graphql_rejections_total", {
|
|
285
|
+
reason: costRejected
|
|
286
|
+
? "cost"
|
|
287
|
+
: validationErrors.some(isDepthLimitError)
|
|
288
|
+
? "depth"
|
|
289
|
+
: "validation",
|
|
290
|
+
});
|
|
267
291
|
}
|
|
268
292
|
|
|
269
293
|
return {
|
|
@@ -295,186 +319,243 @@ export const handleGraphQLRequestFactory = (
|
|
|
295
319
|
const startTime = Bun.nanoseconds();
|
|
296
320
|
|
|
297
321
|
// Reuse cached analysis on repeated identical queries
|
|
322
|
+
const analyzeSpan = startSpan("graphoria.analyze");
|
|
298
323
|
const entry = isString(query) ? getCacheEntry(query) : undefined;
|
|
299
324
|
let queryAnalysis: AnalysisResult;
|
|
300
325
|
if (entry?.analysis) {
|
|
301
326
|
queryAnalysis = entry.analysis;
|
|
327
|
+
analyzeSpan?.setAttribute("graphoria.analysis.cached", true);
|
|
302
328
|
} else {
|
|
303
329
|
queryAnalysis = isString(query) ? analyzeQuery(query, entities, gqlEntities.schema) : query;
|
|
304
330
|
if (entry) entry.analysis = queryAnalysis;
|
|
331
|
+
analyzeSpan?.setAttribute("graphoria.analysis.cached", false);
|
|
305
332
|
}
|
|
333
|
+
analyzeSpan?.end();
|
|
306
334
|
|
|
307
335
|
if (queryAnalysis.operations.length === 0) {
|
|
308
336
|
return { data: {} };
|
|
309
337
|
}
|
|
310
338
|
|
|
311
339
|
const operation = queryAnalysis.operations[0];
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
(field) => field.source === EntitySource.REMOTE_SCHEMA,
|
|
331
|
-
);
|
|
332
|
-
const aiFields = resolved.fields.filter((field) => field.source === EntitySource.AI);
|
|
333
|
-
const tableFields = resolved.fields.filter(
|
|
334
|
-
(field) =>
|
|
335
|
-
field.source !== EntitySource.AUTH &&
|
|
336
|
-
field.source !== EntitySource.REMOTE_SCHEMA &&
|
|
337
|
-
field.source !== EntitySource.AI,
|
|
340
|
+
// An anonymous operation is named by its root fields, exactly as the slow
|
|
341
|
+
// query log names one: the document itself would carry inline literals,
|
|
342
|
+
// which are caller data.
|
|
343
|
+
const operationName =
|
|
344
|
+
operation.name ?? (operation.fields ?? []).map((field) => field.name).join(",");
|
|
345
|
+
const role = session?.role ?? "anonymous";
|
|
346
|
+
const recordOperation = (outcome: "success" | "error") => {
|
|
347
|
+
if (!isMetricsEnabled()) return;
|
|
348
|
+
const labels = {
|
|
349
|
+
operation: operationName,
|
|
350
|
+
type: operation.operation,
|
|
351
|
+
role,
|
|
352
|
+
};
|
|
353
|
+
incMetric("graphoria_graphql_operations_total", { ...labels, outcome });
|
|
354
|
+
observeMetric(
|
|
355
|
+
"graphoria_graphql_operation_duration_seconds",
|
|
356
|
+
labels,
|
|
357
|
+
(Bun.nanoseconds() - startTime) / 1e9,
|
|
338
358
|
);
|
|
359
|
+
};
|
|
360
|
+
let outcome: "success" | "error" = "success";
|
|
361
|
+
const span = startSpan(`${operation.operation} ${operationName}`, {
|
|
362
|
+
attributes: {
|
|
363
|
+
"graphql.operation.name": operationName,
|
|
364
|
+
"graphql.operation.type": operation.operation,
|
|
365
|
+
"graphoria.role": role,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
339
368
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
// Handle remote schema queries in parallel
|
|
347
|
-
let remoteData: Record<string, unknown> = {};
|
|
348
|
-
if (remoteFields.length > 0) {
|
|
349
|
-
const remoteResults = await Promise.all(
|
|
350
|
-
remoteFields.map(async (field) => {
|
|
351
|
-
const entry = entities.remoteQueriesMap[field.name];
|
|
352
|
-
if (!entry) {
|
|
353
|
-
throw new Error(`Remote schema query not found: ${field.name}`);
|
|
354
|
-
}
|
|
355
|
-
const result = await proxyRemoteField(
|
|
356
|
-
field,
|
|
357
|
-
entry.remoteSchema,
|
|
358
|
-
entry.originalFieldName,
|
|
359
|
-
resolved.allVariables,
|
|
360
|
-
"query",
|
|
361
|
-
req,
|
|
362
|
-
);
|
|
363
|
-
return { [field.alias || field.name]: result };
|
|
364
|
-
}),
|
|
365
|
-
);
|
|
366
|
-
remoteData = remoteResults.reduce((acc, curr) => Object.assign(acc, curr), {});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Handle AI agent queries (admin-only `ask` field)
|
|
370
|
-
let aiData: Record<string, unknown> = {};
|
|
371
|
-
for (const field of aiFields) {
|
|
372
|
-
const alias = field.alias || field.name;
|
|
373
|
-
const prompt = String(field.arguments?.prompt ?? "");
|
|
374
|
-
audit().emit({
|
|
375
|
-
action: "ai.ask",
|
|
376
|
-
actor: actorFromSession(session),
|
|
377
|
-
target: { kind: "ai", via: "graphql" },
|
|
378
|
-
prompt,
|
|
379
|
-
});
|
|
380
|
-
aiData[alias] = await getAgent()(prompt);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Skip SQL generation if there are no table fields
|
|
384
|
-
if (tableFields.length === 0) {
|
|
369
|
+
// The span is entered rather than passed: the statements this operation
|
|
370
|
+
// runs reach the executor through call chains that carry no span, and the
|
|
371
|
+
// async context is what joins them to this one.
|
|
372
|
+
return withActiveSpan(span, async () => {
|
|
373
|
+
try {
|
|
385
374
|
log.debug(
|
|
386
|
-
{ durationMs: (Bun.nanoseconds() - startTime) / 1e6 },
|
|
387
|
-
"graphql request completed (no table fields)",
|
|
388
|
-
);
|
|
389
|
-
return { data: { ...authData, ...remoteData, ...aiData } };
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const tableQueryAnalysis = {
|
|
393
|
-
...queryAnalysis,
|
|
394
|
-
operations: [
|
|
395
375
|
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
376
|
+
operation: operation.operation,
|
|
377
|
+
name: operation.name,
|
|
378
|
+
fieldCount: operation.fields?.length,
|
|
379
|
+
queryLength: isString(query) ? query.length : undefined,
|
|
399
380
|
},
|
|
400
|
-
|
|
401
|
-
|
|
381
|
+
"graphql request",
|
|
382
|
+
);
|
|
402
383
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
resolved.allVariables,
|
|
407
|
-
false,
|
|
408
|
-
(options?.enforcePageLimits ?? true) ? pageLimits : null,
|
|
409
|
-
options?.timeoutMs,
|
|
410
|
-
);
|
|
384
|
+
// Single pass: validate, flatten object vars, resolve field args + session vars
|
|
385
|
+
// Returns an immutable ResolvedOperation — original operation is not mutated
|
|
386
|
+
const resolved = resolveVariables(operation, variables, session);
|
|
411
387
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
|
|
388
|
+
if (operation.operation === "query") {
|
|
389
|
+
// Separate auth fields, remote schema fields, and table fields
|
|
390
|
+
const authFields = resolved.fields.filter(
|
|
391
|
+
(field) => field.source === EntitySource.AUTH,
|
|
392
|
+
);
|
|
393
|
+
const remoteFields = resolved.fields.filter(
|
|
394
|
+
(field) => field.source === EntitySource.REMOTE_SCHEMA,
|
|
395
|
+
);
|
|
396
|
+
const aiFields = resolved.fields.filter((field) => field.source === EntitySource.AI);
|
|
397
|
+
const tableFields = resolved.fields.filter(
|
|
398
|
+
(field) =>
|
|
399
|
+
field.source !== EntitySource.AUTH &&
|
|
400
|
+
field.source !== EntitySource.REMOTE_SCHEMA &&
|
|
401
|
+
field.source !== EntitySource.AI,
|
|
402
|
+
);
|
|
423
403
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
404
|
+
// Handle auth queries (e.g. auth_me)
|
|
405
|
+
let authData: Record<string, unknown> = {};
|
|
406
|
+
for (const field of authFields) {
|
|
407
|
+
Object.assign(authData, handleAuthMeQuery(field, session));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Handle remote schema queries in parallel
|
|
411
|
+
let remoteData: Record<string, unknown> = {};
|
|
412
|
+
if (remoteFields.length > 0) {
|
|
413
|
+
const remoteResults = await Promise.all(
|
|
414
|
+
remoteFields.map(async (field) => {
|
|
415
|
+
const entry = entities.remoteQueriesMap[field.name];
|
|
416
|
+
if (!entry) {
|
|
417
|
+
throw new Error(`Remote schema query not found: ${field.name}`);
|
|
418
|
+
}
|
|
419
|
+
const result = await proxyRemoteField(
|
|
420
|
+
field,
|
|
421
|
+
entry.remoteSchema,
|
|
422
|
+
entry.originalFieldName,
|
|
423
|
+
resolved.allVariables,
|
|
424
|
+
"query",
|
|
425
|
+
req,
|
|
426
|
+
);
|
|
427
|
+
return { [field.alias || field.name]: result };
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
remoteData = remoteResults.reduce((acc, curr) => Object.assign(acc, curr), {});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Handle AI agent queries (admin-only `ask` field)
|
|
434
|
+
let aiData: Record<string, unknown> = {};
|
|
435
|
+
for (const field of aiFields) {
|
|
436
|
+
const alias = field.alias || field.name;
|
|
437
|
+
const prompt = String(field.arguments?.prompt ?? "");
|
|
438
|
+
audit().emit({
|
|
439
|
+
action: "ai.ask",
|
|
440
|
+
actor: actorFromSession(session),
|
|
441
|
+
target: { kind: "ai", via: "graphql" },
|
|
442
|
+
prompt,
|
|
443
|
+
});
|
|
444
|
+
aiData[alias] = await getAgent()(prompt);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Skip SQL generation if there are no table fields
|
|
448
|
+
if (tableFields.length === 0) {
|
|
449
|
+
log.debug(
|
|
450
|
+
{ durationMs: (Bun.nanoseconds() - startTime) / 1e6 },
|
|
451
|
+
"graphql request completed (no table fields)",
|
|
452
|
+
);
|
|
453
|
+
return { data: { ...authData, ...remoteData, ...aiData } };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const tableQueryAnalysis = {
|
|
457
|
+
...queryAnalysis,
|
|
458
|
+
operations: [
|
|
459
|
+
{
|
|
460
|
+
...operation,
|
|
461
|
+
fields: tableFields,
|
|
462
|
+
variables: resolved.variables,
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
428
466
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
: {}),
|
|
438
|
-
data: data.reduce((acc, curr) => Object.assign(acc, curr), {
|
|
439
|
-
...authData,
|
|
440
|
-
...remoteData,
|
|
441
|
-
...aiData,
|
|
442
|
-
}),
|
|
443
|
-
};
|
|
444
|
-
} else if (operation.operation === "mutation") {
|
|
445
|
-
// Route mutation to the appropriate handler based on field source
|
|
446
|
-
let results: Record<string, object> = {};
|
|
467
|
+
const sqlQueries = generateSQL(
|
|
468
|
+
entities,
|
|
469
|
+
tableQueryAnalysis,
|
|
470
|
+
resolved.allVariables,
|
|
471
|
+
false,
|
|
472
|
+
(options?.enforcePageLimits ?? true) ? pageLimits : null,
|
|
473
|
+
options?.timeoutMs,
|
|
474
|
+
);
|
|
447
475
|
|
|
448
|
-
|
|
449
|
-
|
|
476
|
+
const data = await Promise.all<object>(
|
|
477
|
+
sqlQueries.map(([db, query]) =>
|
|
478
|
+
executeQueryJSON(
|
|
479
|
+
query,
|
|
480
|
+
db,
|
|
481
|
+
resolved.variables,
|
|
482
|
+
resolved.allVariables as Record<string, string | number | boolean | null>,
|
|
483
|
+
options?.timeoutMs,
|
|
484
|
+
{
|
|
485
|
+
operation: {
|
|
486
|
+
type: operation.operation,
|
|
487
|
+
name: operation.name,
|
|
488
|
+
fields: tableFields.map((field) => field.name),
|
|
489
|
+
},
|
|
490
|
+
role: session?.role,
|
|
491
|
+
},
|
|
492
|
+
),
|
|
493
|
+
),
|
|
494
|
+
);
|
|
450
495
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
resolved.allVariables,
|
|
455
|
-
queryAnalysis,
|
|
456
|
-
req,
|
|
457
|
-
session,
|
|
496
|
+
log.debug(
|
|
497
|
+
{ durationMs: (Bun.nanoseconds() - startTime) / 1e6, dbCount: sqlQueries.length },
|
|
498
|
+
"graphql request completed",
|
|
458
499
|
);
|
|
459
500
|
|
|
460
|
-
|
|
461
|
-
...
|
|
462
|
-
|
|
501
|
+
return {
|
|
502
|
+
...(env.queryOnResponse
|
|
503
|
+
? {
|
|
504
|
+
sqlQueries: sqlQueries.map(([db, query]) => ({
|
|
505
|
+
db: db.name,
|
|
506
|
+
query,
|
|
507
|
+
})),
|
|
508
|
+
}
|
|
509
|
+
: {}),
|
|
510
|
+
data: data.reduce((acc, curr) => Object.assign(acc, curr), {
|
|
511
|
+
...authData,
|
|
512
|
+
...remoteData,
|
|
513
|
+
...aiData,
|
|
514
|
+
}),
|
|
463
515
|
};
|
|
464
|
-
}
|
|
465
|
-
|
|
516
|
+
} else if (operation.operation === "mutation") {
|
|
517
|
+
// Route mutation to the appropriate handler based on field source
|
|
518
|
+
let results: Record<string, object> = {};
|
|
519
|
+
|
|
520
|
+
for await (const field of resolved.fields) {
|
|
521
|
+
const source = field.source;
|
|
522
|
+
|
|
523
|
+
if (source && mutationHandlers[source]) {
|
|
524
|
+
const result = await mutationHandlers[source]!(
|
|
525
|
+
field,
|
|
526
|
+
resolved.allVariables,
|
|
527
|
+
queryAnalysis,
|
|
528
|
+
req,
|
|
529
|
+
session,
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
results = {
|
|
533
|
+
...results,
|
|
534
|
+
...result.data,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
466
538
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
539
|
+
log.debug(
|
|
540
|
+
{ durationMs: (Bun.nanoseconds() - startTime) / 1e6 },
|
|
541
|
+
"graphql request completed",
|
|
542
|
+
);
|
|
471
543
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
544
|
+
return {
|
|
545
|
+
data: results,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
476
548
|
|
|
477
|
-
|
|
549
|
+
return { data: {} };
|
|
550
|
+
} catch (error) {
|
|
551
|
+
outcome = "error";
|
|
552
|
+
span?.recordError(error);
|
|
553
|
+
throw error;
|
|
554
|
+
} finally {
|
|
555
|
+
recordOperation(outcome);
|
|
556
|
+
span?.end();
|
|
557
|
+
}
|
|
558
|
+
});
|
|
478
559
|
},
|
|
479
560
|
};
|
|
480
561
|
|
package/src/console/api.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { createConsoleSessions } from "./session";
|
|
|
11
11
|
import { getTags } from "../configuration/rest/generateOpenAPI";
|
|
12
12
|
import { audit } from "../logging/audit";
|
|
13
13
|
import { getCronJobs } from "../singletons/cron";
|
|
14
|
-
import { databasesConnections } from "../singletons/databases";
|
|
14
|
+
import { databasesConnections, pingConnection } from "../singletons/databases";
|
|
15
15
|
import { queueManager } from "../singletons/queues";
|
|
16
16
|
import { resolveClientAddress } from "../utils/rateLimit";
|
|
17
17
|
import { S200, S400, S401, S403, S404, S429 } from "../utils/responses";
|
|
@@ -35,11 +35,6 @@ type ConsoleRouteHandler = (
|
|
|
35
35
|
|
|
36
36
|
const PING_TIMEOUT_MS = 2000;
|
|
37
37
|
|
|
38
|
-
const pingConnection = (connection: SQL | ConnectionPool, type: string) =>
|
|
39
|
-
type === "mssql"
|
|
40
|
-
? (connection as ConnectionPool).query("SELECT 1")
|
|
41
|
-
: (connection as SQL).unsafe("SELECT 1");
|
|
42
|
-
|
|
43
38
|
const measureLatency = async (connection: SQL | ConnectionPool, type: string) => {
|
|
44
39
|
const start = performance.now();
|
|
45
40
|
let timer: ReturnType<typeof setTimeout> | undefined;
|