graphql#buildSchema TypeScript Examples

The following examples show how to use graphql#buildSchema. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From graphql-query-generator with MIT License 6 votes vote down vote up
export function getGitHubQueryGenerator(gitHubAccessToken: string) {
  return new Promise<GitHubQueryGenerator>((resolve, reject) => {
    const gitHubSchemaStr = fs.readFileSync(
      path.resolve(__dirname, '../fixtures/github.graphql'),
      'utf8'
    )
    const gitHubSchema = buildSchema(gitHubSchemaStr)

    getProviderMap(gitHubAccessToken).then((gitHubProviders) => {
      resolve(
        new GitHubQueryGenerator(gitHubSchema, {
          breadthProbability: 0.5,
          depthProbability: 0.5,
          maxDepth: 10,
          providerMap: gitHubProviders,
          argumentsToConsider: ['first'],
          considerUnions: true,
          pickNestedQueryField: true
        })
      )
    })
  })
}
Example #2
Source File: typeCase.ts    From amplify-codegen with Apache License 2.0 6 votes vote down vote up
animalSchema = buildSchema(`
  type Query {
    animal: Animal
    catOrBird: CatOrBird
  }

  union Animal = Cat | Bird | Crocodile | Fish
  union CatOrBird = Cat | Bird

  interface Pet {
    name: String!
  }

  interface WarmBlooded {
    bodyTemperature: Int!
  }

  type Cat implements Pet & WarmBlooded {
    name: String!
    bodyTemperature: Int!
  }

  type Bird implements Pet & WarmBlooded {
    name: String!
    bodyTemperature: Int!
  }

  type Fish implements Pet {
    name: String!
  }

  type Crocodile {
    age: Int!
  }
`)
Example #3
Source File: GraphQlDefinition.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
GraphQlDefinition = ({ definition }: Props) => {
  const classes = useStyles();
  const schema = buildSchema(definition);

  return (
    <div className={classes.root}>
      <div className={classes.graphiQlWrapper}>
        <GraphiQL
          fetcher={() => Promise.resolve(null) as any}
          schema={schema}
          docExplorerOpen
          defaultSecondaryEditorOpen={false}
        />
      </div>
    </div>
  );
}
Example #4
Source File: index.ts    From mercurius-typescript with MIT License 6 votes vote down vote up
{ schema } = loadSchemaFiles('src/graphql/schema/**/*.gql', {
  watchOptions: {
    enabled: process.env.NODE_ENV === 'development',
    onChange(schema) {
      app.graphql.replaceSchema(buildSchema(schema.join('\n')))
      app.graphql.defineResolvers(resolvers)

      codegenMercurius(app, {
        targetPath: './src/graphql/generated.ts',
        operationsGlob: './src/graphql/operations/*.gql',
      }).catch(console.error)
    },
  },
})
Example #5
Source File: graphql.ts    From react-js-tutorial with MIT License 6 votes vote down vote up
schema = buildSchema(`
  type Query {
      course(id: Int!): Course
      courses(topic: String): [Course]
  },
  type Course {
      id: Int
      topic: String
      title: String
      author: String
      description: String
      url: String
  }
`)
Example #6
Source File: render.ts    From genql with MIT License 6 votes vote down vote up
toClientSchema = async (schemaGql: string) => {
    const schema = buildSchema(schemaGql)

    const introspectionResponse = await graphql(
        schema,
        getIntrospectionQuery(),
    )

    if (!introspectionResponse.data) {
        throw new Error(JSON.stringify(introspectionResponse.errors))
    }

    return buildClientSchema(introspectionResponse.data as any)
}
Example #7
Source File: consider-listsize-directive.test.ts    From graphql-query-generator with MIT License 6 votes vote down vote up
test(`@listSize with assumedSize and required argument`, () => {
  const schema = buildSchema(`
    directive @listSize(requireOneSlicingArgument: Boolean = true, assumedSize: Int, slicingArguments: [String], sizedFields: [String]) on FIELD_DEFINITION

    type Order {
      id: ID
      date: String
    }

    type Query {
      orders(first: Int!, after: ID, last: Int, before: ID): [Order] @listSize(assumedSize: 10)
    }
  `)
  const config = {
    providePlaceholders: true,
    seed: 1
  }
  const { queryDocument, variableValues } = generateRandomQuery(schema, config)
  const query = print(queryDocument)
  expect(query.trim()).toEqual(
    dedent(`
      query RandomQuery($Query__orders__first: Int!) {
        orders(first: $Query__orders__first) {
          id
          date
        }
      }
    `).trim()
  )
  const variables = JSON.stringify(variableValues, null, 2)
  expect(variables.trim()).toEqual(
    dedent(`
      {
        "Query__orders__first": 10
      }
    `).trim()
  )
  expect(validate(schema, queryDocument)).toEqual([])
})
Example #8
Source File: index.ts    From nodejs-typescript-graphql-starter with MIT License 6 votes vote down vote up
graphQlSchema = buildSchema(`
        scalar Date

        type User {
            _id: ID!
            name: String
            email: String!
            password: String!
            salt: String!
            role: String!
            lastLogin: Date
            createdAt: Date
            updatedAt: Date
        }

        type RootQuery {
          users: [User]
        }

        schema {
            query: RootQuery
        }
  `)
Example #9
Source File: consider-listsize-directive.test.ts    From graphql-query-generator with MIT License 6 votes vote down vote up
test(`Ignore slicing argument if requireOneSlicingArgument is set to false`, () => {
  const schema = buildSchema(`
    directive @listSize(requireOneSlicingArgument: Boolean = true, assumedSize: Int, slicingArguments: [String], sizedFields: [String]) on FIELD_DEFINITION

    type Order {
      id: ID
      date: String
    }

    type Query {
      orders(first: Int, after: ID, last: Int, before: ID): [Order] @listSize(slicingArguments: ["first"], requireOneSlicingArgument: false)
    }
  `)
  const { queryDocument } = generateRandomQuery(schema, { seed: 1 })
  const query = print(queryDocument)
  expect(query.trim()).toEqual(
    dedent(`
      query RandomQuery {
        orders {
          id
          date
        }
      }
    `).trim()
  )
  expect(validate(schema, queryDocument)).toEqual([])
})
Example #10
Source File: appsync-javascript-visitor.test.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
buildSchemaWithDirectives = (schema: String): GraphQLSchema => {
  return buildSchema([schema, directives, scalars].join('\n'));
}
Example #11
Source File: testGenerateQueries.ts    From genql with MIT License 5 votes vote down vote up
schema = buildSchema(`
interface Node {
    id: ID
  }
  
  enum Choice {
    ONE,
    TWO,
    THREE
  }
  
  type User implements Node {
    name: String
    company(id: String): Company
    employerCompany: Company
    pastEmployers(max: Int! = 1): [Company]
    id: ID
  }
  
  type DirectorConnection {
    ceos: [User],
    cursor: ID
  }
  
  type Nested {
    user: User
  }
  
  type Company implements Node {
    name: String
    nested: Nested
    legalForm: String
    ceo: User
    id: ID
    employees(limit: Int! = 1): [User]
    directors(limit: Int! = 1): DirectorConnection,
    choice: Choice
  }
  
  type Query {
    user(username: String!, choice: Choice!): User
    users(limit: Int! = 1, first: Int = 1, last: Int = 1): [User]
    company(id: String!, max: Int! = 1): Company
    node(id: ID): Node
    other(_id: ID!): String
  }
  
  schema {
    query: Query
  }`)
