@envelop/extended-validation 8.0.0-alpha-20260116142832-a2277336f0a5a4d64d168c98cf97b7513f70b9f7 → 8.0.0-alpha-20260116185633-bd914add1fcad0f37d83c1b72ae6d57e300e62c4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Dotan Simha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/cjs/common.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDirectiveFromAstNode = getDirectiveFromAstNode;
4
+ function getDirectiveFromAstNode(astNode, names) {
5
+ const directives = astNode.directives || [];
6
+ const namesArr = Array.isArray(names) ? names : [names];
7
+ const authDirective = directives.find(d => namesArr.includes(d.name.value));
8
+ return authDirective || null;
9
+ }
package/cjs/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./plugin.js"), exports);
5
+ tslib_1.__exportStar(require("./common.js"), exports);
6
+ tslib_1.__exportStar(require("./rules/one-of.js"), exports);
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
package/cjs/plugin.js ADDED
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useExtendedValidation = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const core_1 = require("@envelop/core");
6
+ const symbolExtendedValidationRules = Symbol('extendedValidationContext');
7
+ const useExtendedValidation = (options) => {
8
+ let schemaTypeInfo;
9
+ function getTypeInfo() {
10
+ return schemaTypeInfo;
11
+ }
12
+ return {
13
+ onSchemaChange({ schema }) {
14
+ schemaTypeInfo = new graphql_1.TypeInfo(schema);
15
+ },
16
+ onContextBuilding({ context, extendContext }) {
17
+ // We initialize the validationRules context in onContextBuilding as onExecute is already too late!
18
+ let validationRulesContext = context[symbolExtendedValidationRules];
19
+ if (validationRulesContext === undefined) {
20
+ validationRulesContext = {
21
+ rules: [],
22
+ didRun: false,
23
+ };
24
+ extendContext({
25
+ ...context,
26
+ [symbolExtendedValidationRules]: validationRulesContext,
27
+ });
28
+ }
29
+ validationRulesContext.rules.push(...options.rules);
30
+ },
31
+ onSubscribe: buildHandler('subscribe', getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false),
32
+ onExecute: buildHandler('execute', getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false),
33
+ };
34
+ };
35
+ exports.useExtendedValidation = useExtendedValidation;
36
+ function buildHandler(name, getTypeInfo, onDocument, onValidationFailed, rejectOnErrors = true) {
37
+ return function handler({ args, setResultAndStopExecution, }) {
38
+ // We hook into onExecute/onSubscribe even though this is a validation pattern. The reasoning behind
39
+ // it is that hooking right after validation and before execution has started is the
40
+ // same as hooking into the validation step. The benefit of this approach is that
41
+ // we may use execution context in the validation rules.
42
+ const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
43
+ if (validationRulesContext === undefined) {
44
+ throw new Error('Plugin has not been properly set up. ' +
45
+ `The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
46
+ }
47
+ // we only want to run the extended execution once.
48
+ if (validationRulesContext.didRun === false) {
49
+ validationRulesContext.didRun = true;
50
+ if (validationRulesContext.rules.length !== 0) {
51
+ const errors = [];
52
+ // We replicate the default validation step manually before execution starts.
53
+ const typeInfo = getTypeInfo() ?? new graphql_1.TypeInfo(args.schema);
54
+ const validationContext = new graphql_1.ValidationContext(args.schema, args.document, typeInfo, e => {
55
+ errors.push(e);
56
+ });
57
+ const visitor = (0, graphql_1.visitInParallel)(validationRulesContext.rules.map(rule => rule(validationContext, args)));
58
+ args.document = (0, graphql_1.visit)(args.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, visitor));
59
+ if (onDocument) {
60
+ args.document = onDocument(args.document);
61
+ }
62
+ if (errors.length > 0) {
63
+ if (rejectOnErrors) {
64
+ let result = {
65
+ data: null,
66
+ errors,
67
+ };
68
+ if (onValidationFailed) {
69
+ onValidationFailed({ args, result, setResult: newResult => (result = newResult) });
70
+ }
71
+ setResultAndStopExecution(result);
72
+ }
73
+ else {
74
+ // eslint-disable-next-line no-inner-declarations
75
+ function onResult({ result, setResult, }) {
76
+ if ((0, core_1.isAsyncIterable)(result)) {
77
+ // rejectOnErrors is false doesn't work with async iterables
78
+ setResult({
79
+ data: null,
80
+ errors,
81
+ });
82
+ return;
83
+ }
84
+ const newResult = {
85
+ ...result,
86
+ errors: [...(result.errors || []), ...errors],
87
+ };
88
+ function visitPath(path, data = {}) {
89
+ let currentData = (data ||= typeof path[0] === 'number' ? [] : {});
90
+ for (const pathItemIndex in path.slice(0, -1)) {
91
+ const pathItem = path[pathItemIndex];
92
+ currentData = currentData[pathItem] ||=
93
+ typeof path[Number(pathItemIndex) + 1] === 'number' ? [] : {};
94
+ if (Array.isArray(currentData)) {
95
+ let pathItemIndexInArray = Number(pathItemIndex) + 1;
96
+ if (path[pathItemIndexInArray] === '@') {
97
+ pathItemIndexInArray = Number(pathItemIndex) + 2;
98
+ }
99
+ currentData = currentData.map((c, i) => visitPath(path.slice(pathItemIndexInArray), c));
100
+ }
101
+ }
102
+ currentData[path[path.length - 1]] = null;
103
+ return data;
104
+ }
105
+ errors.forEach(e => {
106
+ if (e.path?.length) {
107
+ newResult.data = visitPath(e.path, newResult.data);
108
+ }
109
+ });
110
+ setResult(newResult);
111
+ }
112
+ return {
113
+ onSubscribeResult: onResult,
114
+ onExecuteDone: onResult,
115
+ };
116
+ }
117
+ }
118
+ }
119
+ }
120
+ };
121
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OneOfInputObjectsRule = exports.ONE_OF_DIRECTIVE_SDL = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const utils_1 = require("@graphql-tools/utils");
6
+ const common_js_1 = require("../common.js");
7
+ exports.ONE_OF_DIRECTIVE_SDL = `
8
+ directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
9
+ `;
10
+ const OneOfInputObjectsRule = (validationContext, executionArgs) => {
11
+ return {
12
+ Field: node => {
13
+ if (node.arguments?.length) {
14
+ const fieldType = validationContext.getFieldDef();
15
+ if (!fieldType) {
16
+ return;
17
+ }
18
+ const values = (0, utils_1.getArgumentValues)(fieldType, node, executionArgs.variableValues || undefined);
19
+ const isOneOfFieldType = fieldType.extensions?.oneOf ||
20
+ (fieldType.astNode && (0, common_js_1.getDirectiveFromAstNode)(fieldType.astNode, 'oneOf'));
21
+ if (isOneOfFieldType && Object.keys(values).length !== 1) {
22
+ validationContext.reportError(new graphql_1.GraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, [node]));
23
+ }
24
+ for (const arg of node.arguments) {
25
+ const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
26
+ if (argType) {
27
+ traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
28
+ }
29
+ }
30
+ }
31
+ },
32
+ };
33
+ };
34
+ exports.OneOfInputObjectsRule = OneOfInputObjectsRule;
35
+ function getNonNullType(ttype) {
36
+ if (ttype instanceof graphql_1.GraphQLNonNull) {
37
+ return ttype.ofType;
38
+ }
39
+ return ttype;
40
+ }
41
+ function traverseVariables(validationContext, arg, graphqlType, currentValue) {
42
+ // if the current value is empty we don't need to traverse deeper
43
+ // if it shouldn't be empty, the "original" validation phase should complain.
44
+ if (currentValue == null) {
45
+ return;
46
+ }
47
+ const unwrappedType = getNonNullType(graphqlType);
48
+ if ((0, graphql_1.isListType)(unwrappedType)) {
49
+ if (!Array.isArray(currentValue)) {
50
+ // because of graphql type coercion a single object should be treated as an array of one object
51
+ currentValue = [currentValue];
52
+ }
53
+ currentValue.forEach(value => {
54
+ traverseVariables(validationContext, arg, unwrappedType.ofType, value);
55
+ });
56
+ return;
57
+ }
58
+ if (typeof currentValue !== 'object' || currentValue == null) {
59
+ // in case the value is not an object, the "original" validation phase should complain.
60
+ return;
61
+ }
62
+ const inputType = (0, graphql_1.getNamedType)(graphqlType);
63
+ const isOneOfInputType = inputType.extensions?.oneOf ||
64
+ (inputType.astNode && (0, common_js_1.getDirectiveFromAstNode)(inputType.astNode, 'oneOf'));
65
+ if (isOneOfInputType && Object.keys(currentValue).length !== 1) {
66
+ validationContext.reportError((0, utils_1.createGraphQLError)(`Exactly one key must be specified for input type "${inputType.name}"`, {
67
+ nodes: [arg],
68
+ }));
69
+ }
70
+ if (inputType instanceof graphql_1.GraphQLInputObjectType) {
71
+ for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
72
+ traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
73
+ }
74
+ }
75
+ }
package/esm/common.js ADDED
@@ -0,0 +1,6 @@
1
+ export function getDirectiveFromAstNode(astNode, names) {
2
+ const directives = astNode.directives || [];
3
+ const namesArr = Array.isArray(names) ? names : [names];
4
+ const authDirective = directives.find(d => namesArr.includes(d.name.value));
5
+ return authDirective || null;
6
+ }
package/esm/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './plugin.js';
2
+ export * from './common.js';
3
+ export * from './rules/one-of.js';
package/esm/plugin.js ADDED
@@ -0,0 +1,117 @@
1
+ import { TypeInfo, ValidationContext, visit, visitInParallel, visitWithTypeInfo, } from 'graphql';
2
+ import { isAsyncIterable, } from '@envelop/core';
3
+ const symbolExtendedValidationRules = Symbol('extendedValidationContext');
4
+ export const useExtendedValidation = (options) => {
5
+ let schemaTypeInfo;
6
+ function getTypeInfo() {
7
+ return schemaTypeInfo;
8
+ }
9
+ return {
10
+ onSchemaChange({ schema }) {
11
+ schemaTypeInfo = new TypeInfo(schema);
12
+ },
13
+ onContextBuilding({ context, extendContext }) {
14
+ // We initialize the validationRules context in onContextBuilding as onExecute is already too late!
15
+ let validationRulesContext = context[symbolExtendedValidationRules];
16
+ if (validationRulesContext === undefined) {
17
+ validationRulesContext = {
18
+ rules: [],
19
+ didRun: false,
20
+ };
21
+ extendContext({
22
+ ...context,
23
+ [symbolExtendedValidationRules]: validationRulesContext,
24
+ });
25
+ }
26
+ validationRulesContext.rules.push(...options.rules);
27
+ },
28
+ onSubscribe: buildHandler('subscribe', getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false),
29
+ onExecute: buildHandler('execute', getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false),
30
+ };
31
+ };
32
+ function buildHandler(name, getTypeInfo, onDocument, onValidationFailed, rejectOnErrors = true) {
33
+ return function handler({ args, setResultAndStopExecution, }) {
34
+ // We hook into onExecute/onSubscribe even though this is a validation pattern. The reasoning behind
35
+ // it is that hooking right after validation and before execution has started is the
36
+ // same as hooking into the validation step. The benefit of this approach is that
37
+ // we may use execution context in the validation rules.
38
+ const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
39
+ if (validationRulesContext === undefined) {
40
+ throw new Error('Plugin has not been properly set up. ' +
41
+ `The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
42
+ }
43
+ // we only want to run the extended execution once.
44
+ if (validationRulesContext.didRun === false) {
45
+ validationRulesContext.didRun = true;
46
+ if (validationRulesContext.rules.length !== 0) {
47
+ const errors = [];
48
+ // We replicate the default validation step manually before execution starts.
49
+ const typeInfo = getTypeInfo() ?? new TypeInfo(args.schema);
50
+ const validationContext = new ValidationContext(args.schema, args.document, typeInfo, e => {
51
+ errors.push(e);
52
+ });
53
+ const visitor = visitInParallel(validationRulesContext.rules.map(rule => rule(validationContext, args)));
54
+ args.document = visit(args.document, visitWithTypeInfo(typeInfo, visitor));
55
+ if (onDocument) {
56
+ args.document = onDocument(args.document);
57
+ }
58
+ if (errors.length > 0) {
59
+ if (rejectOnErrors) {
60
+ let result = {
61
+ data: null,
62
+ errors,
63
+ };
64
+ if (onValidationFailed) {
65
+ onValidationFailed({ args, result, setResult: newResult => (result = newResult) });
66
+ }
67
+ setResultAndStopExecution(result);
68
+ }
69
+ else {
70
+ // eslint-disable-next-line no-inner-declarations
71
+ function onResult({ result, setResult, }) {
72
+ if (isAsyncIterable(result)) {
73
+ // rejectOnErrors is false doesn't work with async iterables
74
+ setResult({
75
+ data: null,
76
+ errors,
77
+ });
78
+ return;
79
+ }
80
+ const newResult = {
81
+ ...result,
82
+ errors: [...(result.errors || []), ...errors],
83
+ };
84
+ function visitPath(path, data = {}) {
85
+ let currentData = (data ||= typeof path[0] === 'number' ? [] : {});
86
+ for (const pathItemIndex in path.slice(0, -1)) {
87
+ const pathItem = path[pathItemIndex];
88
+ currentData = currentData[pathItem] ||=
89
+ typeof path[Number(pathItemIndex) + 1] === 'number' ? [] : {};
90
+ if (Array.isArray(currentData)) {
91
+ let pathItemIndexInArray = Number(pathItemIndex) + 1;
92
+ if (path[pathItemIndexInArray] === '@') {
93
+ pathItemIndexInArray = Number(pathItemIndex) + 2;
94
+ }
95
+ currentData = currentData.map((c, i) => visitPath(path.slice(pathItemIndexInArray), c));
96
+ }
97
+ }
98
+ currentData[path[path.length - 1]] = null;
99
+ return data;
100
+ }
101
+ errors.forEach(e => {
102
+ if (e.path?.length) {
103
+ newResult.data = visitPath(e.path, newResult.data);
104
+ }
105
+ });
106
+ setResult(newResult);
107
+ }
108
+ return {
109
+ onSubscribeResult: onResult,
110
+ onExecuteDone: onResult,
111
+ };
112
+ }
113
+ }
114
+ }
115
+ }
116
+ };
117
+ }
@@ -0,0 +1,71 @@
1
+ import { getNamedType, GraphQLError, GraphQLInputObjectType, GraphQLNonNull, isListType, } from 'graphql';
2
+ import { createGraphQLError, getArgumentValues } from '@graphql-tools/utils';
3
+ import { getDirectiveFromAstNode } from '../common.js';
4
+ export const ONE_OF_DIRECTIVE_SDL = /* GraphQL */ `
5
+ directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
6
+ `;
7
+ export const OneOfInputObjectsRule = (validationContext, executionArgs) => {
8
+ return {
9
+ Field: node => {
10
+ if (node.arguments?.length) {
11
+ const fieldType = validationContext.getFieldDef();
12
+ if (!fieldType) {
13
+ return;
14
+ }
15
+ const values = getArgumentValues(fieldType, node, executionArgs.variableValues || undefined);
16
+ const isOneOfFieldType = fieldType.extensions?.oneOf ||
17
+ (fieldType.astNode && getDirectiveFromAstNode(fieldType.astNode, 'oneOf'));
18
+ if (isOneOfFieldType && Object.keys(values).length !== 1) {
19
+ validationContext.reportError(new GraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, [node]));
20
+ }
21
+ for (const arg of node.arguments) {
22
+ const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
23
+ if (argType) {
24
+ traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
25
+ }
26
+ }
27
+ }
28
+ },
29
+ };
30
+ };
31
+ function getNonNullType(ttype) {
32
+ if (ttype instanceof GraphQLNonNull) {
33
+ return ttype.ofType;
34
+ }
35
+ return ttype;
36
+ }
37
+ function traverseVariables(validationContext, arg, graphqlType, currentValue) {
38
+ // if the current value is empty we don't need to traverse deeper
39
+ // if it shouldn't be empty, the "original" validation phase should complain.
40
+ if (currentValue == null) {
41
+ return;
42
+ }
43
+ const unwrappedType = getNonNullType(graphqlType);
44
+ if (isListType(unwrappedType)) {
45
+ if (!Array.isArray(currentValue)) {
46
+ // because of graphql type coercion a single object should be treated as an array of one object
47
+ currentValue = [currentValue];
48
+ }
49
+ currentValue.forEach(value => {
50
+ traverseVariables(validationContext, arg, unwrappedType.ofType, value);
51
+ });
52
+ return;
53
+ }
54
+ if (typeof currentValue !== 'object' || currentValue == null) {
55
+ // in case the value is not an object, the "original" validation phase should complain.
56
+ return;
57
+ }
58
+ const inputType = getNamedType(graphqlType);
59
+ const isOneOfInputType = inputType.extensions?.oneOf ||
60
+ (inputType.astNode && getDirectiveFromAstNode(inputType.astNode, 'oneOf'));
61
+ if (isOneOfInputType && Object.keys(currentValue).length !== 1) {
62
+ validationContext.reportError(createGraphQLError(`Exactly one key must be specified for input type "${inputType.name}"`, {
63
+ nodes: [arg],
64
+ }));
65
+ }
66
+ if (inputType instanceof GraphQLInputObjectType) {
67
+ for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
68
+ traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
69
+ }
70
+ }
71
+ }
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "@envelop/extended-validation",
3
- "version": "8.0.0-alpha-20260116142832-a2277336f0a5a4d64d168c98cf97b7513f70b9f7",
4
- "type": "module",
3
+ "version": "8.0.0-alpha-20260116185633-bd914add1fcad0f37d83c1b72ae6d57e300e62c4",
4
+ "sideEffects": false,
5
+ "peerDependencies": {
6
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
7
+ "@envelop/core": "^5.5.0-alpha-20260116185633-bd914add1fcad0f37d83c1b72ae6d57e300e62c4"
8
+ },
9
+ "dependencies": {
10
+ "@graphql-tools/utils": "^11.0.0",
11
+ "tslib": "^2.5.0"
12
+ },
5
13
  "repository": {
6
14
  "type": "git",
7
- "url": "https://github.com/graphql-hive/graphql-yoga",
15
+ "url": "https://github.com/graphql-hive/envelop.git",
8
16
  "directory": "packages/plugins/extended-validation"
9
17
  },
