diff --git a/.changeset/neat-adults-appear.md b/.changeset/neat-adults-appear.md new file mode 100644 index 0000000000..1576cfa048 --- /dev/null +++ b/.changeset/neat-adults-appear.md @@ -0,0 +1,5 @@ +--- +"@neo4j/graphql": major +--- + +The `limit` argument of the `@queryOptions` directive has been moved to its own directive, `@limit`. diff --git a/packages/graphql/src/classes/LimitDirective.test.ts b/packages/graphql/src/classes/LimitDirective.test.ts new file mode 100644 index 0000000000..45ab35531f --- /dev/null +++ b/packages/graphql/src/classes/LimitDirective.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as neo4j from "neo4j-driver"; +import { LimitDirective } from "./LimitDirective"; + +describe("QueryOptionsDirective", () => { + describe("getLimit", () => { + test("should return default limit", () => { + const limit = new LimitDirective({ + default: neo4j.int(5), + max: neo4j.int(8), + }); + + expect(limit.getLimit()).toEqual(neo4j.int(5)); + }); + + test("should return max limit if default is not available", () => { + const limit = new LimitDirective({ + max: neo4j.int(8), + }); + + expect(limit.getLimit()).toEqual(neo4j.int(8)); + }); + + test("should override default limit", () => { + const limit = new LimitDirective({ + default: neo4j.int(5), + max: neo4j.int(8), + }); + + expect(limit.getLimit(neo4j.int(2))).toEqual(neo4j.int(2)); + expect(limit.getLimit(neo4j.int(6))).toEqual(neo4j.int(6)); + }); + + test("should return if a higher one is given max limit if a higher one is given", () => { + const limit = new LimitDirective({ + default: neo4j.int(5), + max: neo4j.int(8), + }); + + expect(limit.getLimit(neo4j.int(16))).toEqual(neo4j.int(8)); + }); + + test("should return undefined if no limit is given", () => { + const limit = new LimitDirective({}); + + expect(limit.getLimit()).toBeUndefined(); + }); + }); +}); diff --git a/packages/graphql/src/classes/QueryOptionsDirective.ts b/packages/graphql/src/classes/LimitDirective.ts similarity index 67% rename from packages/graphql/src/classes/QueryOptionsDirective.ts rename to packages/graphql/src/classes/LimitDirective.ts index 1399d5c21e..6fd3e591eb 100644 --- a/packages/graphql/src/classes/QueryOptionsDirective.ts +++ b/packages/graphql/src/classes/LimitDirective.ts @@ -20,29 +20,29 @@ import * as neo4j from "neo4j-driver"; import type { Integer } from "neo4j-driver"; -type QueryOptionsDirectiveConstructor = { - limit: { - default?: Integer; - max?: Integer; - }; +type LimitDirectiveConstructor = { + default?: Integer; + max?: Integer; }; -export class QueryOptionsDirective { - private limit: QueryOptionsDirectiveConstructor["limit"]; +export class LimitDirective { + private default?: Integer; + private max?: Integer; - constructor(args: QueryOptionsDirectiveConstructor) { - this.limit = args.limit; + constructor(limit: LimitDirectiveConstructor) { + this.default = limit.default; + this.max = limit.max; } public getLimit(optionsLimit?: Integer | number): Integer | undefined { if (optionsLimit) { const integerLimit = neo4j.int(optionsLimit); - if (this.limit.max && integerLimit.greaterThan(this.limit.max)) { - return this.limit.max; + if (this.max && integerLimit.greaterThan(this.max)) { + return this.max; } return integerLimit; } - return this.limit.default || this.limit.max; + return this.default || this.max; } } diff --git a/packages/graphql/src/classes/Node.ts b/packages/graphql/src/classes/Node.ts index 6287cfeaa2..a38ad0a7a9 100644 --- a/packages/graphql/src/classes/Node.ts +++ b/packages/graphql/src/classes/Node.ts @@ -41,7 +41,7 @@ import type Exclude from "./Exclude"; import type { GraphElementConstructor } from "./GraphElement"; import { GraphElement } from "./GraphElement"; import type { NodeDirective } from "./NodeDirective"; -import type { QueryOptionsDirective } from "./QueryOptionsDirective"; +import type { LimitDirective } from "./LimitDirective"; import type { SchemaConfiguration } from "../schema/schema-configuration"; import { leadingUnderscores } from "../utils/leading-underscore"; import type { Neo4jGraphQLContext } from "../types/neo4j-graphql-context"; @@ -68,7 +68,7 @@ export interface NodeConstructor extends GraphElementConstructor { schemaConfiguration?: SchemaConfiguration; nodeDirective?: NodeDirective; description?: string; - queryOptionsDirective?: QueryOptionsDirective; + limitDirective?: LimitDirective; isGlobalNode?: boolean; globalIdField?: string; globalIdFieldIsInt?: boolean; @@ -134,7 +134,7 @@ class Node extends GraphElement { public nodeDirective?: NodeDirective; public fulltextDirective?: FullText; public description?: string; - public queryOptions?: QueryOptionsDirective; + public limit?: LimitDirective; public singular: string; public plural: string; public isGlobalNode: boolean | undefined; @@ -156,7 +156,7 @@ class Node extends GraphElement { this.schemaConfiguration = input.schemaConfiguration; this.nodeDirective = input.nodeDirective; this.fulltextDirective = input.fulltextDirective; - this.queryOptions = input.queryOptionsDirective; + this.limit = input.limitDirective; this.isGlobalNode = input.isGlobalNode; this._idField = input.globalIdField; this._idFieldIsInt = input.globalIdFieldIsInt; diff --git a/packages/graphql/src/classes/QueryOptionsDirective.test.ts b/packages/graphql/src/classes/QueryOptionsDirective.test.ts deleted file mode 100644 index bd1fd0c797..0000000000 --- a/packages/graphql/src/classes/QueryOptionsDirective.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as neo4j from "neo4j-driver"; -import { QueryOptionsDirective } from "./QueryOptionsDirective"; - -describe("QueryOptionsDirective", () => { - describe("getLimit", () => { - test("should return default limit", () => { - const queryOptions = new QueryOptionsDirective({ - limit: { - default: neo4j.int(5), - max: neo4j.int(8), - }, - }); - - expect(queryOptions.getLimit()).toEqual(neo4j.int(5)); - }); - - test("should return max limit if default is not available", () => { - const queryOptions = new QueryOptionsDirective({ - limit: { - max: neo4j.int(8), - }, - }); - - expect(queryOptions.getLimit()).toEqual(neo4j.int(8)); - }); - - test("should override default limit", () => { - const queryOptions = new QueryOptionsDirective({ - limit: { - default: neo4j.int(5), - max: neo4j.int(8), - }, - }); - - expect(queryOptions.getLimit(neo4j.int(2))).toEqual(neo4j.int(2)); - expect(queryOptions.getLimit(neo4j.int(6))).toEqual(neo4j.int(6)); - }); - - test("should return if a higher one is given max limit if a higher one is given", () => { - const queryOptions = new QueryOptionsDirective({ - limit: { - default: neo4j.int(5), - max: neo4j.int(8), - }, - }); - - expect(queryOptions.getLimit(neo4j.int(16))).toEqual(neo4j.int(8)); - }); - - test("should return undefined if no limit is given", () => { - const queryOptions = new QueryOptionsDirective({ - limit: {}, - }); - - expect(queryOptions.getLimit()).toBeUndefined(); - }); - }); -}); diff --git a/packages/graphql/src/graphql/directives/index.ts b/packages/graphql/src/graphql/directives/index.ts index 734e39d74b..5af8702674 100644 --- a/packages/graphql/src/graphql/directives/index.ts +++ b/packages/graphql/src/graphql/directives/index.ts @@ -31,7 +31,7 @@ export { nodeDirective } from "./node"; export { pluralDirective } from "./plural"; export { populatedByDirective } from "./populatedBy"; export { privateDirective } from "./private"; -export { queryOptionsDirective } from "./query-options"; +export { limitDirective } from "./limit"; export { readonlyDirective } from "./readonly"; export { relationshipPropertiesDirective } from "./relationship-properties"; export { relationshipDirective } from "./relationship"; diff --git a/packages/graphql/src/graphql/directives/limit.ts b/packages/graphql/src/graphql/directives/limit.ts new file mode 100644 index 0000000000..a42a04d615 --- /dev/null +++ b/packages/graphql/src/graphql/directives/limit.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DirectiveLocation, GraphQLDirective, GraphQLInt } from "graphql"; + +export const limitDirective = new GraphQLDirective({ + name: "limit", + description: "Instructs @neo4j/graphql to inject limit values into a query.", + args: { + default: { + description: "If no limit argument is supplied on query will fallback to this value.", + type: GraphQLInt, + }, + max: { + description: "Maximum limit to be used for queries.", + type: GraphQLInt, + }, + }, + locations: [DirectiveLocation.OBJECT], +}); diff --git a/packages/graphql/src/graphql/directives/query-options.ts b/packages/graphql/src/graphql/directives/query-options.ts deleted file mode 100644 index 74666f92fb..0000000000 --- a/packages/graphql/src/graphql/directives/query-options.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DirectiveLocation, GraphQLDirective, GraphQLInputObjectType, GraphQLInt } from "graphql"; - -export const queryOptionsDirective = new GraphQLDirective({ - name: "queryOptions", - description: "Instructs @neo4j/graphql to inject default values into a query such as a default limit.", - args: { - limit: { - description: "Limit options.", - type: new GraphQLInputObjectType({ - name: "LimitInput", - fields: { - default: { - description: "If no limit argument is supplied on query will fallback to this value.", - type: GraphQLInt, - }, - max: { - description: "Maximum limit to be used for queries.", - type: GraphQLInt, - }, - }, - }), - }, - }, - locations: [DirectiveLocation.OBJECT], -}); diff --git a/packages/graphql/src/schema-model/annotation/Annotation.ts b/packages/graphql/src/schema-model/annotation/Annotation.ts index 99084de0d6..a0237691b8 100644 --- a/packages/graphql/src/schema-model/annotation/Annotation.ts +++ b/packages/graphql/src/schema-model/annotation/Annotation.ts @@ -34,7 +34,7 @@ import { PluralAnnotation } from "./PluralAnnotation"; import { PopulatedByAnnotation } from "./PopulatedByAnnotation"; import { PrivateAnnotation } from "./PrivateAnnotation"; import { QueryAnnotation } from "./QueryAnnotation"; -import { QueryOptionsAnnotation } from "./QueryOptionsAnnotation"; +import { LimitAnnotation } from "./LimitAnnotation"; import { SelectableAnnotation } from "./SelectableAnnotation"; import { SettableAnnotation } from "./SettableAnnotation"; import { SubscriptionAnnotation } from "./SubscriptionAnnotation"; @@ -48,7 +48,7 @@ export type Annotation = | AuthenticationAnnotation | KeyAnnotation | SubscriptionsAuthorizationAnnotation - | QueryOptionsAnnotation + | LimitAnnotation | DefaultAnnotation | CoalesceAnnotation | CustomResolverAnnotation @@ -68,7 +68,6 @@ export type Annotation = | JWTClaimAnnotation | JWTPayloadAnnotation; - export enum AnnotationsKey { authentication = "authentication", authorization = "authorization", @@ -82,12 +81,12 @@ export enum AnnotationsKey { jwtClaim = "jwtClaim", jwtPayload = "jwtPayload", key = "key", + limit = "limit", mutation = "mutation", plural = "plural", populatedBy = "populatedBy", private = "private", query = "query", - queryOptions = "queryOptions", selectable = "selectable", settable = "settable", subscription = "subscription", @@ -102,7 +101,7 @@ export type Annotations = { [AnnotationsKey.authentication]: AuthenticationAnnotation; [AnnotationsKey.key]: KeyAnnotation; [AnnotationsKey.subscriptionsAuthorization]: SubscriptionsAuthorizationAnnotation; - [AnnotationsKey.queryOptions]: QueryOptionsAnnotation; + [AnnotationsKey.limit]: LimitAnnotation; [AnnotationsKey.default]: DefaultAnnotation; [AnnotationsKey.coalesce]: CoalesceAnnotation; [AnnotationsKey.customResolver]: CustomResolverAnnotation; @@ -129,7 +128,7 @@ export function annotationToKey(ann: Annotation): keyof Annotations { if (ann instanceof AuthenticationAnnotation) return AnnotationsKey.authentication; if (ann instanceof KeyAnnotation) return AnnotationsKey.key; if (ann instanceof SubscriptionsAuthorizationAnnotation) return AnnotationsKey.subscriptionsAuthorization; - if (ann instanceof QueryOptionsAnnotation) return AnnotationsKey.queryOptions; + if (ann instanceof LimitAnnotation) return AnnotationsKey.limit; if (ann instanceof DefaultAnnotation) return AnnotationsKey.default; if (ann instanceof CoalesceAnnotation) return AnnotationsKey.coalesce; if (ann instanceof CustomResolverAnnotation) return AnnotationsKey.customResolver; diff --git a/packages/graphql/src/schema-model/annotation/QueryOptionsAnnotation.ts b/packages/graphql/src/schema-model/annotation/LimitAnnotation.ts similarity index 77% rename from packages/graphql/src/schema-model/annotation/QueryOptionsAnnotation.ts rename to packages/graphql/src/schema-model/annotation/LimitAnnotation.ts index 840a3deb08..3ccbd0194a 100644 --- a/packages/graphql/src/schema-model/annotation/QueryOptionsAnnotation.ts +++ b/packages/graphql/src/schema-model/annotation/LimitAnnotation.ts @@ -17,15 +17,15 @@ * limitations under the License. */ -type QueryOptionsLimit = { +export class LimitAnnotation { default?: number; max?: number; -}; -export class QueryOptionsAnnotation { - public readonly limit: QueryOptionsLimit; - - constructor({ limit }: { limit: QueryOptionsLimit }) { - this.limit = limit; + constructor({ default: _default, max }: { + default?: number; + max?: number; + }) { + this.default = _default; + this.max = max; } } diff --git a/packages/graphql/src/schema-model/parser/annotations-parser/query-options-annotation.test.ts b/packages/graphql/src/schema-model/parser/annotations-parser/limit-annotation.test.ts similarity index 59% rename from packages/graphql/src/schema-model/parser/annotations-parser/query-options-annotation.test.ts rename to packages/graphql/src/schema-model/parser/annotations-parser/limit-annotation.test.ts index ee73023677..f91bb56a48 100644 --- a/packages/graphql/src/schema-model/parser/annotations-parser/query-options-annotation.test.ts +++ b/packages/graphql/src/schema-model/parser/annotations-parser/limit-annotation.test.ts @@ -18,51 +18,51 @@ */ import { makeDirectiveNode } from "@graphql-tools/utils"; -import { parseQueryOptionsAnnotation } from "./query-options-annotation"; -import { queryOptionsDirective } from "../../../graphql/directives"; +import { parseLimitAnnotation } from "./limit-annotation"; +import { limitDirective } from "../../../graphql/directives"; const tests = [ { name: "should parse correctly with both limit arguments", - directive: makeDirectiveNode("queryOptions", { - limit: { + directive: makeDirectiveNode( + "limit", + { default: 25, max: 100, }, - }, queryOptionsDirective), + limitDirective + ), expected: { - limit: { - default: 25, - max: 100, - }, + default: 25, + max: 100, }, }, { name: "should parse correctly with only default limit argument", - directive: makeDirectiveNode("queryOptions", { - limit: { + directive: makeDirectiveNode( + "limit", + { default: 25, }, - }, queryOptionsDirective), + limitDirective + ), expected: { - limit: { - default: 25, - max: undefined, - }, + default: 25, + max: undefined, }, }, { name: "should parse correctly with only max limit argument", - directive: makeDirectiveNode("queryOptions", { - limit: { + directive: makeDirectiveNode( + "limit", + { max: 100, }, - }, queryOptionsDirective), + limitDirective + ), expected: { - limit: { - default: undefined, - max: 100, - }, + default: undefined, + max: 100, }, }, ]; @@ -70,8 +70,9 @@ const tests = [ describe("parseQueryOptionsAnnotation", () => { tests.forEach((test) => { it(`${test.name}`, () => { - const queryOptionsAnnotation = parseQueryOptionsAnnotation(test.directive); - expect(queryOptionsAnnotation.limit).toEqual(test.expected.limit); + const limitAnnotation = parseLimitAnnotation(test.directive); + expect(limitAnnotation.default).toEqual(test.expected.default); + expect(limitAnnotation.max).toEqual(test.expected.max); }); }); }); diff --git a/packages/graphql/src/schema-model/parser/annotations-parser/query-options-annotation.ts b/packages/graphql/src/schema-model/parser/annotations-parser/limit-annotation.ts similarity index 53% rename from packages/graphql/src/schema-model/parser/annotations-parser/query-options-annotation.ts rename to packages/graphql/src/schema-model/parser/annotations-parser/limit-annotation.ts index 353eb56987..933573a862 100644 --- a/packages/graphql/src/schema-model/parser/annotations-parser/query-options-annotation.ts +++ b/packages/graphql/src/schema-model/parser/annotations-parser/limit-annotation.ts @@ -19,27 +19,26 @@ import type { DirectiveNode } from "graphql"; import { Neo4jGraphQLSchemaValidationError } from "../../../classes"; -import { QueryOptionsAnnotation } from "../../annotation/QueryOptionsAnnotation"; +import { LimitAnnotation } from "../../annotation/LimitAnnotation"; import { parseArguments } from "../parse-arguments"; -import { queryOptionsDirective } from "../../../graphql/directives"; +import { limitDirective } from "../../../graphql/directives"; -export function parseQueryOptionsAnnotation(directive: DirectiveNode): QueryOptionsAnnotation { - const { limit } = parseArguments(queryOptionsDirective, directive) as { - limit: { - default?: number; - max?: number; - }; +export function parseLimitAnnotation(directive: DirectiveNode): LimitAnnotation { + const { default: _default, max } = parseArguments(limitDirective, directive) as { + default?: number; + max?: number; resolvable: boolean; }; - if (limit.default && typeof limit.default !== "number") { - throw new Neo4jGraphQLSchemaValidationError(`@queryOptions limit.default must be a number`); + if (_default && typeof _default !== "number") { + throw new Neo4jGraphQLSchemaValidationError(`@limit default must be a number`); } - if (limit.max && typeof limit.max !== "number") { - throw new Neo4jGraphQLSchemaValidationError(`@queryOptions limit.max must be a number`); + if (max && typeof max !== "number") { + throw new Neo4jGraphQLSchemaValidationError(`@limit max must be a number`); } - return new QueryOptionsAnnotation({ - limit, + return new LimitAnnotation({ + default: _default, + max, }); } diff --git a/packages/graphql/src/schema-model/parser/parse-annotation.ts b/packages/graphql/src/schema-model/parser/parse-annotation.ts index dbdcff7951..1fbe15124a 100644 --- a/packages/graphql/src/schema-model/parser/parse-annotation.ts +++ b/packages/graphql/src/schema-model/parser/parse-annotation.ts @@ -29,7 +29,7 @@ import { parsePluralAnnotation } from "./annotations-parser/plural-annotation"; import { parsePopulatedByAnnotation } from "./annotations-parser/populated-by-annotation"; import { parsePrivateAnnotation } from "./annotations-parser/private-annotation"; import { parseQueryAnnotation } from "./annotations-parser/query-annotation"; -import { parseQueryOptionsAnnotation } from "./annotations-parser/query-options-annotation"; +import { parseLimitAnnotation } from "./annotations-parser/limit-annotation"; import { parseSelectableAnnotation } from "./annotations-parser/selectable-annotation"; import { parseSettableAnnotation } from "./annotations-parser/settable-annotation"; import { parseSubscriptionAnnotation } from "./annotations-parser/subscription-annotation"; @@ -81,8 +81,8 @@ export function parseAnnotations(directives: readonly DirectiveNode[]): Annotati return parsePrivateAnnotation(directive); case AnnotationsKey.query: return parseQueryAnnotation(directive); - case AnnotationsKey.queryOptions: - return parseQueryOptionsAnnotation(directive); + case AnnotationsKey.limit: + return parseLimitAnnotation(directive); case AnnotationsKey.selectable: return parseSelectableAnnotation(directive); case AnnotationsKey.settable: diff --git a/packages/graphql/src/schema/get-nodes.ts b/packages/graphql/src/schema/get-nodes.ts index 62f336c9f8..7359fc3a2c 100644 --- a/packages/graphql/src/schema/get-nodes.ts +++ b/packages/graphql/src/schema/get-nodes.ts @@ -22,7 +22,7 @@ import type { DirectiveNode, NamedTypeNode } from "graphql"; import type { Exclude } from "../classes"; import { Node } from "../classes"; import type { NodeDirective } from "../classes/NodeDirective"; -import type { QueryOptionsDirective } from "../classes/QueryOptionsDirective"; +import type { LimitDirective } from "../classes/LimitDirective"; import type { FullText, Neo4jGraphQLCallbacks } from "../types"; import { asArray } from "../utils/utils"; import type { DefinitionNodes } from "./get-definition-nodes"; @@ -31,7 +31,7 @@ import parseExcludeDirective from "./parse-exclude-directive"; import parseNodeDirective from "./parse-node-directive"; import parseFulltextDirective from "./parse/parse-fulltext-directive"; import parsePluralDirective from "./parse/parse-plural-directive"; -import { parseQueryOptionsDirective } from "./parse/parse-query-options-directive"; +import { parseLimitDirective } from "./parse/parse-limit-directive"; import { schemaConfigurationFromObjectTypeDefinition } from "./schema-configuration"; type Nodes = { @@ -66,7 +66,7 @@ function getNodes( "exclude", "node", "fulltext", - "queryOptions", + "limit", "plural", "shareable", "subscriptionsAuthorization", @@ -85,9 +85,7 @@ function getNodes( const nodeDirectiveDefinition = (definition.directives || []).find((x) => x.name.value === "node"); const pluralDirectiveDefinition = (definition.directives || []).find((x) => x.name.value === "plural"); const fulltextDirectiveDefinition = (definition.directives || []).find((x) => x.name.value === "fulltext"); - const queryOptionsDirectiveDefinition = (definition.directives || []).find( - (x) => x.name.value === "queryOptions" - ); + const limitDirectiveDefinition = (definition.directives || []).find((x) => x.name.value === "limit"); const nodeInterfaces = [...(definition.interfaces || [])] as NamedTypeNode[]; const { interfaceExcludeDirectives } = nodeInterfaces.reduce<{ @@ -154,10 +152,10 @@ function getNodes( floatWhereInTypeDefs = true; } - let queryOptionsDirective: QueryOptionsDirective | undefined; - if (queryOptionsDirectiveDefinition) { - queryOptionsDirective = parseQueryOptionsDirective({ - directive: queryOptionsDirectiveDefinition, + let limitDirective: LimitDirective | undefined; + if (limitDirectiveDefinition) { + limitDirective = parseLimitDirective({ + directive: limitDirectiveDefinition, definition, }); } @@ -233,7 +231,7 @@ function getNodes( nodeDirective, // @ts-ignore we can be sure it's defined fulltextDirective, - queryOptionsDirective, + limitDirective, description: definition.description?.value, isGlobalNode: Boolean(globalIdField), globalIdField: globalIdField?.fieldName, diff --git a/packages/graphql/src/schema/parse/parse-query-options-directive.test.ts b/packages/graphql/src/schema/parse/parse-limit-directive.test.ts similarity index 67% rename from packages/graphql/src/schema/parse/parse-query-options-directive.test.ts rename to packages/graphql/src/schema/parse/parse-limit-directive.test.ts index f95074573d..368dff1e66 100644 --- a/packages/graphql/src/schema/parse/parse-query-options-directive.test.ts +++ b/packages/graphql/src/schema/parse/parse-limit-directive.test.ts @@ -20,16 +20,16 @@ import type { DirectiveNode, ObjectTypeDefinitionNode } from "graphql"; import gql from "graphql-tag"; import * as neo4j from "neo4j-driver"; -import { QueryOptionsDirective } from "../../classes/QueryOptionsDirective"; -import { parseQueryOptionsDirective } from "./parse-query-options-directive"; +import { LimitDirective } from "../../classes/LimitDirective"; +import { parseLimitDirective } from "./parse-limit-directive"; -describe("parseQueryOptionsDirective", () => { +describe("parseLimitDirective", () => { test("max and default argument", () => { const maxLimit = 100; const defaultLimit = 10; const typeDefs = gql` - type Movie @queryOptions(limit: {max: ${maxLimit}, default: ${defaultLimit}} ) { + type Movie @limit(max: ${maxLimit}, default: ${defaultLimit}) { id: ID } `; @@ -37,19 +37,17 @@ describe("parseQueryOptionsDirective", () => { const definition = typeDefs.definitions[0] as ObjectTypeDefinitionNode; const directive = (definition.directives || [])[0] as DirectiveNode; - const result = parseQueryOptionsDirective({ + const result = parseLimitDirective({ directive, definition, }); - expect(result).toEqual( - new QueryOptionsDirective({ limit: { max: neo4j.int(maxLimit), default: neo4j.int(defaultLimit) } }) - ); + expect(result).toEqual(new LimitDirective({ max: neo4j.int(maxLimit), default: neo4j.int(defaultLimit) })); }); test("should return correct object if default limit is undefined", () => { const typeDefs = gql` - type Movie @queryOptions { + type Movie @limit { id: ID } `; @@ -57,15 +55,11 @@ describe("parseQueryOptionsDirective", () => { const definition = typeDefs.definitions[0] as ObjectTypeDefinitionNode; const directive = (definition.directives || [])[0] as DirectiveNode; - const result = parseQueryOptionsDirective({ + const result = parseLimitDirective({ directive, definition, }); - expect(result).toEqual( - new QueryOptionsDirective({ - limit: {}, - }) - ); + expect(result).toEqual(new LimitDirective({})); }); test("fail if default argument is bigger than max", () => { @@ -73,7 +67,7 @@ describe("parseQueryOptionsDirective", () => { const defaultLimit = 100; const typeDefs = gql` - type Movie @queryOptions(limit: {max: ${maxLimit}, default: ${defaultLimit}} ) { + type Movie @limit(max: ${maxLimit}, default: ${defaultLimit}) { id: ID } `; @@ -82,12 +76,12 @@ describe("parseQueryOptionsDirective", () => { const directive = (definition.directives || [])[0] as DirectiveNode; expect(() => - parseQueryOptionsDirective({ + parseLimitDirective({ directive, definition, }) ).toThrow( - `Movie @queryOptions(limit: {max: ${maxLimit}, default: ${defaultLimit}}) invalid default value, 'default' must be smaller than 'max'` + `Movie @limit(max: ${maxLimit}, default: ${defaultLimit}) invalid default value, 'default' must be smaller than 'max'` ); }); @@ -95,7 +89,7 @@ describe("parseQueryOptionsDirective", () => { test("should throw error when default limit is less than or equal to 0", () => { [-10, -100, 0].forEach((i) => { const typeDefs = gql` - type Movie @queryOptions(limit: {default: ${i}}) { + type Movie @limit(default: ${i}) { id: ID } `; @@ -103,13 +97,11 @@ describe("parseQueryOptionsDirective", () => { const definition = typeDefs.definitions[0] as ObjectTypeDefinitionNode; const directive = (definition.directives || [])[0] as DirectiveNode; expect(() => - parseQueryOptionsDirective({ + parseLimitDirective({ directive, definition, }) - ).toThrow( - `Movie @queryOptions(limit: {default: ${i}}) invalid value: '${i}', it should be a number greater than 0` - ); + ).toThrow(`Movie @limit(default: ${i}) invalid value: '${i}', it should be a number greater than 0`); }); }); }); @@ -117,7 +109,7 @@ describe("parseQueryOptionsDirective", () => { describe("max argument", () => { test("should fail if value is 0", () => { const typeDefs = gql` - type Movie @queryOptions(limit: { max: 0 }) { + type Movie @limit(max: 0) { id: ID } `; @@ -125,16 +117,16 @@ describe("parseQueryOptionsDirective", () => { const definition = typeDefs.definitions[0] as ObjectTypeDefinitionNode; const directive = (definition.directives || [])[0] as DirectiveNode; expect(() => - parseQueryOptionsDirective({ + parseLimitDirective({ directive, definition, }) - ).toThrow(`Movie @queryOptions(limit: {max: 0}) invalid value: '0', it should be a number greater than 0`); + ).toThrow(`Movie @limit(max: 0) invalid value: '0', it should be a number greater than 0`); }); test("should fail if value is less 0", () => { const typeDefs = gql` - type Movie @queryOptions(limit: { max: -10 }) { + type Movie @limit(max: -10) { id: ID } `; @@ -142,13 +134,11 @@ describe("parseQueryOptionsDirective", () => { const definition = typeDefs.definitions[0] as ObjectTypeDefinitionNode; const directive = (definition.directives || [])[0] as DirectiveNode; expect(() => - parseQueryOptionsDirective({ + parseLimitDirective({ directive, definition, }) - ).toThrow( - `Movie @queryOptions(limit: {max: -10}) invalid value: '-10', it should be a number greater than 0` - ); + ).toThrow(`Movie @limit(max: -10) invalid value: '-10', it should be a number greater than 0`); }); }); }); diff --git a/packages/graphql/src/schema/parse/parse-query-options-directive.ts b/packages/graphql/src/schema/parse/parse-limit-directive.ts similarity index 50% rename from packages/graphql/src/schema/parse/parse-query-options-directive.ts rename to packages/graphql/src/schema/parse/parse-limit-directive.ts index e53268df10..66d62c48b7 100644 --- a/packages/graphql/src/schema/parse/parse-query-options-directive.ts +++ b/packages/graphql/src/schema/parse/parse-limit-directive.ts @@ -17,63 +17,64 @@ * limitations under the License. */ -import type { DirectiveNode, ObjectFieldNode, ObjectTypeDefinitionNode, ObjectValueNode } from "graphql"; +import type { ArgumentNode, DirectiveNode, ObjectTypeDefinitionNode } from "graphql"; import * as neo4j from "neo4j-driver"; -import { QueryOptionsDirective } from "../../classes/QueryOptionsDirective"; +import { LimitDirective } from "../../classes/LimitDirective"; import { Neo4jGraphQLError } from "../../classes/Error"; import { parseValueNode } from "../../schema-model/parser/parse-value-node"; -export function parseQueryOptionsDirective({ +export function parseLimitDirective({ directive, definition, }: { directive: DirectiveNode; definition: ObjectTypeDefinitionNode; -}): QueryOptionsDirective { - const limitArgument = directive.arguments?.find((argument) => argument.name.value === "limit"); - const limitValue = limitArgument?.value as ObjectValueNode | undefined; - const defaultLimitArgument = limitValue?.fields.find((field) => field.name.value === "default"); - const maxLimitArgument = limitValue?.fields.find((field) => field.name.value === "max"); +}): LimitDirective { + const defaultLimitArgument = directive.arguments?.find((argument) => argument.name.value === "default"); + const maxLimitArgument = directive.arguments?.find((argument) => argument.name.value === "max"); const defaultLimit = parseArgumentToInt(defaultLimitArgument); const maxLimit = parseArgumentToInt(maxLimitArgument); - const queryOptionsLimit = { default: defaultLimit, max: maxLimit }; + const limit = { default: defaultLimit, max: maxLimit }; - const queryOptionsError = validateLimitArguments(queryOptionsLimit, definition.name.value); - if (queryOptionsError) { - throw queryOptionsError; + const limitError = validateLimitArguments(limit, definition.name.value); + if (limitError) { + throw limitError; } - return new QueryOptionsDirective({ limit: queryOptionsLimit }); + return new LimitDirective(limit); } -function parseArgumentToInt(field: ObjectFieldNode | undefined): neo4j.Integer | undefined { - if (field) { - const parsed = parseValueNode(field.value) as number; +function parseArgumentToInt(argument: ArgumentNode | undefined): neo4j.Integer | undefined { + if (argument) { + const parsed = parseValueNode(argument.value) as number; return neo4j.int(parsed); } return undefined; } -function validateLimitArguments(arg: QueryOptionsDirective["limit"], typeName: string): Neo4jGraphQLError | undefined { +function validateLimitArguments( + arg: { default: neo4j.Integer | undefined; max: neo4j.Integer | undefined }, + typeName: string +): Neo4jGraphQLError | undefined { const maxLimit = arg.max?.toNumber(); const defaultLimit = arg.default?.toNumber(); if (defaultLimit !== undefined && defaultLimit <= 0) { return new Neo4jGraphQLError( - `${typeName} @queryOptions(limit: {default: ${defaultLimit}}) invalid value: '${defaultLimit}', it should be a number greater than 0` + `${typeName} @limit(default: ${defaultLimit}) invalid value: '${defaultLimit}', it should be a number greater than 0` ); } if (maxLimit !== undefined && maxLimit <= 0) { return new Neo4jGraphQLError( - `${typeName} @queryOptions(limit: {max: ${maxLimit}}) invalid value: '${maxLimit}', it should be a number greater than 0` + `${typeName} @limit(max: ${maxLimit}) invalid value: '${maxLimit}', it should be a number greater than 0` ); } if (maxLimit && defaultLimit) { if (maxLimit < defaultLimit) { return new Neo4jGraphQLError( - `${typeName} @queryOptions(limit: {max: ${maxLimit}, default: ${defaultLimit}}) invalid default value, 'default' must be smaller than 'max'` + `${typeName} @limit(max: ${maxLimit}, default: ${defaultLimit}) invalid default value, 'default' must be smaller than 'max'` ); } } diff --git a/packages/graphql/src/translate/connection-clause/create-connection-clause.ts b/packages/graphql/src/translate/connection-clause/create-connection-clause.ts index bc44f4885d..4bdc0fdbc3 100644 --- a/packages/graphql/src/translate/connection-clause/create-connection-clause.ts +++ b/packages/graphql/src/translate/connection-clause/create-connection-clause.ts @@ -80,8 +80,8 @@ export function createConnectionClause({ totalCount, ]); - // `first` specified on connection field in query needs to be compared with existing `@queryOptions`-imposed limit - const relatedFirstArg = relatedNode.queryOptions ? relatedNode.queryOptions.getLimit(firstArg) : firstArg; + // `first` specified on connection field in query needs to be compared with existing `@limit`-imposed limit + const relatedFirstArg = relatedNode.limit ? relatedNode.limit.getLimit(firstArg) : firstArg; const withSortAfterUnwindClause = createSortAndLimitProjection({ resolveTree, relationshipRef: edgeItem, @@ -170,7 +170,7 @@ function createConnectionClauseForUnions({ ]); let withOrderClause: Cypher.Clause | undefined; - const limit = relatedNode?.queryOptions?.getLimit(); + const limit = relatedNode?.limit?.getLimit(); const withOrder = createSortAndLimitProjection({ resolveTree, relationshipRef: edgeItem, diff --git a/packages/graphql/src/translate/connection-clause/create-sort-and-limit.ts b/packages/graphql/src/translate/connection-clause/create-sort-and-limit.ts index f7df46daea..f185e99ec1 100644 --- a/packages/graphql/src/translate/connection-clause/create-sort-and-limit.ts +++ b/packages/graphql/src/translate/connection-clause/create-sort-and-limit.ts @@ -76,7 +76,7 @@ export function createSortAndLimitProjection({ }); }); if (limit) { - // this limit is specified using `@queryOptions` directive + // this limit is specified using `@limit` directive addLimitOrOffsetOptionsToClause({ optionsInput: { limit: firstArg, offset }, projectionClause: withStatement, diff --git a/packages/graphql/src/translate/create-projection-and-params.ts b/packages/graphql/src/translate/create-projection-and-params.ts index efef4e431a..4d1abd8019 100644 --- a/packages/graphql/src/translate/create-projection-and-params.ts +++ b/packages/graphql/src/translate/create-projection-and-params.ts @@ -135,8 +135,8 @@ export default function createProjectionAndParams({ if (relationField) { const referenceNode = context.nodes.find((x) => x.name === relationField.typeMeta.name); - if (referenceNode?.queryOptions) { - optionsInput.limit = referenceNode.queryOptions.getLimit(optionsInput.limit); + if (referenceNode?.limit) { + optionsInput.limit = referenceNode.limit.getLimit(optionsInput.limit); } const subqueryReturnAlias = new Cypher.Variable(); diff --git a/packages/graphql/src/translate/translate-read.ts b/packages/graphql/src/translate/translate-read.ts index 35f128e645..353aff3d75 100644 --- a/packages/graphql/src/translate/translate-read.ts +++ b/packages/graphql/src/translate/translate-read.ts @@ -88,8 +88,8 @@ export function translateRead( optionsInput.sort = optionsInput.sort?.[node?.singular] || optionsInput.sort; } - if (node.queryOptions) { - optionsInput.limit = node.queryOptions.getLimit(optionsInput.limit); // TODO: improve this + if (node.limit) { + optionsInput.limit = node.limit.getLimit(optionsInput.limit); resolveTree.args.options = resolveTree.args.options || {}; (resolveTree.args.options as Record).limit = optionsInput.limit; } diff --git a/packages/graphql/tests/integration/rfcs/query-limits.int.test.ts b/packages/graphql/tests/integration/rfcs/query-limits.int.test.ts index 6d00544621..6a501d9e24 100644 --- a/packages/graphql/tests/integration/rfcs/query-limits.int.test.ts +++ b/packages/graphql/tests/integration/rfcs/query-limits.int.test.ts @@ -43,7 +43,7 @@ describe("integration/rfcs/query-limits", () => { const randomType = new UniqueType("Movie"); const typeDefs = ` - type ${randomType.name} @queryOptions(limit: {default:2}) { + type ${randomType.name} @limit(default: 2) { id: ID! } `; @@ -101,7 +101,7 @@ describe("integration/rfcs/query-limits", () => { actors: [${randomType2.name}!]! @relationship(type: "ACTED_IN", direction: IN) } - type ${randomType2.name} @queryOptions(limit:{default: 3}) { + type ${randomType2.name} @limit(default: 3) { id: ID! } `; @@ -161,7 +161,7 @@ describe("integration/rfcs/query-limits", () => { actors: [${randomType2.name}!]! @relationship(type: "ACTED_IN", direction: IN) } - type ${randomType2.name} @queryOptions(limit:{default: 4}) { + type ${randomType2.name} @limit(default: 4) { id: ID! } `; diff --git a/packages/graphql/tests/tck/rfcs/query-limits.test.ts b/packages/graphql/tests/tck/rfcs/query-limits.test.ts index f80af7f543..fafad4198c 100644 --- a/packages/graphql/tests/tck/rfcs/query-limits.test.ts +++ b/packages/graphql/tests/tck/rfcs/query-limits.test.ts @@ -28,16 +28,16 @@ describe("tck/rfcs/query-limits", () => { beforeAll(() => { typeDefs = gql` - type Movie @queryOptions(limit: { default: 3, max: 5 }) { + type Movie @limit(default: 3, max: 5) { id: ID! actors: [Person!]! @relationship(type: "ACTED_IN", direction: IN) } - type Person @queryOptions(limit: { default: 2 }) { + type Person @limit(default: 2) { id: ID! } - type Show @queryOptions(limit: { max: 2 }) { + type Show @limit(max: 2) { id: ID! }