Mutating Data

Data in the store is always mutated via the mutate method on a Paper instance by passing in a Mutation Transaction callback. Within the Mutation Transaction callback there are several operations available to support being able to make changes easily to the store, even custom ones can be added.

mutate and the Mutate Transaction callback

To make any changes call the mutate method on the Paper instance and provide a Mutate Transaction callback.

Operations can be destructured from the first argument provided in the Mutation Transaction callback.

For example, an Actor document could be created within mutate by using the create operation. In this example only create is being destructured for use but any combination of operations can be used with the callback (see more of the library-provided operations below).

With a GraphQL Schema:

schema {
query: Query
}
type Query {
allFilms: [Film!]!
}
type Film {
title: String!
actors: [Actor!]!
}
type Actor {
name: String!
}

The following Mutate Transaction callback will create a Document of the GraphQL Actor type.

await paper.mutate(({ create }) => {
create('Actor', {
name: 'Julia Roberts'
});
});

Note: mutate returns a promise and the transaction callback is not considered executed until the promise is fulfilled. Calls to mutate will process transaction callbacks in the order they are called.

All changes within a Mutation Transaction callback will be validated via Validators after the transaction to ensure the new version of the DocumentStore is consistent.

Transaction Operations

Out of the box the following operations can be destructured within the callback:

create, find, remove, clone, getStore, queueEvent.

await paper.mutate(({ create, find, remove, clone, getStore, queueEvent }) => {
// do something within the callback
});

Creating custom operations can be helpful for creating common functional mutations on the GraphQL Paper DocumentStore or to provide common helpers that are useful within a Transaction Callback.

create

await paper.mutate(({ create }) => {
const julia = create('Actor', {
name: 'Julia Roberts'
});
});

The first argument is the GraphQL type for the document and the second is an object representing its data, mapping GraphQL fields to the object properties.

Creating a Documented with Connections

The create operation supports the ability to create connections through either by a nested object or explicitly through the property on the document, both of which are covered below.

find

In order to make changes to documents it's important to have access to a version of the document that can be mutated. If there is access to a read-only/frozen/stale document in scope, a mutable version can be looked up via find.

let existingDocument;
await paper.mutate(({ find }) => {
const mutableVersion = find(existingDocument);
});

remove

To remove a Document from the store use the remove operation.

await paper.mutate(({ remove }) => {
remove(document);
});

clone

Use the clone operation to create a new document that copies the properties and connections of an existing document.

await paper.mutate(({ clone }) => {
const newDocument = clone(document);
});

getStore

This operation gives the current mutable version of the DocumentStore available for mutating within the Mutation Transaction callback. This is useful for when access to underlying DocumentStore data structure and its Documents is required. It can also be useful to query by using typical javascript methods, for example:

await paper.mutate(({ getStore }) => {
const store = getStore();
// Get the `Actor` document for "Julia Roberts" using available
// javascript array methods
const julia = store.Actor.find(({ name }) => name === 'Julia Roberts');
});

If common modifications are being done via getStore consider making a custom operation.

queueEvent

Use the queueEvent operation to queue an event to be dispatched after the transaction is complete. The queueEvent takes an instance of Event.

await paper.mutate(({ queueEvent }) => {
queueEvent(new Event('meow', { /* custom event data */ }));
});

Creating Connections Between Documents

A Connection is used to create a relationship between Documents where one GraphQL type references another GraphQL type in the GraphQL schema.

A Document reference can be:

  • one-to-one, ie: one film can have one leading actor:
type Film {
leadingActor: Actor
}
  • one-to-many, ie: one film can have many actors:
type Film {
leadingActors: [Actor]
}

Note: Non-null (denoted by a !, ie: Actor!, [Actor!]!, [Actor!], [Actor]!) variations of these also work and are validated.

Note: Connections are one direction. If "Document A" is connected to "Document B" and "Document B" is also connected to "Document A" then two connections must be defined explicitly. There is no automatic reflexive assumptions or setup done between connections (although a custom operation could be created to handle these cases).

Creating Connections via Document Properties

Within a Mutate Transaction callback changes can be made to any documents and their properties.

One-to-One Connections

To create a one-way one-to-one connection between a document and another, assign the property to a Document, see below where the leadingActor property is connected by assigning the jeffGoldblum document.

await paper.mutate(({ create }) => {
const jeffGoldblum = create('Actor', {
name: 'Jeff Goldblum'
});
// as a property within `create`
const jurassicPark = create('Film', {
name: 'Jurassic Park',
leadingActor: jeffGoldblum
});
// or assigned after
const lifeAquatic = create('Film', {
name: 'The Life Aquatic'
});
lifeAquatic.leadingActor = jeffGoldbum;
});

One-to-Many Connections

To create a one-way one-to-many connection reference documents on the property via an Array, this works with new and existing documents.

await paper.mutate(({ create }) => {
const anjelicaHuston = create('Actor', {
name: 'Anjelica Huston'
});
const owenWilson = create('Actor', {
name: 'Owen Wilson'
});
// on the `actors` property within `create`
const theRoyalTenebaums = create('Film', {
title: 'The Royal Tenebaums',
actors: [anjelicaHuston, owenWilson]
})
// or assigned after via `push` to an array
const theLifeAquatic = create('Film', {
title: 'The Life Aquatic'
});
// This works assuming it's a non-null list:
// (ie: `actors: [Actor]!` or `actors: [Actor!]!`.
//
// Otherwise the array needs to be created first since it could be null:
// `theLifeAquatic.actors = theLifeAquatic.actors ?? [];`
//
// see note below for more details
theLifeAquatic.actors.push(anjelicaHuston, owenWilson);
});

Note: While less typical in GraphQL Schemas, if a one-to-many property can nullable (ie: actors: [Actor] without an ! outside the list) then it's important to make sure you are working with an array before pushing to it. The ?? can help in this case. If working with a non-null list ([Actor]! or [Actor!]!) then it will already be an array by default.

await paper.mutate(({ create }) => {
film.actors = film.actors ?? [];
film.actors.push(newActor);
});

Creating Connections within create via Nesting

One powerful technique is to use the create operation with a nested object that includes its connections. This nesting will work recursively. Other documents that have already been created can be included, too.

await paper.mutate(({ create }) => {
// documents created outside of nesting can be used within nesting, too
const scarlettJohansson = create('Actor', { name: 'Scarlett Johansson' });
const isleOfDogs = create('Film', {
title: 'Isle of Dogs',
actors: [
scarlettJohansson,
{ name: 'Jeff Goldblum' },
{ name: 'Tilda Swinton' },
{ name: 'Bill Murray' },
{ name: 'Bryan Cranston' },
]
});
});

This nested create will end up creating a Film document and four Actor documents, skipping creating scarlettJohansson because the Actor document was already created but it will still be included as a connection.

Returning Data Outside the Mutate Transaction callback

It's also very useful to return documents that have been used or created within a mutate transaction to be referenced afterwards. This can be done by returning a document, an array of documents, or an object with documents values, from the Mutate Transaction callback. See Returning Documents from Mutation Transactions for examples.