Example #12
Source File: appsync-json-metadata-visitor.test.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
buildSchemaWithDirectives = (schema: String): GraphQLSchema => {
  return buildSchema([schema, directives, scalars].join('\n'));
}
Example #13
Source File: Variables.test.ts    From tql with MIT License 5 votes vote down vote up
describe("Variables", () => {
  it("builds variable definitions", () => {
    const schema = buildSchema(
      `
      type Query {
        hello(name: String!): String!
      }
    `,
      { noLocation: true }
    );

    const selection = new Selection(schema, "Query", [
      field("hello", [argument("name", variable("name"))]),
    ]);

    // @note we need a way to get the input type at runtime
    const variableDefinitions = buildVariableDefinitions(
      schema,
      OperationTypeNode.QUERY,
      selectionSet(selection)
    );

    expect(variableDefinitions).toEqual([
      variableDefinition(variable("name"), nonNull(namedType("String"))),
    ]);
  });

  it("infers nested variable definitions", () => {
    const schema = buildSchema(
      `
      type Query {
        viewer: User!
      }

      type User {
        id: ID!
        friends(limit: Int!): [User!]
      }
    `,
      { noLocation: true }
    );

    const selection = new Selection(schema, "Query", [
      field(
        "viewer",
        undefined,
        selectionSet([
          field(
            "friends",
            [argument("limit", variable("friendsLimit"))],
            selectionSet([field("id")])
          ),
        ])
      ),
    ]);

    const variableDefinitions = buildVariableDefinitions(
      schema,
      OperationTypeNode.QUERY,
      selectionSet(selection)
    );

    expect(variableDefinitions).toEqual([
      variableDefinition(variable("friendsLimit"), nonNull(namedType("Int"))),
    ]);
  });
});
Example #14
Source File: index.ts    From graphql-typed-document-node with MIT License 5 votes vote down vote up
schema = buildSchema(readFileSync('./schema.graphql', 'utf-8'))
Example #15
Source File: index.test.ts    From hono with MIT License 5 votes vote down vote up
describe('GraphQL Middleware - Simple way', () => {
  // Construct a schema, using GraphQL schema language
  const schema = buildSchema(`
  type Query {
    hello: String
  }
`)

  // The root provides a resolver function for each API endpoint
  const rootValue = {
    hello: () => 'Hello world!',
  }

  const app = new Hono()

  app.use(
    '/graphql',
    graphqlServer({
      schema,
      rootValue,
    })
  )

  app.all('*', (c) => {
    c.header('foo', 'bar')
    return c.text('fallback')
  })

  it('Should return GraphQL response', async () => {
    const query = 'query { hello }'
    const body = {
      query: query,
    }

    const res = await app.request('http://localhost/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    })
    expect(res).not.toBeNull()
    expect(res.status).toBe(200)
    expect(await res.text()).toBe('{"data":{"hello":"Hello world!"}}')
    expect(res.headers.get('foo')).toBeNull() // GraphQL Server middleware should be Handler
  })
})
Example #16
Source File: schema.ts    From Deep-Lynx with MIT License 5 votes vote down vote up
schema = buildSchema(generateSchema())
Example #17
Source File: generated.tsx    From genql with MIT License 5 votes vote down vote up
schema = buildSchema(`
interface Node {
    id: ID
  }
  
  enum Choice {
    ONE,
    TWO,
    THREE
  }
  
  type User implements Node {
    name: String
    company(id: String): Company
    employerCompany: Company
    pastEmployers(max: Int! = 1): [Company]
    id: ID
  }
  
  type DirectorConnection {
    ceos: [User],
    cursor: ID
  }
  
  type Nested {
    user: User
  }
  
  type Company implements Node {
    name: String
    nested: Nested
    legalForm: String
    ceo: User
    id: ID
    employees(limit: Int! = 1): [User]
    directors(limit: Int! = 1): DirectorConnection,
    choice: Choice
  }
  
  type Query {
    user(username: String!, choice: Choice!): User
    users(limit: Int! = 1, first: Int = 1, last: Int = 1): [User]
    company(id: String!, max: Int! = 1): Company
    node(id: ID): Node
    other(_id: ID!): String
  }
  
  schema {
    query: Query
  }`)
Example #18
Source File: graphql-server.ts    From graphql-eslint with MIT License 5 votes vote down vote up
graphqlSchemaObj = buildSchema(sdlSchema)
Example #19
Source File: render.ts    From tql with MIT License 5 votes vote down vote up
render = (sdl: string): string => {
  const ast = parse(sdl, { noLocation: true });
  const schema = buildSchema(sdl);

  const transforms = [
    typeTransform(ast, schema),
    selectorInterfaceTransform(ast, schema),
  ];

  // additive transforms
  const results = transforms.map(
    (vistor) => visit(ast, vistor)
  ) as unknown as Array<{ readonly definitions: Code[] }> ;

  const types = Object.values(schema.getTypeMap()).filter(
    (type) => !type.name.startsWith("__")
  );

  const enumValues = new Set(
    Object.values(schema.getTypeMap())
      .filter((type) => type instanceof GraphQLEnumType)
      .flatMap((type) =>
        (type as GraphQLEnumType).getValues().map((value) => value.value)
      )
  );

  const ENUMS = `
    Object.freeze({
      ${Array.from(enumValues)
        .map((value) => `${value}: true`)
        .join(",\n")}
    } as const)
  `;

  const typeMap = `
    export interface ISchema {
      ${types.map(printType).join("\n")}
    }
  `;

  const source =
    `
    import { buildASTSchema, Kind, OperationTypeNode } from 'graphql'

    import {
      TypeConditionError,
      NamedType,
      Field,
      InlineFragment,
      Argument,
      Variable,
      Selection,
      SelectionSet,
      SelectionBuilder,
      namedType,
      field,
      inlineFragment,
      argument,
      selectionSet
     } from '@timkendall/tql'

     export type { Result, SelectionResult, Variables } from '@timkendall/tql'
     export { $ } from '@timkendall/tql'

     ` +
    `
    export const SCHEMA = buildASTSchema(${stringifyAST(ast)})

    export const ENUMS = ${ENUMS}

    ${typeMap}
  ` +
    results
      .flatMap((result) =>
        result.definitions.map((code) => code.toCodeString())
      )
      .join("\n");

  return prettier.format(source, { parser: "typescript" });
}
Example #20
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
private async replaceFederationSDLWithStitchingSDL(
    name: string,
    oldSchema: GraphQLSchema,
    executor: Executor,
    stitchingDirectives: StitchingDirectivesResult
  ) {
    const rawSourceLogger = this.logger.child(name);

    rawSourceLogger.debug(`Extracting existing resolvers if available`);
    const resolvers = extractResolvers(oldSchema);

    let newSchema = await this.store
      .proxy(`${name}_stitching`, PredefinedProxyOptions.GraphQLSchemaWithDiffing)
      .getWithSet(async () => {
        this.logger.debug(`Fetching Apollo Federated Service SDL for ${name}`);
        const sdlQueryResult: any = (await executor({
          document: parse(APOLLO_GET_SERVICE_DEFINITION_QUERY),
        })) as ExecutionResult;
        if (sdlQueryResult.errors?.length) {
          throw new AggregateError(sdlQueryResult.errors, `Failed on fetching Federated SDL for ${name}`);
        }
        const federationSdl = sdlQueryResult.data._service.sdl;
        this.logger.debug(`Generating Stitching SDL for ${name}`);
        const stitchingSdl = federationToStitchingSDL(federationSdl, stitchingDirectives);
        return buildSchema(stitchingSdl, {
          assumeValid: true,
          assumeValidSDL: true,
        });
      });

    rawSourceLogger.debug(`Adding existing resolvers back to the schema`);
    newSchema = addResolversToSchema({
      schema: newSchema,
      resolvers,
      updateResolversInPlace: true,
      resolverValidationOptions: {
        requireResolversToMatchSchema: 'ignore',
      },
    });

    return newSchema;
  }
Example #21
Source File: consider-listsize-directive.test.ts    From graphql-query-generator with MIT License 5 votes vote down vote up
test(`Add nested slicing argument ("args.first") when defined in @listSize`, () => {
  const schema = buildSchema(`
    directive @listSize(requireOneSlicingArgument: Boolean = true, assumedSize: Int, slicingArguments: [String], sizedFields: [String]) on FIELD_DEFINITION

    input Args {
      first: Int
      after: ID
      last: Int
      before: ID
    }

    type Order {
      id: ID
      date: String
    }

    type Query {
      orders(args: Args): [Order] @listSize(slicingArguments: ["args.first", "args.last"])
    }
  `)
  const config = {
    providePlaceholders: true,
    seed: 1
  }
  const { queryDocument, variableValues } = generateRandomQuery(schema, config)
  const query = print(queryDocument)
  expect(query.trim()).toEqual(
    dedent(`
      query RandomQuery($Query__orders__args: Args) {
        orders(args: $Query__orders__args) {
          id
          date
        }
      }
    `).trim()
  )
  const variables = JSON.stringify(variableValues, null, 2)
  expect(variables.trim()).toEqual(
    dedent(`
      {
        "Query__orders__args": {
          "first": 10
        }
      }
    `).trim()
  )
  expect(validate(schema, queryDocument)).toEqual([])
})
Example #22
Source File: index.ts    From graphql-mesh with MIT License 5 votes vote down vote up
async getNonExecutableSchemaForHTTPSource(
    httpSourceConfig: YamlConfig.GraphQLHandlerHTTPConfiguration
  ): Promise<GraphQLSchema> {
    const schemaHeadersFactory = getInterpolatedHeadersFactory(httpSourceConfig.schemaHeaders || {});
    const customFetch = await this.getCustomFetchImpl(httpSourceConfig.customFetch);
    if (httpSourceConfig.introspection) {
      const headers = schemaHeadersFactory({
        env: process.env,
      });
      const sdlOrIntrospection = await readFileOrUrl<string | IntrospectionQuery | DocumentNode>(
        httpSourceConfig.introspection,
        {
          cwd: this.baseDir,
          allowUnknownExtensions: true,
          fetch: customFetch,
          headers,
        }
      );
      if (typeof sdlOrIntrospection === 'string') {
        return buildSchema(sdlOrIntrospection);
      } else if (isDocumentNode(sdlOrIntrospection)) {
        return buildASTSchema(sdlOrIntrospection);
      } else if (sdlOrIntrospection.__schema) {
        return buildClientSchema(sdlOrIntrospection);
      }
      throw new Error(`Invalid introspection data: ${util.inspect(sdlOrIntrospection)}`);
    }
    return this.nonExecutableSchema.getWithSet(() => {
      const endpointFactory = getInterpolatedStringFactory(httpSourceConfig.endpoint);
      const executor = this.urlLoader.getExecutorAsync(httpSourceConfig.endpoint, {
        ...httpSourceConfig,
        customFetch,
        subscriptionsProtocol: httpSourceConfig.subscriptionsProtocol as SubscriptionProtocol,
      });
      return introspectSchema(function meshIntrospectionExecutor(params: ExecutionRequest) {
        const resolverData = getResolverData(params);
        return executor({
          ...params,
          extensions: {
            ...params.extensions,
            headers: schemaHeadersFactory(resolverData),
            endpoint: endpointFactory(resolverData),
          },
        });
      });
    });
  }
Example #23
Source File: appsync-java-visitor.test.ts    From amplify-codegen with Apache License 2.0 5 votes vote down vote up
buildSchemaWithDirectives = (schema: String): GraphQLSchema => {
  return buildSchema([schema, directives, scalars].join('\n'));
}
Example #24
Source File: cache.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('cache', () => {
  let schema: GraphQLSchema;
  let cache: KeyValueCache;
  let pubsub: MeshPubSub;
  const baseDir: string = undefined;

  beforeEach(() => {
    const baseSchema = buildSchema(/* GraphQL */ `
      type Query {
        user(id: ID!): User
        users(filter: SearchUsersInput): [User!]!
      }

      type Mutation {
        updateUser(userId: ID!, name: String!): User
        deleteUser(userIdToDelete: ID!): Boolean
      }

      input SearchUsersInput {
        username: String
        email: String
        name: String
        age: String
      }

      type User {
        id: ID!
        username: String!
        email: String!
        profile: Profile!
        friend(id: ID!): User
      }

      type Profile {
        name: String!
        age: Int!
      }
    `);

    schema = addResolversToSchema({
      schema: baseSchema,
      resolvers: spies,
    });

    cache = new LocalforageCache();
    pubsub = new PubSub();

    spies.Query.user.mockClear();
    spies.Query.users.mockClear();
  });

  describe('Resolvers Composition', () => {
    it('should replace resolvers correctly with a specific type and field', async () => {
      expect(schema.getQueryType()?.getFields().user.resolve.name).toBe(spies.Query.user.bind(null).name);

      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        cache,
        config: [
          {
            field: 'Query.user',
          },
        ],
        pubsub,
        baseDir,
      });
      const modifiedSchema = transform.transformSchema(schema);

      expect(modifiedSchema!.getQueryType()?.getFields().user.resolve.name).not.toBe(spies.Query.user.bind(null).name);
      expect(modifiedSchema!.getQueryType()?.getFields().users.resolve.name).toBe(spies.Query.users.bind(null).name);
    });

    it('should replace resolvers correctly with a wildcard', async () => {
      expect(schema.getQueryType()?.getFields().user.resolve.name).toBe(spies.Query.user.bind(null).name);
      expect(schema.getQueryType()?.getFields().users.resolve.name).toBe(spies.Query.users.bind(null).name);

      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        cache,
        config: [
          {
            field: 'Query.*',
          },
        ],
        pubsub,
        baseDir,
      });

      const modifiedSchema = transform.transformSchema(schema);

      expect(modifiedSchema!.getQueryType()?.getFields().user.resolve.name).not.toBe(spies.Query.user.bind(null).name);
      expect(modifiedSchema!.getQueryType()?.getFields().users.resolve.name).not.toBe(
        spies.Query.users.bind(null).name
      );
    });
  });

  describe('Cache Wrapper', () => {
    const checkCache = async (config: YamlConfig.CacheTransformConfig[], cacheKeyToCheck?: string) => {
      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        cache,
        config,
        pubsub,
        baseDir,
      });

      const modifiedSchema = transform.transformSchema(schema);

      const executeOptions = {
        schema: modifiedSchema!,
        document: parse(/* GraphQL */ `
          query user {
            user(id: 1) {
              id
              username
            }
          }
        `),
        contextValue: {},
      };

      const queryType = schema.getType('Query') as GraphQLObjectType;
      const queryFields = queryType.getFields();
      const operation = executeOptions.document.definitions[0] as OperationDefinitionNode;
      const cacheKey =
        cacheKeyToCheck ||
        computeCacheKey({
          keyStr: undefined,
          args: { id: '1' },
          info: {
            fieldName: 'user',
            parentType: queryType,
            returnType: queryFields.user.type,
            schema,
            fieldNodes: operation.selectionSet.selections as FieldNode[],
            fragments: {},
            rootValue: {},
            operation,
            variableValues: {},
            path: {
              prev: null,
              key: 'user',
            },
          } as any,
        });

      // No data in cache before calling it
      expect(await cache.get(cacheKey)).not.toBeDefined();
      // Run it for the first time
      await execute(executeOptions);
      // Original resolver should now be called
      expect(spies.Query.user.mock.calls.length).toBe(1);
      // Data should be stored in cache
      const data: any = await cache.get(cacheKey);
      const mockData = MOCK_DATA[0];
      expect(data.id).toBe(mockData.id);
      expect(data.username).toBe(mockData.username);
      // Running it again
      await execute(executeOptions);
      // No new calls to the original resolver
      expect(spies.Query.user.mock.calls.length).toBe(1);

      return {
        cache,
        executeAgain: () => execute(executeOptions),
        executeDocument: (operation: DocumentNode, variables: Record<string, any> = {}) =>
          execute({
            schema: modifiedSchema,
            document: operation,
            variableValues: variables,
          }),
      };
    };

    it('Should wrap resolver correctly with caching - without cacheKey', async () => {
      await checkCache([
        {
          field: 'Query.user',
        },
      ]);
    });

    it('Should wrap resolver correctly with caching with custom key', async () => {
      const cacheKey = `customUser`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey,
          },
        ],
        cacheKey
      );
    });

    it('Should wrap resolver correctly with caching with custom key', async () => {
      const cacheKey = `customUser`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey,
          },
        ],
        cacheKey
      );
    });

    it('Should clear cache correctly when TTL is set', async () => {
      const key = 'user-1';
      const { cache, executeAgain } = await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: key,
            invalidate: {
              ttl: 1,
            },
          },
        ],
        key
      );

      expect(await cache.get(key)).toBeDefined();
      await wait(1.1);
      expect(await cache.get(key)).not.toBeDefined();
      await executeAgain();
      expect(await cache.get(key)).toBeDefined();
    });

    it('Should wrap resolver correctly with caching with custom calculated key - and ensure calling resovler again when key is different', async () => {
      const { cache, executeDocument } = await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `query-user-{args.id}`,
          },
        ],
        'query-user-1'
      );

      const otherIdQuery = parse(/* GraphQL */ `
        query user {
          user(id: 2) {
            id
          }
        }
      `);

      expect(await cache.get('query-user-2')).not.toBeDefined();
      await executeDocument(otherIdQuery);
      const cachedObj: any = await cache.get('query-user-2');
      const mockObj = MOCK_DATA[1];
      expect(cachedObj.id).toBe(mockObj.id);
      expect(spies.Query.user.mock.calls.length).toBe(2);
      await executeDocument(otherIdQuery);
      expect(spies.Query.user.mock.calls.length).toBe(2);
    });

    it('Should work correctly with argsHash', async () => {
      const expectedHash = `query-user-${hashObject({ id: '1' })}`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `query-user-{argsHash}`,
          },
        ],
        expectedHash
      );
    });

    it('Should work correctly with hash helper', async () => {
      const expectedHash = hashObject('1');

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `{args.id|hash}`,
          },
        ],
        expectedHash
      );
    });

    it('Should work correctly with date helper', async () => {
      const expectedHash = `1-${dayjs(new Date()).format(`yyyy-MM-dd`)}`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `{args.id}-{yyyy-MM-dd|date}`,
          },
        ],
        expectedHash
      );
    });
  });

  describe('Opration-based invalidation', () => {
    it('Should invalidate cache when mutation is done based on key', async () => {
      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        config: [
          {
            field: 'Query.user',
            cacheKey: 'query-user-{args.id}',
            invalidate: {
              effectingOperations: [
                {
                  operation: 'Mutation.updateUser',
                  matchKey: 'query-user-{args.userId}',
                },
                {
                  operation: 'Mutation.deleteUser',
                  matchKey: 'query-user-{args.userIdToDelete}',
                },
              ],
            },
          },
        ],
        cache,
        pubsub,
        baseDir,
      });

      const schemaWithCache = transform.transformSchema(schema);

      const expectedCacheKey = `query-user-1`;

      const executeOptions = {
        schema: schemaWithCache!,
        document: parse(/* GraphQL */ `
          query user {
            user(id: 1) {
              id
              name
            }
          }
        `),
      };

      // Make sure cache works as needed, runs resolvers logic only once
      expect(await cache.get(expectedCacheKey)).not.toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(0);
      await execute(executeOptions);
      expect(await cache.get(expectedCacheKey)).toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(1);
      await execute(executeOptions);
      expect(spies.Query.user.mock.calls.length).toBe(1);

      // Run effecting mutation
      await execute({
        schema: schemaWithCache!,
        document: parse(/* GraphQL */ `
          mutation updateUser {
            updateUser(userId: 1, name: "test new") {
              id
              name
            }
          }
        `),
      });

      // Cache should be empty now, no calls for resolvers since then
      expect(await cache.get(expectedCacheKey)).not.toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(1);

      // Running again query, cache should be filled and resolver should get called again
      await execute(executeOptions);
      expect(await cache.get(expectedCacheKey)).toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(2);
    });

    describe('Subfields', () => {
      it('Should cache queries including subfield arguments', async () => {
        const transform = new CacheTransform({
          apiName: 'test',
          importFn,
          config: [{ field: 'Query.user' }],
          cache,
          pubsub,
          baseDir,
        });
        const schemaWithCache = transform.transformSchema(schema);

        // First query should call resolver and fill cache
        const executeOptions1 = {
          schema: schemaWithCache,
          document: parse(/* GraphQL */ `
            query {
              user(id: 1) {
                friend(id: 2) {
                  id
                }
              }
            }
          `),
        };
        const { data: actual1 }: any = await execute(executeOptions1);
        expect(spies.Query.user.mock.calls.length).toBe(1);
        expect(actual1.user.friend.id).toBe('2');

        // Second query should call resolver and also fill cache
        const executeOptions2 = {
          schema: schemaWithCache,
          document: parse(/* GraphQL */ `
            query {
              user(id: 1) {
                friend(id: 3) {
                  id
                }
              }
            }
          `),
        };
        const { data: actual2 }: any = await execute(executeOptions2);
        expect(spies.Query.user.mock.calls.length).toBe(2);
        expect(actual2.user.friend.id).toBe('3');

        // Repeat both queries, no new calls for resolver
        const { data: repeat1 }: any = await execute(executeOptions1);
        const { data: repeat2 }: any = await execute(executeOptions2);
        expect(spies.Query.user.mock.calls.length).toBe(2);
        expect(repeat1.user.friend.id).toBe('2');
        expect(repeat2.user.friend.id).toBe('3');
      });
    });

    describe('Race condition', () => {
      it('should wait for local cache transform to finish writing the entry', async () => {
        const options: MeshTransformOptions<YamlConfig.CacheTransformConfig[]> = {
          apiName: 'test',
          importFn,
          config: [
            {
              field: 'Query.foo',
              cacheKey: 'random',
            },
          ],
          cache,
          pubsub,
          baseDir,
        };

        let callCount = 0;
        const schema = makeExecutableSchema({
          typeDefs: /* GraphQL */ `
            type Query {
              foo: String
            }
          `,
          resolvers: {
            Query: {
              foo: () => new Promise(resolve => setTimeout(() => resolve((callCount++).toString()), 300)),
            },
          },
        });
        const transform = new CacheTransform(options);
        const transformedSchema = transform.transformSchema(schema);
        const query = /* GraphQL */ `
          {
            foo1: foo
            foo2: foo
          }
        `;
        const result = await execute({
          schema: transformedSchema,
          document: parse(query),
        });

        expect(result.data.foo2).toBe(result.data.foo1);
      });

      it('should wait for other cache transform to finish writing the entry when delay >= safe threshold)', async () => {
        let callCount = 0;
        const options: MeshTransformOptions<YamlConfig.CacheTransformConfig[]> = {
          apiName: 'test',
          importFn,
          config: [
            {
              field: 'Query.foo',
              cacheKey: 'random',
            },
          ],
          cache,
          pubsub,
          baseDir,
        };
        function getNewSchema() {
          return makeExecutableSchema({
            typeDefs: /* GraphQL */ `
              type Query {
                foo: String
              }
            `,
            resolvers: {
              Query: {
                foo: async () => {
                  callCount++;
                  await new Promise(resolve => setTimeout(resolve, 300));
                  return 'FOO';
                },
              },
            },
          });
        }
        const transform1 = new CacheTransform(options);
        const transformedSchema1 = transform1.transformSchema(getNewSchema());
        const transform2 = new CacheTransform(options);
        const transformedSchema2 = transform2.transformSchema(getNewSchema());
        const query = /* GraphQL */ `
          {
            foo
          }
        `;

        await execute({
          schema: transformedSchema1,
          document: parse(query),
        });
        await wait(0);
        await execute({
          schema: transformedSchema2,
          document: parse(query),
        });

        expect(callCount).toBe(1);
      });

      it('should fail to wait for other cache transform to finish writing the entry when delay < safe threshold', async () => {
        let callCount = 0;
        const options: MeshTransformOptions<YamlConfig.CacheTransformConfig[]> = {
          apiName: 'test',
          importFn,
          config: [
            {
              field: 'Query.foo',
              cacheKey: 'random',
            },
          ],
          cache,
          pubsub,
          baseDir,
        };
        function getNewSchema() {
          return makeExecutableSchema({
            typeDefs: /* GraphQL */ `
              type Query {
                foo: String
              }
            `,
            resolvers: {
              Query: {
                foo: async () => {
                  callCount++;
                  await new Promise(resolve => setTimeout(resolve, 300));
                  return 'FOO';
                },
              },
            },
          });
        }
        const transform1 = new CacheTransform(options);
        const transformedSchema1 = transform1.transformSchema(getNewSchema());
        const transform2 = new CacheTransform(options);
        const transformedSchema2 = transform2.transformSchema(getNewSchema());
        const query = /* GraphQL */ `
          {
            foo
          }
        `;
        await Promise.all([
          execute({
            schema: transformedSchema1,
            document: parse(query),
          }),
          execute({
            schema: transformedSchema2,
            document: parse(query),
          }),
        ]);
        expect(callCount).toBe(2);
      });
    });
  });
});
Example #25
Source File: jsonOutput.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
describe('JSON output', function() {
  test(`should generate JSON output for a query with an enum variable`, function() {
    const context = compileFromSource(`
      query HeroName($episode: Episode) {
        hero(episode: $episode) {
          name
        }
      }
    `);

    const output = serializeToJSON(context);

    expect(output).toMatchSnapshot();
  });

  test(`should generate JSON output for a query with a nested selection set`, function() {
    const context = compileFromSource(`
      query HeroAndFriendsNames {
        hero {
          name
          friends {
            name
          }
        }
      }
    `);

    const output = serializeToJSON(context);

    expect(output).toMatchSnapshot();
  });

  test(`should generate JSON output for a query with a fragment spread and nested inline fragments`, function() {
    const context = compileFromSource(`
      query HeroAndDetails {
        hero {
          id
          ...CharacterDetails
        }
      }

      fragment CharacterDetails on Character {
        name
        ... on Droid {
          primaryFunction
        }
        ... on Human {
          height
        }
      }
    `);

    const output = serializeToJSON(context);

    expect(output).toMatchSnapshot();
  });

  test(`should generate JSON output for a mutation with an enum and an input object variable`, function() {
    const context = compileFromSource(`
      mutation CreateReview($episode: Episode, $review: ReviewInput) {
        createReview(episode: $episode, review: $review) {
          stars
          commentary
        }
      }
    `);

    const output = serializeToJSON(context);

    expect(output).toMatchSnapshot();
  });

  test(`should generate JSON output for an input object type with default field values`, function() {
    const schema = buildSchema(`
      type Query {
        someField(input: ComplexInput!): String!
      }

      input ComplexInput {
        string: String = "Hello"
        customScalar: Date = "2017-04-16"
        listOfString: [String] = ["test1", "test2", "test3"]
        listOfInt: [Int] = [1, 2, 3]
        listOfEnums: [Episode] = [JEDI, EMPIRE]
        listOfCustomScalar: [Date] = ["2017-04-16", "2017-04-17", "2017-04-18"]
      }

      scalar Date

      enum Episode {
        NEWHOPE
        EMPIRE
        JEDI
      }
    `);

    const context = compileFromSource(
      `
      query QueryWithComplexInput($input: ComplexInput) {
        someField(input: $input)
      }
      `,
      schema
    );

    const output = serializeToJSON(context);

    expect(output).toMatchSnapshot();
  });
});
Example #26
Source File: transform.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('filter', () => {
  const cache = new InMemoryLRUCache();
  const pubsub = new PubSub();
  const baseDir: string = undefined;
  const importFn: ImportFn = m => import(m);

  it('filters correctly with array of rules', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        posts: [Post]
      }

      type Post {
        id: ID
        message: String
        author: User
        comments: [Comment]
      }

      type Comment {
        id: ID
        message: String
      }

      type Query {
        user(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['!Comment', 'User.posts.{message, author}', 'Query.user.!pk'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
  posts: [Post]
}

type Post {
  id: ID
  message: String
  author: User
}

type Query {
  user(name: String, age: Int): User
}
`.trim()
    );
  });

  it('filters correctly with declarative syntax', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        posts: [Post]
      }

      type Post {
        id: ID
        message: String
        author: User
        comments: [Comment]
      }

      type Comment {
        id: ID
        message: String
      }

      type Query {
        user(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: { filters: ['!Comment', 'User.posts.{message, author}', 'Query.user.!pk'] },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
  posts: [Post]
}

type Post {
  id: ID
  message: String
  author: User
}

type Query {
  user(name: String, age: Int): User
}
`.trim()
    );
  });

  it("filters correctly on 'bare' mode", async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        posts: [Post]
        notifications: [Notification]
        mentions: [Mention]
      }

      type Post {
        id: ID
        message: String
        author: User
        comments: [Comment]
      }

      type Comment {
        id: ID
        message: String
      }

      type Notification {
        type: Int
        content: String
      }

      type Mention {
        reference: ID
        link: String
      }

      type LooseType {
        foo: String
        bar: String
      }

      type Query {
        user(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: {
            mode: 'bare',
            filters: [
              '!Comment',
              'Type.!LooseType',
              'Type.!{Notification, Mention}',
              'Query.user.!{notifications, mentions}',
              'User.posts.{message, author}',
              'Query.user.!pk',
            ],
          },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
  posts: [Post]
}

type Post {
  id: ID
  message: String
  author: User
}

type Query {
  user(name: String, age: Int): User
}
`.trim()
    );
  });

  it("filters correctly arguments on all fields in Type, with 'bare' mode", async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
      }

      type Query {
        userOne(pk: ID!, name: String, age: Int): User
        userTwo(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: {
            mode: 'bare',
            filters: ['Query.*.!pk'],
          },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
}

type Query {
  userOne(name: String, age: Int): User
  userTwo(name: String, age: Int): User
}
`.trim()
    );
  });

  it("filters correctly arguments on all fields in Type, plus specific field arguments; with 'bare' mode", async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
      }

      type Query {
        userOne(pk: ID!, name: String, age: Int): User
        userTwo(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: {
            mode: 'bare',
            filters: ['Query.*.!pk', 'Query.userOne.!age'],
          },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
}

type Query {
  userOne(name: String): User
  userTwo(name: String, age: Int): User
}
`.trim()
    );
  });

  it("filters correctly arguments on all fields in Type, with 'wrap' mode", async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
      }

      type Query {
        userOne(pk: ID!, name: String, age: Int): User
        userTwo(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: {
            mode: 'wrap',
            filters: ['Query.*.!pk'],
          },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
}

type Query {
  userOne(name: String, age: Int): User
  userTwo(name: String, age: Int): User
}
`.trim()
    );
  });

  it("filters correctly arguments on all fields in Type, plus specific field arguments; with 'wrap' mode", async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
      }

      type Query {
        userOne(pk: ID!, name: String, age: Int): User
        userTwo(pk: ID!, name: String, age: Int): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: {
            mode: 'wrap',
            filters: ['Query.*.!pk', 'Query.userOne.!age'],
          },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
}

