npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

graphql-data-generator

v0.1.0

Published

A tool to generate objects and operation mocks from a GraphQL schema. Allows defining _transforms_ to simplify common variations.

Downloads

66

Readme

graphql-data-generator

A tool to generate objects and operation mocks from a GraphQL schema. Allows defining transforms to simplify common variations.

Example

First generate types and type lists:

npx graphql-data-generator --schema src/graphql/schema.graphql --outfile src/util/test/types.ts

Then consume these types and initialize a builder:

import { readFileSync } from "node:fs";
import { init, Patch } from "npm:graphql-data-generator";
import {
  Inputs,
  inputs,
  Mutation,
  mutations,
  queries,
  Query,
  Subscription,
  subscriptions,
  Types,
  types,
} from "./types.ts";

const schema = readFileSync("graphql/schema.graphql", "utf-8");

const scalars = {
  ID: (typename) => `${typename.toLowerCase()}-0`,
  String: "",
};

export const build = init<Query, Mutation, Subscription, Types, Inputs>(
  schema,
  queries,
  mutations,
  subscriptions,
  types,
  inputs,
  scalars,
)(() => ({
  // Can define transforms for objects
  User: {
    // `default` automatically applies to all User objects
    default: { profilePicture: (u) => `https://example.com/${u.id}.png` },
    // Can invoke with `build.User.withPost()` or `build.User().withPost()`
    withPost: (_p, post: Patch<Types["Post"]> = {}) => ({
      posts: { next: post },
    }),
  },
  // Can define transforms for operations
  CreatePost: {
    withAuthorId: (_, authorId: string) => ({
      variables: { input: { authorId } },
      data: { createPost: { author: { id: authorId } } },
    }),
  },
}));

After which you can build objects and operations in your tests:

import { build } from "util/tests/build.ts";

const user1 = build.User().withPost();
// Can override properties
const user2 = build.User({ id: "user-2", email: (u) => u.email + "2" });
// `patch` is a built-in transform while `withPost` was defined above
const user3 = user1.patch({
  id: "user-3",
  // `last` is special property for arrays to modify the last element in the array. If one does not exist it is created
  // `next` is a special property for arrays to append a new item to the array
  posts: { last: { author: { id: "user-3" } }, next: {} },
});

const createPost = build.CreatePost({ data: { createPost: { id: "post-id" } } })
  .withAuthorId("user-3");

Patches

A patch is similar to a DeepPartial with a few extensions. First, functions can be passed instead of literal properties. These functions will be invoked during instantiation and will receieve the previous host value as a property:

type Thing = { foo: string };
type ThingPatch = Patch<Thing>;
// Can exclude properties
const patch1: ThingPatch = {};
// Can specify them
const patch2: ThingPatch = { foo: "ok" };
// undefined will be ignored
const patch3: ThingPatch = { foo: undefined };
// Can use a function for more dynamic values
const patch4: ThingPatch = { foo: (prev: Thing) => `${prev.foo}2` };

Patch also has added semantics for arrays, including an object notation:

type Container = { values: string[] };
type ContainerPatch = Patch<Container>;
// Directly set index 1
const patch1: ContainerPatch = { values: { 1: "ok" } };
// `last` will modify the last element in the array. If the array is empty,
// instantiates a new element.
const patch2: ContainerPatch = { values: { last: "ok" } };
// `next` instantiates a new element and appends it to the array.
const patch3: ContainerPatch = { values: { next: "ok" } };
// `length` can be used to truncate or instantiate new elements
const patch4: ContainerPatch = { values: { length: 0 } };
// An array can be directly used. Will truncate extra elements.
const patch5: ContainerPatch = { values: ["ok"] };

Transforms

graphql-data-generator creates objects and operations by applying patches in sequence. A patch is similar to a DeepPartial, but supports functions for each property and has . Transforms are a mechanism to define shorthands for common patches for particular objects or operations. There are several built in transforms:

Objects:

  • default: A special transform that is automatically called for all instantations.
  • patch: Accepts a list of patches

Operations:

  • patch: Accepts a list of patches
  • variables: Accepts an operation variable patch
  • data: Accepts an operation data patch

When defining custom transforms, the default transform has special meaning: it will be automatically applied as the first aptch to all instances.

Extra

The init function supports a 6th optional generic parameter, Extra, which allows defining extra properties for operation mocks, passable in operation patches. This is helpful to support extra Apollo-related properties or custom logic. Extra properties will always be optional in patches and the final object and will not be patched in but simply merged, such as by Object.assign.

Example: Adding an extra optional property to bypass an assertion a mock is used

const build = init<
  Query,
  Mutation,
  Subscription,
  Types,
  Inputs,
  { optional: boolean }
>(
  schema,
  queries,
  mutations,
  subscriptions,
  types,
  inputs,
  scalars,
)(() => ({}));

build.CreatePost({ optional: true }).optional; // true

finalizeOperation

The init's final parmaeter, options, supports a finalizeOperation key. This is used as final step when building an operation and acts as a generic transform on the final mock itself, which can be useful to attach spies or when building interactivity with other GQL tools.

Exmaple: Enforcing a mock is used in Apollo & Jest

const build = init<Query, Mutation, Subscription, Types, Inputs>(
  schema,
  queries,
  mutations,
  subscriptions,
  types,
  inputs,
  scalars,
  {
    finalizeOperation: (op) => {
      const fn = Object.assign(
        jest.fn(() => op.result),
        op.result,
      ) as typeof op["result"];
      op.result = fn;
      return op;
    },
  },
)(() => ({}));

const createPost = build.CreatePost();
// ...
expect(createPost.result).toHaveBeenCalled();