10
18
  "author": "Dotan Simha <dotansimha@gmail.com>",
@@ -12,70 +20,42 @@
12
20
  "engines": {
13
21
  "node": ">=18.0.0"
14
22
  },
15
- "main": "dist/index.cjs",
16
- "typings": "dist/index.d.mts",
17
- "module": "dist/index.mjs",
23
+ "main": "cjs/index.js",
24
+ "module": "esm/index.js",
25
+ "typings": "typings/index.d.ts",
26
+ "typescript": {
27
+ "definition": "typings/index.d.ts"
28
+ },
29
+ "type": "module",
18
30
  "exports": {
19
31
  ".": {
20
32
  "require": {
21
- "types": "./dist/index.d.cts",
22
- "default": "./dist/index.cjs"
33
+ "types": "./typings/index.d.cts",
34
+ "default": "./cjs/index.js"
23
35
  },
24
36
  "import": {
25
- "types": "./dist/index.d.mts",
26
- "default": "./dist/index.mjs"
37
+ "types": "./typings/index.d.ts",
38
+ "default": "./esm/index.js"
27
39
  },
28
40
  "default": {
29
- "types": "./dist/index.d.mts",
30
- "default": "./dist/index.mjs"
41
+ "types": "./typings/index.d.ts",
42
+ "default": "./esm/index.js"
31
43
  }
32
44
  },
33
45
  "./*": {
34
46
  "require": {
35
- "types": "./dist/*.d.cts",
36
- "default": "./dist/*.cjs"
47
+ "types": "./typings/*.d.cts",
48
+ "default": "./cjs/*.js"
37
49
  },
38
50
  "import": {
39
- "types": "./dist/*.d.mts",
40
- "default": "./dist/*.mjs"
51
+ "types": "./typings/*.d.ts",
52
+ "default": "./esm/*.js"
41
53
  },
42
54
  "default": {
43
- "types": "./dist/*.d.mts",
44
- "default": "./dist/*.mjs"
55
+ "types": "./typings/*.d.ts",
56
+ "default": "./esm/*.js"
45
57
  }
46
58
  },
47
59
  "./package.json": "./package.json"
48
- },
49
- "files": [
50
- "dist"
51
- ],
52
- "scripts": {
53
- "build": "tsdown",
54
- "check": "tsc --pretty --noEmit"
55
- },
56
- "peerDependencies": {
57
- "@envelop/core": "workspace:^",
58
- "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
59
- },
60
- "dependencies": {
61
- "@graphql-tools/utils": "^10.0.0",
62
- "tslib": "^2.5.0"
63
- },
64
- "devDependencies": {
65
- "@envelop/core": "workspace:^",
66
- "@graphql-tools/schema": "10.0.30",
67
- "graphql": "16.12.0",
68
- "tsdown": "^0.20.0-beta.1",
69
- "typescript": "5.9.3"
70
- },
71
- "publishConfig": {
72
- "access": "public"
73
- },
74
- "sideEffects": false,
75
- "buildOptions": {
76
- "input": "./src/index.ts"
77
- },
78
- "typescript": {
79
- "definition": "dist/index.d.mts"
80
60
  }