type Query {
  userOne(name: String): User
  userTwo(name: String, age: Int): User
}
`.trim()
    );
  });

  it('should filter out fields', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        a: String
        b: String
        c: String
        d: String
        e: String
      }

      type Book {
        id: ID
        name: String
        authorId: ID
        author: User
      }

      type Query {
        user: User
        admin: User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['User.!{a,b,c,d,e}', 'Query.!admin', 'Book.{id,name,author}'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
}

type Book {
  id: ID
  name: String
  author: User
}

type Query {
  user: User
}
`.trim()
    );
  });

  it('should remove type with pruning if all fields are filtered out', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type Query {
        foo: String
        bar: String
      }
      type Mutation {
        baz: String
        qux: String
      }
    `);

    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['Mutation.!*'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });
    expect(printSchema(pruneSchema(schema)).trim()).toBe(
      /* GraphQL */ `
type Query {
  foo: String
  bar: String
}
`.trim()
    );
  });
  it('should filter out fields if array syntax is used only with one element', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        a: String
        b: String
        c: String
        d: String
        e: String
      }

      type Book {
        id: ID
        name: String
        authorId: ID
        author: User
      }

      type Query {
        user: User
        admin: User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['User.{id, username}', 'Query.!{admin}', 'Book.{id}'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  username: String
}

type Book {
  id: ID
}

type Query {
  user: User
}
`.trim()
    );
  });

  it('should filter out single type, with pending-deprecation syntax', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        a: String
        b: String
        c: String
        d: String
        e: String
      }

      type Book {
        id: ID
        name: String
        authorId: ID
        author: User
      }

      type Query {
        user: User
        admin: User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['!Book'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
  a: String
  b: String
  c: String
  d: String
  e: String
}

type Query {
  user: User
  admin: User
}
`.trim()
    );
  });

  it('filters out single type and multiple types rules', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        posts: [Post]
      }

      type Post {
        id: ID
        message: String
        author: User
      }

      type Comment {
        id: ID
        message: String
      }

      type Notification {
        type: Int
        content: String
      }

      type Mention {
        reference: ID
        link: String
      }

      type Query {
        user(id: ID!): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: { mode: 'bare', filters: ['Type.!Comment', 'Type.!{Notification, Mention}'] },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
  posts: [Post]
}

type Post {
  id: ID
  message: String
  author: User
}

type Query {
  user(id: ID!): User
}
`.trim()
    );
  });

  it('handles whitelist filtering for types correctly', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        posts: [Post]
      }

      type Post {
        id: ID
        message: String
        author: User
      }

      type Comment {
        id: ID
        message: String
      }

      type Notification {
        type: Int
        content: String
      }

      type Mention {
        reference: ID
        link: String
      }

      type Query {
        user(id: ID!): User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          // bizarre case, but logic should still work
          config: { mode: 'bare', filters: ['Type.{Query, User, Post, String, ID}'] },
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
  posts: [Post]
}