81
- }
61
+ }
@@ -0,0 +1,5 @@
1
+ import { ASTVisitor, DirectiveNode, ExecutionArgs, ValidationContext } from 'graphql';
2
+ export type ExtendedValidationRule = (context: ValidationContext, executionArgs: ExecutionArgs) => ASTVisitor;
3
+ export declare function getDirectiveFromAstNode(astNode: {
4
+ directives?: ReadonlyArray<DirectiveNode>;
5
+ }, names: string | string[]): null | DirectiveNode;
@@ -0,0 +1,5 @@
1
+ import { ASTVisitor, DirectiveNode, ExecutionArgs, ValidationContext } from 'graphql';
2
+ export type ExtendedValidationRule = (context: ValidationContext, executionArgs: ExecutionArgs) => ASTVisitor;
3
+ export declare function getDirectiveFromAstNode(astNode: {
4
+ directives?: ReadonlyArray<DirectiveNode>;
5
+ }, names: string | string[]): null | DirectiveNode;
@@ -0,0 +1,3 @@
1
+ export * from './plugin.cjs';
2
+ export * from './common.cjs';
3
+ export * from './rules/one-of.cjs';
@@ -0,0 +1,3 @@
1
+ export * from './plugin.js';
2
+ export * from './common.js';
3
+ export * from './rules/one-of.js';
@@ -0,0 +1,33 @@
1
+ import { DocumentNode, ExecutionArgs, ExecutionResult } from 'graphql';
2
+ import { Plugin } from '@envelop/core';
3
+ import { ExtendedValidationRule } from './common.cjs';
4
+ declare const symbolExtendedValidationRules: unique symbol;
5
+ type ExtendedValidationContext = {
6
+ rules: Array<ExtendedValidationRule>;
7
+ didRun: boolean;
8
+ };
9
+ type OnValidationFailedCallback = (params: {
10
+ args: ExecutionArgs;
11
+ result: ExecutionResult;
12
+ setResult: (result: ExecutionResult) => void;
13
+ }) => void;
14
+ export declare const useExtendedValidation: <PluginContext extends Record<string, any> = {}>(options: {
15
+ rules: Array<ExtendedValidationRule>;
16
+ /**
17
+ * Callback that is invoked when the document is assembled by the visitor.
18
+ */
19
+ onDocument?: (document: DocumentNode) => DocumentNode;
20
+ /**
21
+ * Callback that is invoked if the extended validation yields any errors.
22
+ */
23
+ onValidationFailed?: OnValidationFailedCallback;
24
+ /**
25
+ * Reject the execution if the validation fails.
26
+ *
27
+ * @default true
28
+ */
29
+ rejectOnErrors?: boolean;
30
+ }) => Plugin<PluginContext & {
31
+ [symbolExtendedValidationRules]?: ExtendedValidationContext;
32
+ }>;
33
+ export {};
@@ -0,0 +1,33 @@
1
+ import { DocumentNode, ExecutionArgs, ExecutionResult } from 'graphql';
2
+ import { Plugin } from '@envelop/core';
3
+ import { ExtendedValidationRule } from './common.js';
4
+ declare const symbolExtendedValidationRules: unique symbol;
5
+ type ExtendedValidationContext = {
6
+ rules: Array<ExtendedValidationRule>;
7
+ didRun: boolean;
8
+ };
9
+ type OnValidationFailedCallback = (params: {
10
+ args: ExecutionArgs;
11
+ result: ExecutionResult;
12
+ setResult: (result: ExecutionResult) => void;
13
+ }) => void;
14
+ export declare const useExtendedValidation: <PluginContext extends Record<string, any> = {}>(options: {
15
+ rules: Array<ExtendedValidationRule>;
16
+ /**
17
+ * Callback that is invoked when the document is assembled by the visitor.
18
+ */
19
+ onDocument?: (document: DocumentNode) => DocumentNode;
20
+ /**
21
+ * Callback that is invoked if the extended validation yields any errors.
22
+ */
23
+ onValidationFailed?: OnValidationFailedCallback;
24
+ /**
25
+ * Reject the execution if the validation fails.
26
+ *
27
+ * @default true
28
+ */
29
+ rejectOnErrors?: boolean;
30
+ }) => Plugin<PluginContext & {
31
+ [symbolExtendedValidationRules]?: ExtendedValidationContext;
32
+ }>;
33
+ export {};
@@ -0,0 +1,3 @@
1
+ import { ExtendedValidationRule } from '../common.cjs';
2
+ export declare const ONE_OF_DIRECTIVE_SDL = "\n directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION\n";
3
+ export declare const OneOfInputObjectsRule: ExtendedValidationRule;
@@ -0,0 +1,3 @@
1
+ import { ExtendedValidationRule } from '../common.js';
2
+ export declare const ONE_OF_DIRECTIVE_SDL = "\n directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION\n";
3
+ export declare const OneOfInputObjectsRule: ExtendedValidationRule;
package/dist/common.cjs DELETED
@@ -1,10 +0,0 @@
1
-
2
- //#region src/common.ts
3
- function getDirectiveFromAstNode(astNode, names) {
4
- const directives = astNode.directives || [];
5
- const namesArr = Array.isArray(names) ? names : [names];
6
- return directives.find((d) => namesArr.includes(d.name.value)) || null;
7
- }
8
-
9
- //#endregion
10
- exports.getDirectiveFromAstNode = getDirectiveFromAstNode;
package/dist/common.d.cts DELETED
@@ -1,9 +0,0 @@
1
- import { ASTVisitor, DirectiveNode, ExecutionArgs, ValidationContext } from "graphql";
2
-
3
- //#region src/common.d.ts
4
- type ExtendedValidationRule = (context: ValidationContext, executionArgs: ExecutionArgs) => ASTVisitor;
5
- declare function getDirectiveFromAstNode(astNode: {
6
- directives?: ReadonlyArray<DirectiveNode>;
7
- }, names: string | string[]): null | DirectiveNode;
8
- //#endregion
9
- export { ExtendedValidationRule, getDirectiveFromAstNode };
package/dist/common.d.mts DELETED
@@ -1,9 +0,0 @@
1
- import { ASTVisitor, DirectiveNode, ExecutionArgs, ValidationContext } from "graphql";
2
-
3
- //#region src/common.d.ts
4
- type ExtendedValidationRule = (context: ValidationContext, executionArgs: ExecutionArgs) => ASTVisitor;
5
- declare function getDirectiveFromAstNode(astNode: {
6
- directives?: ReadonlyArray<DirectiveNode>;
7
- }, names: string | string[]): null | DirectiveNode;
8
- //#endregion
9
- export { ExtendedValidationRule, getDirectiveFromAstNode };
package/dist/common.mjs DELETED
@@ -1,9 +0,0 @@
1
- //#region src/common.ts
2
- function getDirectiveFromAstNode(astNode, names) {
3
- const directives = astNode.directives || [];
4
- const namesArr = Array.isArray(names) ? names : [names];
5
- return directives.find((d) => namesArr.includes(d.name.value)) || null;
6
- }
7
-
8
- //#endregion
9
- export { getDirectiveFromAstNode };
package/dist/index.cjs DELETED
@@ -1,5 +0,0 @@
1
- const require_plugin = require('./plugin.cjs');
2
- const require_common = require('./common.cjs');
3
-
4
- exports.getDirectiveFromAstNode = require_common.getDirectiveFromAstNode;
5
- exports.useExtendedValidation = require_plugin.useExtendedValidation;
package/dist/index.d.cts DELETED
@@ -1,3 +0,0 @@
1
- import { ExtendedValidationRule, getDirectiveFromAstNode } from "./common.cjs";
2
- import { useExtendedValidation } from "./plugin.cjs";
3
- export { ExtendedValidationRule, getDirectiveFromAstNode, useExtendedValidation };
package/dist/index.d.mts DELETED
@@ -1,3 +0,0 @@
1
- import { ExtendedValidationRule, getDirectiveFromAstNode } from "./common.mjs";
2
- import { useExtendedValidation } from "./plugin.mjs";
3
- export { ExtendedValidationRule, getDirectiveFromAstNode, useExtendedValidation };
package/dist/index.mjs DELETED
@@ -1,4 +0,0 @@
1
- import { useExtendedValidation } from "./plugin.mjs";
2
- import { getDirectiveFromAstNode } from "./common.mjs";
3
-
4
- export { getDirectiveFromAstNode, useExtendedValidation };
package/dist/plugin.cjs DELETED
@@ -1,102 +0,0 @@
1
- let graphql = require("graphql");
2
- let _envelop_core = require("@envelop/core");
3
-
4
- //#region src/plugin.ts
5
- const symbolExtendedValidationRules = Symbol("extendedValidationContext");
6
- const useExtendedValidation = (options) => {
7
- let schemaTypeInfo;
8
- function getTypeInfo() {
9
- return schemaTypeInfo;
10
- }
11
- return {
12
- onSchemaChange({ schema }) {
13
- schemaTypeInfo = new graphql.TypeInfo(schema);
14
- },
15
- onContextBuilding({ context, extendContext }) {
16
- let validationRulesContext = context[symbolExtendedValidationRules];
17
- if (validationRulesContext === void 0) {
18
- validationRulesContext = {
19
- rules: [],
20
- didRun: false
21
- };
22
- extendContext({
23
- ...context,
24
- [symbolExtendedValidationRules]: validationRulesContext
25
- });
26
- }
27
- validationRulesContext.rules.push(...options.rules);
28
- },
29
- onSubscribe: buildHandler("subscribe", getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false),
30
- onExecute: buildHandler("execute", getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false)
31
- };
32
- };
33
- function buildHandler(name, getTypeInfo, onDocument, onValidationFailed, rejectOnErrors = true) {
34
- return function handler({ args, setResultAndStopExecution }) {
35
- const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
36
- if (validationRulesContext === void 0) throw new Error(`Plugin has not been properly set up. The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
37
- if (validationRulesContext.didRun === false) {
38
- validationRulesContext.didRun = true;
39
- if (validationRulesContext.rules.length !== 0) {
40
- const errors = [];
41
- const typeInfo = getTypeInfo() ?? new graphql.TypeInfo(args.schema);
42
- const validationContext = new graphql.ValidationContext(args.schema, args.document, typeInfo, (e) => {
43
- errors.push(e);
44
- });
45
- const visitor = (0, graphql.visitInParallel)(validationRulesContext.rules.map((rule) => rule(validationContext, args)));
46
- args.document = (0, graphql.visit)(args.document, (0, graphql.visitWithTypeInfo)(typeInfo, visitor));
47
- if (onDocument) args.document = onDocument(args.document);
48
- if (errors.length > 0) if (rejectOnErrors) {
49
- let result = {
50
- data: null,
51
- errors
52
- };
53
- if (onValidationFailed) onValidationFailed({
54
- args,
55
- result,
56
- setResult: (newResult) => result = newResult
57
- });
58
- setResultAndStopExecution(result);
59
- } else {
60
- function onResult({ result, setResult }) {
61
- if ((0, _envelop_core.isAsyncIterable)(result)) {
62
- setResult({
63
- data: null,
64
- errors
65
- });
66
- return;
67
- }
68
- const newResult = {
69
- ...result,
70
- errors: [...result.errors || [], ...errors]
71
- };
72
- function visitPath(path, data = {}) {
73
- let currentData = data ||= typeof path[0] === "number" ? [] : {};
74
- for (const pathItemIndex in path.slice(0, -1)) {
75
- const pathItem = path[pathItemIndex];
76
- currentData = currentData[pathItem] ||= typeof path[Number(pathItemIndex) + 1] === "number" ? [] : {};
77
- if (Array.isArray(currentData)) {
78
- let pathItemIndexInArray = Number(pathItemIndex) + 1;
79
- if (path[pathItemIndexInArray] === "@") pathItemIndexInArray = Number(pathItemIndex) + 2;
80
- currentData = currentData.map((c) => visitPath(path.slice(pathItemIndexInArray), c));
81
- }
82
- }
83
- currentData[path[path.length - 1]] = null;
84
- return data;
85
- }
86
- errors.forEach((e) => {
87
- if (e.path?.length) newResult.data = visitPath(e.path, newResult.data);
88
- });
89
- setResult(newResult);
90
- }
91
- return {
92
- onSubscribeResult: onResult,
93
- onExecuteDone: onResult
94
- };
95
- }
96
- }
97
- }
98
- };
99
- }
100
-
101
- //#endregion
102
- exports.useExtendedValidation = useExtendedValidation;
package/dist/plugin.d.cts DELETED
@@ -1,36 +0,0 @@
1
- import { ExtendedValidationRule } from "./common.cjs";
2
- import { DocumentNode, ExecutionArgs, ExecutionResult } from "graphql";
3
- import { Plugin } from "@envelop/core";
4
-
5
- //#region src/plugin.d.ts
6
- declare const symbolExtendedValidationRules: unique symbol;
7
- type ExtendedValidationContext = {
8
- rules: Array<ExtendedValidationRule>;
9
- didRun: boolean;
10
- };
11
- type OnValidationFailedCallback = (params: {
12
- args: ExecutionArgs;
13
- result: ExecutionResult;
14
- setResult: (result: ExecutionResult) => void;
15
- }) => void;
16
- declare const useExtendedValidation: <PluginContext extends Record<string, any> = {}>(options: {
17
- rules: Array<ExtendedValidationRule>;
18
- /**
19
- * Callback that is invoked when the document is assembled by the visitor.
20
- */
21
- onDocument?: (document: DocumentNode) => DocumentNode;
22
- /**
23
- * Callback that is invoked if the extended validation yields any errors.
24
- */
25
- onValidationFailed?: OnValidationFailedCallback;
26
- /**
27
- * Reject the execution if the validation fails.
28
- *
29
- * @default true
30
- */
31
- rejectOnErrors?: boolean;
32
- }) => Plugin<PluginContext & {
33
- [symbolExtendedValidationRules]?: ExtendedValidationContext;
34
- }>;
35
- //#endregion
36
- export { useExtendedValidation };
package/dist/plugin.d.mts DELETED
@@ -1,36 +0,0 @@
1
- import { ExtendedValidationRule } from "./common.mjs";
2
- import { DocumentNode, ExecutionArgs, ExecutionResult } from "graphql";
3
- import { Plugin } from "@envelop/core";
4
-
5
- //#region src/plugin.d.ts
6
- declare const symbolExtendedValidationRules: unique symbol;
7
- type ExtendedValidationContext = {
8
- rules: Array<ExtendedValidationRule>;
9
- didRun: boolean;
10
- };
11
- type OnValidationFailedCallback = (params: {
12
- args: ExecutionArgs;
13
- result: ExecutionResult;
14
- setResult: (result: ExecutionResult) => void;
15
- }) => void;
16
- declare const useExtendedValidation: <PluginContext extends Record<string, any> = {}>(options: {
17
- rules: Array<ExtendedValidationRule>;
18
- /**
19
- * Callback that is invoked when the document is assembled by the visitor.
20
- */
21
- onDocument?: (document: DocumentNode) => DocumentNode;
22
- /**
23
- * Callback that is invoked if the extended validation yields any errors.
24
- */
25
- onValidationFailed?: OnValidationFailedCallback;
26
- /**
27
- * Reject the execution if the validation fails.
28
- *
29
- * @default true
30
- */
31
- rejectOnErrors?: boolean;
32
- }) => Plugin<PluginContext & {
33
- [symbolExtendedValidationRules]?: ExtendedValidationContext;
34
- }>;
35
- //#endregion
36
- export { useExtendedValidation };
package/dist/plugin.mjs DELETED
@@ -1,102 +0,0 @@
1
- import { TypeInfo, ValidationContext, visit, visitInParallel, visitWithTypeInfo } from "graphql";
2
- import { isAsyncIterable } from "@envelop/core";
3
-
4
- //#region src/plugin.ts
5
- const symbolExtendedValidationRules = Symbol("extendedValidationContext");
6
- const useExtendedValidation = (options) => {
7
- let schemaTypeInfo;
8
- function getTypeInfo() {
9
- return schemaTypeInfo;
10
- }
11
- return {
12
- onSchemaChange({ schema }) {
13
- schemaTypeInfo = new TypeInfo(schema);
14
- },
15
- onContextBuilding({ context, extendContext }) {
16
- let validationRulesContext = context[symbolExtendedValidationRules];
17
- if (validationRulesContext === void 0) {
18
- validationRulesContext = {
19
- rules: [],
20
- didRun: false
21
- };
22
- extendContext({
23
- ...context,
24
- [symbolExtendedValidationRules]: validationRulesContext
25
- });
26
- }
27
- validationRulesContext.rules.push(...options.rules);
28
- },
29
- onSubscribe: buildHandler("subscribe", getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false),
30
- onExecute: buildHandler("execute", getTypeInfo, options.onDocument, options.onValidationFailed, options.rejectOnErrors !== false)
31
- };
32
- };
33
- function buildHandler(name, getTypeInfo, onDocument, onValidationFailed, rejectOnErrors = true) {
34
- return function handler({ args, setResultAndStopExecution }) {
35
- const validationRulesContext = args.contextValue[symbolExtendedValidationRules];
36
- if (validationRulesContext === void 0) throw new Error(`Plugin has not been properly set up. The 'contextFactory' function is not invoked and the result has not been passed to '${name}'.`);
37
- if (validationRulesContext.didRun === false) {
38
- validationRulesContext.didRun = true;
39
- if (validationRulesContext.rules.length !== 0) {
40
- const errors = [];
41
- const typeInfo = getTypeInfo() ?? new TypeInfo(args.schema);
42
- const validationContext = new ValidationContext(args.schema, args.document, typeInfo, (e) => {
43
- errors.push(e);
44
- });
45
- const visitor = visitInParallel(validationRulesContext.rules.map((rule) => rule(validationContext, args)));
46
- args.document = visit(args.document, visitWithTypeInfo(typeInfo, visitor));
47
- if (onDocument) args.document = onDocument(args.document);
48
- if (errors.length > 0) if (rejectOnErrors) {
49
- let result = {
50
- data: null,
51
- errors
52
- };
53
- if (onValidationFailed) onValidationFailed({
54
- args,
55
- result,
56
- setResult: (newResult) => result = newResult
57
- });
58
- setResultAndStopExecution(result);
59
- } else {
60
- function onResult({ result, setResult }) {
61
- if (isAsyncIterable(result)) {
62
- setResult({
63
- data: null,
64
- errors
65
- });
66
- return;
67
- }
68
- const newResult = {
69
- ...result,
70
- errors: [...result.errors || [], ...errors]
71
- };
72
- function visitPath(path, data = {}) {
73
- let currentData = data ||= typeof path[0] === "number" ? [] : {};
74
- for (const pathItemIndex in path.slice(0, -1)) {
75
- const pathItem = path[pathItemIndex];
76
- currentData = currentData[pathItem] ||= typeof path[Number(pathItemIndex) + 1] === "number" ? [] : {};
77
- if (Array.isArray(currentData)) {
78
- let pathItemIndexInArray = Number(pathItemIndex) + 1;
79
- if (path[pathItemIndexInArray] === "@") pathItemIndexInArray = Number(pathItemIndex) + 2;
80
- currentData = currentData.map((c) => visitPath(path.slice(pathItemIndexInArray), c));
81
- }
82
- }
83
- currentData[path[path.length - 1]] = null;
84
- return data;
85
- }
86
- errors.forEach((e) => {
87
- if (e.path?.length) newResult.data = visitPath(e.path, newResult.data);
88
- });
89
- setResult(newResult);
90
- }
91
- return {
92
- onSubscribeResult: onResult,
93
- onExecuteDone: onResult
94
- };
95
- }
96
- }
97
- }
98
- };
99
- }
100
-
101
- //#endregion
102
- export { useExtendedValidation };