type Post {
  id: ID
  message: String
  author: User
}

type Query {
  user(id: ID!): User
}
`.trim()
    );
  });

  it('should filter out fields of filtered types', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
        a: String
        b: String
        c: String
        d: String
        e: String
      }

      type Book {
        id: ID
        name: String
        authorId: ID
        author: User
      }

      type Query {
        book: Book
        user: User
        admin: User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['!User'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    // TODO: temporary fix
    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type Book {
  id: ID
  name: String
  authorId: ID
}

type Query {
  book: Book
}
`.trim()
    );
  });

  it('should filter out directive fields of filtered types', async () => {
    let schema = buildSchema(/* GraphQL */ `
      input AuthRule {
        and: [AuthRule]
        or: [AuthRule]
        not: AuthRule
        rule: String
      }

      directive @auth(query: AuthRule, add: AuthRule, update: AuthRule, delete: AuthRule, role: String!) on OBJECT

      type User {
        id: ID
        name: String
        username: String
        a: String
        b: String
        c: String
        d: String
        e: String
      }

      type Book {
        id: ID
        name: String
        authorId: ID
        author: User
      }

      type Query {
        user: User
        admin: User
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['!AuthRule'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
directive @auth(role: String!) on OBJECT

type User {
  id: ID
  name: String
  username: String
  a: String
  b: String
  c: String
  d: String
  e: String
}

type Book {
  id: ID
  name: String
  authorId: ID
  author: User
}

type Query {
  user: User
  admin: User
}
`.trim()
    );
  });

  it('should filter out arguments of root field', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type User {
        id: ID
        name: String
        username: String
      }

      type Book {
        id: ID
        name: String
        author: User
      }

      type Query {
        user(pk: ID!, name: String, age: Int): User
        book(pk: ID!, title: String): Book
      }
    `);
    schema = wrapSchema({
      schema,
      transforms: [
        FilterSchemaTransform({
          config: ['Query.user.!{pk, age}', 'Query.book.title'],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    expect(printSchema(schema).trim()).toBe(
      /* GraphQL */ `
type User {
  id: ID
  name: String
  username: String
}

type Book {
  id: ID
  name: String
  author: User
}

type Query {
  user(name: String): User
  book(title: String): Book
}
`.trim()
    );
  });
});
Example #27
Source File: typeCase.ts    From amplify-codegen with Apache License 2.0 4 votes vote down vote up
describe('TypeCase', () => {
  it('should recursively include inline fragments with type conditions that match the parent type', () => {
    const context = compile(`
      query Hero {
        hero {
          id
          ... on Character {
            name
            ... on Character {
              id
              appearsIn
            }
            id
          }
        }
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['id', 'name', 'appearsIn']);

    expect(typeCase.variants).toHaveLength(0);

    expect(typeCase.exhaustiveVariants).toHaveLength(1);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human', 'Droid'], ['id', 'name', 'appearsIn']);
  });

  it('should recursively include fragment spreads with type conditions that match the parent type', () => {
    const context = compile(`
      query Hero {
        hero {
          id
          ...HeroDetails
        }
      }

      fragment HeroDetails on Character {
        name
        ...MoreHeroDetails
        id
      }

      fragment MoreHeroDetails on Character {
        appearsIn
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['id', 'name', 'appearsIn']);
    expect(typeCase.default.fragmentSpreads.map(fragmentSpread => fragmentSpread.fragmentName)).toEqual(['HeroDetails', 'MoreHeroDetails']);

    expect(typeCase.variants).toHaveLength(0);

    expect(typeCase.exhaustiveVariants).toHaveLength(1);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human', 'Droid'], ['id', 'name', 'appearsIn']);
  });

  it('should include fragment spreads when nested within inline fragments', () => {
    const context = compile(`
      query Hero {
        hero {
          ... on Character {
            ...CharacterName
          }
        }
      }

      fragment CharacterName on Character {
        name
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['name']);
    expect(typeCase.default.fragmentSpreads.map(fragmentSpread => fragmentSpread.fragmentName)).toEqual(['CharacterName']);

    expect(typeCase.variants).toHaveLength(0);
  });

  it('should only include fragment spreads once even if included twice in different subselections', () => {
    const context = compile(`
      query Hero {
        hero {
          friends {
            ...CharacterName
          }
          ... on Droid {
            friends {
              ...CharacterName
            }
          }
        }
      }

      fragment CharacterName on Character {
        name
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(collectAndMergeFields(typeCaseForSelectionSet(selectionSet).variants[0])[0]
      .selectionSet as SelectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['name']);
    expect(typeCase.default.fragmentSpreads.map(fragmentSpread => fragmentSpread.fragmentName)).toEqual(['CharacterName']);
  });

  it('should ignore type modifiers when matching the parent type', () => {
    const schema = buildSchema(`
      type Query {
        heroes: [Character]
      }

      interface Character {
        name: String!
      }

      type Human implements Character {
        name: String!
      }

      type Droid implements Character {
        name: String!
      }
    `);

    const context = compile(
      `
      query Hero {
        heroes {
          ... on Character {
            name
          }
        }
      }
    `,
      schema
    );

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['name']);

    expect(typeCase.variants).toHaveLength(0);

    expect(typeCase.exhaustiveVariants).toHaveLength(1);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human', 'Droid'], ['name']);
  });

  it('should merge fields from the default case into type conditions', () => {
    const context = compile(`
      query Hero {
        hero {
          name
          ... on Droid {
            primaryFunction
          }
          appearsIn
          ... on Human {
            height
          }
        }
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['name', 'appearsIn']);

    expect(typeCase.variants).toHaveLength(2);
    expect(typeCase.variants).toContainSelectionSetMatching(['Droid'], ['name', 'primaryFunction', 'appearsIn']);
    expect(typeCase.variants).toContainSelectionSetMatching(['Human'], ['name', 'appearsIn', 'height']);

    expect(typeCase.exhaustiveVariants).toHaveLength(2);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Droid'], ['name', 'primaryFunction', 'appearsIn']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human'], ['name', 'appearsIn', 'height']);
  });

  it(`should merge fields from type conditions with the same type`, () => {
    const context = compile(`
      query Hero {
        hero {
          name
          ... on Droid {
            primaryFunction
          }
          ... on Droid {
            appearsIn
          }
        }
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], ['name']);

    expect(typeCase.variants).toHaveLength(1);
    expect(typeCase.variants).toContainSelectionSetMatching(['Droid'], ['name', 'primaryFunction', 'appearsIn']);

    expect(typeCase.exhaustiveVariants).toHaveLength(2);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Droid'], ['name', 'primaryFunction', 'appearsIn']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human'], ['name']);
  });

  it('should inherit type condition when nesting an inline fragment in an inline fragment with a more specific type condition', () => {
    const context = compile(`
      query Hero {
        hero {
          ... on Droid {
            ... on Character {
              name
            }
          }
        }
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], []);

    expect(typeCase.variants).toHaveLength(1);
    expect(typeCase.variants).toContainSelectionSetMatching(['Droid'], ['name']);

    expect(typeCase.exhaustiveVariants).toHaveLength(2);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Droid'], ['name']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human'], []);
  });

  it('should not inherit type condition when nesting an inline fragment in an inline fragment with a less specific type condition', () => {
    const context = compile(`
      query Hero {
        hero {
          ... on Character {
            ... on Droid {
              name
            }
          }
        }
      }
    `);

    const selectionSet = (context.operations['Hero'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Human', 'Droid'], []);

    expect(typeCase.variants).toHaveLength(1);
    expect(typeCase.variants).toContainSelectionSetMatching(['Droid'], ['name']);

    expect(typeCase.exhaustiveVariants).toHaveLength(2);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Droid'], ['name']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Human'], []);
  });

  it('should merge fields from the parent case into nested type conditions', () => {
    const context = compile(
      `
      query Animal {
        animal {
          ... on Pet {
            name
            ... on WarmBlooded {
              bodyTemperature
            }
          }
        }
      }
    `,
      animalSchema
    );

    const selectionSet = (context.operations['Animal'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Cat', 'Bird', 'Fish', 'Crocodile'], []);

    expect(typeCase.variants).toHaveLength(2);
    expect(typeCase.variants).toContainSelectionSetMatching(['Cat', 'Bird'], ['name', 'bodyTemperature']);
    expect(typeCase.variants).toContainSelectionSetMatching(['Fish'], ['name']);

    expect(typeCase.exhaustiveVariants).toHaveLength(3);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Cat', 'Bird'], ['name', 'bodyTemperature']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Fish'], ['name']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Crocodile'], []);
  });

  it('should merge fields from the parent case into nested type conditions', () => {
    const context = compile(
      `
      query Animal {
        animal {
          ... on Pet {
            name
            ... on WarmBlooded {
              bodyTemperature
            }
          }
          ... on WarmBlooded {
            bodyTemperature
            ... on Pet {
              name
            }
          }
        }
      }
    `,
      animalSchema
    );

    const selectionSet = (context.operations['Animal'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Cat', 'Bird', 'Fish', 'Crocodile'], []);

    expect(typeCase.variants).toHaveLength(2);
    expect(typeCase.variants).toContainSelectionSetMatching(['Cat', 'Bird'], ['name', 'bodyTemperature']);
    expect(typeCase.variants).toContainSelectionSetMatching(['Fish'], ['name']);

    expect(typeCase.exhaustiveVariants).toHaveLength(3);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Cat', 'Bird'], ['name', 'bodyTemperature']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Fish'], ['name']);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Crocodile'], []);
  });

  it('should not keep type conditions when all possible objects match', () => {
    const context = compile(
      `
      query Animal {
        catOrBird {
          ... on Pet {
            name
            ... on WarmBlooded {
              bodyTemperature
            }
          }
        }
      }
    `,
      animalSchema
    );

    const selectionSet = (context.operations['Animal'].selectionSet.selections[0] as Field).selectionSet as SelectionSet;
    const typeCase = typeCaseForSelectionSet(selectionSet);

    expect(typeCase.default).toMatchSelectionSet(['Cat', 'Bird'], ['name', 'bodyTemperature']);
    expect(typeCase.variants).toHaveLength(0);

    expect(typeCase.exhaustiveVariants).toHaveLength(1);
    expect(typeCase.exhaustiveVariants).toContainSelectionSetMatching(['Cat', 'Bird'], ['name', 'bodyTemperature']);
  });
});
Example #28
Source File: transform.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('hoist', () => {
  const importFn = defaultImportFn;
  const schema = buildSchema(/* GraphQL */ `
    type Query {
      users(limit: Int!, page: Int): UserSearchResult
    }

    type UserSearchResult {
      page: Int!
      results: [User!]!
    }

    type User {
      id: ID!
      name: String!
    }
  `);
  let cache: InMemoryLRUCache;
  let pubsub: MeshPubSub;
  const baseDir: string = undefined;

  beforeEach(() => {
    cache = new InMemoryLRUCache();
    pubsub = new PubSub();
  });

  it('should hoist field with string pathConfig array', () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new HoistFieldTransform({
          config: [
            {
              typeName: 'Query',
              pathConfig: ['users', 'results'],
              newFieldName: 'users',
            },
          ],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    const queryType = newSchema.getType('Query') as GraphQLObjectType;
    expect(queryType).toBeDefined();

    const fields = queryType.getFields();
    expect(fields.users).toBeDefined();

    expect(printSchema(newSchema)).toMatchSnapshot();
  });

  it('should hoist field with mixed pathConfig array', () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new HoistFieldTransform({
          config: [
            {
              typeName: 'Query',
              pathConfig: [
                {
                  fieldName: 'users',
                  filterArgs: [],
                },
                'results',
              ],
              newFieldName: 'users',
            },
          ],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    const queryType = newSchema.getType('Query') as GraphQLObjectType;
    expect(queryType).toBeDefined();

    const fields = queryType.getFields();
    expect(fields.users).toBeDefined();

    expect(printSchema(newSchema)).toMatchSnapshot();
  });

  it('should hoist field and filter args with global flag', () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new HoistFieldTransform({
          config: [
            {
              typeName: 'Query',
              pathConfig: ['users', 'results'],
              newFieldName: 'users',
              filterArgsInPath: true,
            },
          ],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    const queryType = newSchema.getType('Query') as GraphQLObjectType;
    expect(queryType).toBeDefined();

    const fields = queryType.getFields();
    expect(fields.users).toBeDefined();

    const args = (fields.users as GraphQLField<any, any>).args;
    expect(args.length).toEqual(0);

    expect(printSchema(newSchema)).toMatchSnapshot();
  });

  it('should hoist field and filter individual args via pathConfig', () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new HoistFieldTransform({
          config: [
            {
              typeName: 'Query',
              pathConfig: [
                {
                  fieldName: 'users',
                  filterArgs: ['limit'],
                },
                'results',
              ],
              newFieldName: 'users',
            },
          ],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    const queryType = newSchema.getType('Query') as GraphQLObjectType;
    expect(queryType).toBeDefined();

    const fields = queryType.getFields();
    expect(fields.users).toBeDefined();

    const args = (fields.users as GraphQLField<any, any>).args;
    expect(args.length).toEqual(1);
    expect(args[0].name).toEqual('page');

    expect(printSchema(newSchema)).toMatchSnapshot();
  });

  it('should hoist field and filter individual args via pathConfig independent of global flag', () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new HoistFieldTransform({
          config: [
            {
              typeName: 'Query',
              pathConfig: [
                {
                  fieldName: 'users',
                  filterArgs: ['limit'],
                },
                'results',
              ],
              newFieldName: 'users',
              filterArgsInPath: true,
            },
          ],
          apiName: '',
          cache,
          pubsub,
          baseDir,
          importFn,
        }),
      ],
    });

    const queryType = newSchema.getType('Query') as GraphQLObjectType;
    expect(queryType).toBeDefined();

    const fields = queryType.getFields();
    expect(fields.users).toBeDefined();

    const args = (fields.users as GraphQLField<any, any>).args;
    expect(args.length).toEqual(1);
    expect(args[0].name).toEqual('page');

    expect(printSchema(newSchema)).toMatchSnapshot();
  });
});
Example #29
Source File: server.ts    From client-side-databases with Apache License 2.0 4 votes vote down vote up
export async function run() {
    const startData = exampleData;
    const state: {
        users: WithDeleted<User>[],
        messages: WithDeleted<Message>[]
    } = {
        users: startData.users.map(user => {
            const userWithDeleted = Object.assign({ deleted: false }, user);
            return userWithDeleted;
        }),
        messages: []
    };
    // to faster access all messages for a specific user,
    // we index the message by user-id here
    const messagesByUser: { [userId: string]: Message[] } = {};
    const getMessageArrayOfUser = (userId: string) => {
        if (!messagesByUser[userId]) {
            messagesByUser[userId] = [];
        }
        return messagesByUser[userId];
    };
    const addMessageToState = (message: Message) => {
        const messageWithDeleted = Object.assign({ deleted: false }, message);
        state.messages.push(messageWithDeleted);
        getMessageArrayOfUser(message.sender).push(message);
        getMessageArrayOfUser(message.reciever).push(message);
    };

    const app: Express = express();
    // cache options request
    app.options('*', (req, res, done) => {
        if (req.method === 'OPTIONS') {
            log('OPTIONS request');
            const headers = {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'POST, GET, PUT, OPTIONS, DELETE',
                'Access-Control-Allow-Credentials': 'false',
                'Access-Control-Max-Age': '86400',
                'Access-Control-Allow-Headers': 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'
            };
            res.writeHead(200, headers);
            res.end();
        } else {
            done();
        }
    });
    app.use(cors());


    const generatedSchema = graphQLSchemaFromRxSchema({
        users: {
            schema: RxUsersSchema,
            feedKeys: [
                'id',
                'createdAt'
            ],
            deletedFlag: 'deleted'
        },
        messages: {
            schema: RxMessagesSchema,
            feedKeys: [
                'id',
                'createdAt'
            ],
            deletedFlag: 'deleted'
        }
    });
    const graphQLSchema = generatedSchema.asString;
    console.log('GraphQL schema:');
    console.log(graphQLSchema);
    const schema = buildSchema(graphQLSchema);

    const pubsub = new PubSub();

    // The root provides a resolver function for each API endpoint
    const root = {
        info: () => 1,
        feedUsers: (args: any) => {
            log('## feedUsers()');
            console.dir(args);
            const users = state.users;
            const ret = filterForFeedResult(
                users,
                args.createdAt,
                args.id,
                args.limit
            );
            log('got ' + ret.length + ' users');
            return ret;
        },
        feedMessages: (args: any) => {
            log('## feedMessages()');
            const ret = filterForFeedResult(
                state.messages,
                args.createdAt,
                args.id,
                args.limit
            );
            console.dir(args);
            log('got ' + ret.length + ' messages');
            return ret;
        },
        setMessages: (args: {
            messages: Message[]
        }) => {
            const messages: Message[] = args.messages;
            messages.forEach(message => {
                log('## addMessage() ' + message.id + ' from ' + message.sender);

                // overwrite timestamp
                message.createdAt = unixInSeconds();

                // log(message);
                addMessageToState(message);

                pubsub.publish(
                    'changedMessages',
                    {
                        changedMessages: message
                    }
                );
            });
        },
        setUsers: (args: any) => {
            log('## registerUser()');
            const time = (new Date().getTime() / 1000);
            const user = {
                id: 'u' + time,
                name: args.name,
                createdAt: time,
                deleted: false
            };

            state.users.push(user);
            pubsub.publish(
                'changedUsers',
                {
                    changedUsers: user
                }
            );
            return user;
        },
        changedUsers: () => pubsub.asyncIterator('changedUsers'),
        changedMessages: () => pubsub.asyncIterator('changedMessages')
    };

    // add start-messages to state
    root.setMessages({
        messages: startData.messages
    });


    // server graphql-endpoint
    app.use(GRAPHQL_PATH, graphqlHTTP({
        schema,
        rootValue: root,
        graphiql: true,
    }));

    app.listen(GRAPHQL_PORT, () => {
        log('Started graphql-endpoint at http://localhost:' +
            GRAPHQL_PORT + GRAPHQL_PATH
        );
    });

    const appSubscription = express();
    appSubscription.use(cors);
    const serverSubscription = createServer(appSubscription);
    serverSubscription.listen(GRAPHQL_SUBSCRIPTION_PORT, () => {
        log(
            'Started graphql-subscription endpoint at http://localhost:' +
            GRAPHQL_SUBSCRIPTION_PORT + GRAPHQL_SUBSCRIPTION_PATH
        );
        const subServer = new SubscriptionServer(
            {
                execute,
                subscribe,
                schema,
                rootValue: root
            },
            {
                server: serverSubscription,
                path: GRAPHQL_SUBSCRIPTION_PATH,
            }
        );
        return subServer;
    });


    // comment this in for testing of the subscriptions
    /*
    setInterval(() => {
        const flag = new Date().getTime();
        pubsub.publish(
            'humanChanged',
            {
                humanChanged: {
                    id: 'foobar-' + flag,
                    name: 'name-' + flag
                }
            }
        );
        console.log('published humanChanged ' + flag);
    }, 1000);*/
}