Chaining queries and mutations
Using the Pipeline service you can run several queries and mutations sequentially through a single request.
Tutorial steps
To craft a pipeline, you'll need to:
- Activate Pipeline service
- Add a new field
categoryID
toProduct
type - Create a skeleton of Pipeline file
- Define
resolver
- Apply changes
- Confirm the creation of the pipeline
1. Activate Pipeline service
Before we get into crafting pipelines, we first need to enable the Pipeline
service.
To activate the Pipeline
service, uncomment the Pipeline
entry in the app.cue
file as the following.
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> ...</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">tailor.build/template/services/pipeline</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">application.#Spec </span><span style="color: var(--shiki-token-keyword)">&</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> ...</span></span>
<span><span style="color: var(--shiki-color-text)"> subgraphs: [</span></span>
<span><span style="color: var(--shiki-color-text)"> ...</span></span>
<span><span style="color: var(--shiki-color-text)"> {type: common.#Pipeline</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> name: pipeline.namespace}</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> ]</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
Now, the Pipeline service is enabled. For more information, please refer to the Activating Services guide.
2. Add a new field categoryID
to Product
type
Create Category
type by referring to Adding new data model tutorial. Next, Add categoryID
to Product
type
<span><span style="color: var(--shiki-token-keyword)">package</span><span style="color: var(--shiki-color-text)"> master</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">github.com/tailor-platform/tailorctl/schema/v2/tailordb</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">tailor.build/template/services/tailordb</span><span style="color: var(--shiki-color-text)">:permissions</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">Product: tailordb.#Type </span><span style="color: var(--shiki-token-keyword)">&</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">Product</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">Product model</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Fields: {</span></span>
<span><span style="color: var(--shiki-color-text)"> ...</span></span>
<span><span style="color: var(--shiki-color-text)"> categoryID: {</span></span>
<span><span style="color: var(--shiki-color-text)"> Type: tailordb.#TypeUUID</span></span>
<span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">category ID</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> TypePermission: permissions.editorAccess</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span></span>
3. Create a skeleton of Pipeline file
Let's create a skeleton of the services/pipeline.cue
file:
<span></span>
<span><span style="color: var(--shiki-token-keyword)">package</span><span style="color: var(--shiki-color-text)"> pipeline</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">github.com/tailor-platform/tailorctl/schema/v2/pipeline</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">tailor.build/template/services/pipeline/resolvers</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">tailor.build/template/environment</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">pipeline.#Spec </span><span style="color: var(--shiki-token-keyword)">&</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> Namespace: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">my-pipeline</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Resolvers: [</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// this is where we will implement the queries and mutations chains.</span></span>
<span><span style="color: var(--shiki-color-text)"> resolvers.createCategoryForProduct</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> ]</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
4. Define createCategoryForProduct
resolver
The pipeline we're going to craft will provide a mutation that creates a new Category
and Product
record using the input.
Locate resolvers folder in the services/pipeline
folder and create createCategoryForProduct.cue file.
<span><span style="color: var(--shiki-token-keyword)">package</span><span style="color: var(--shiki-color-text)"> resolvers</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">github.com/tailor-platform/tailorctl/schema/v2/pipeline</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">tailor.build/template/services/pipeline</span><span style="color: var(--shiki-color-text)">:settings</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">createCategoryForProduct: pipeline.#Resolver </span><span style="color: var(--shiki-token-keyword)">&</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> Authorization: pipeline.#AuthInsecure</span></span>
<span><span style="color: var(--shiki-color-text)"> Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">createCategoryForProduct</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> This API is an alternative to createCategory. It creates a Category record and then creates Product record. Use this API if you want to create a Category record and Product record in one API call.</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> </span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-color-text)"> Inputs: [</span></span>
<span><span style="color: var(--shiki-color-text)"> { Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">input</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Required: </span><span style="color: var(--shiki-token-constant)">true</span></span>
<span><span style="color: var(--shiki-color-text)"> Type: {</span></span>
<span><span style="color: var(--shiki-color-text)"> Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">createCategoryForProductInput</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Fields: [</span></span>
<span><span style="color: var(--shiki-color-text)"> {Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">categoryName</span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Type: pipeline.String</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Required: </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> {Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">productTitle</span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Type: pipeline.String</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Required: </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> {Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">productDescription</span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Type: pipeline.String</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> Required: </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> ]</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> ]</span></span>
<span><span style="color: var(--shiki-color-text)"> Response: { Type: pipeline.ID }</span></span>
<span><span style="color: var(--shiki-color-text)"> PostScript: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">context.pipeline.createProduct.id</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Pipelines: []</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
Here you can define the details of your pipeline resolver.
The Inputs
is an array of the input types and Response
is the output type of the pipeline resolver.
In this example, the createCategoryForProduct
pipeline resolver accepts an input of type createCategoryForProductInput
.
Caveats
Subscription
operations are not supported, onlyMutation
andQuery
may be used. For more details of the properties inpipeline.#Resolver
, please refer to the Pipeline Reference.
Next, we're going to add the steps this specific pipeline resolver will go through.
Step 1: Create category
record based on the categoryName.
In the Pipelines
array, let's add the first step: creating a category.
<span><span style="color: var(--shiki-color-text)">{</span></span>
<span><span style="color: var(--shiki-color-text)"> Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">createCategory</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">Create a category.</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Invoker: settings.adminInvoker</span></span>
<span><span style="color: var(--shiki-color-text)"> PreScript: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">context.args.input</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Operation: pipeline.#GraphqlOperation </span><span style="color: var(--shiki-token-keyword)">&</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> Query: </span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> mutation createCategory ($categoryName: String!) {</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> createCategory (input: { name: $categoryName }) {</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> id</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> }</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> }</span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> PostScript: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">args.createCategory</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span></span>
In PreScript
we can transform the arguments that are going to be passed to the GraphQL query.
context.args
is an object containing the arguments passed to the pipeline resolver.
As we defined in the Inputs
property, the input
property is going to be an object of type createCategoryForProductInput
.
The value of the Query
property is the GraphQL query that is going to be executed.
In PostScript
we can transform the result of the query, which is going to be assigned to args
.
Here we do not need to change the shape or contents of the result so we pass the result of the createCategory
query to the next step.
Specifically, the next step would receive an object called args
with the following properties:
<span><span style="color: var(--shiki-color-text)">{</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">"id"</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"<- uuid of the category ->"</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
There are 2 ways to access the result value of an already executed pipeline step: args
and context.pipeline.{PIPELINE_NAME}.{PROPERTY_NAME}
.
In most cases, we recommend using context.pipeline.{PIPELINE_NAME}.{PROPERTY_NAME}
because it is accessible to all pipeline steps that occur after the execution of the {PIPELINE_NAME}
step.
However, args
is only accessible in the pipeline step immediately following the previous step. args
holds the result of the PostScript from the previous step.
Here is an example that demonstrates the content of args after each step in the pipeline:
- step A
- step B (args contains step A post script data)
- step C (args contains step B post script data)
Step 2: Use the data in the createCategory
as inputs for creating a new Product
record.
In the second step, we will create a Product
record for the category created in step 1.
<span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> Name: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">createProduct</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Description: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">Create a new Product record with the Category id created in the previous step.</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)"> Invoker: settings.adminInvoker</span></span>
<span><span style="color: var(--shiki-color-text)"> PreScript: </span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> {</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> "categoryID": context.pipeline.createCategory.id,</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> "title": context.args.input.productTitle,</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> "description": context.args.input.productDescription,</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> }</span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-color-text)"> Operation: pipeline.#GraphqlOperation </span><span style="color: var(--shiki-token-keyword)">&</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> Query: </span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> mutation createProduct ($categoryID: ID!, $title: String!, $description: String!) {</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> createProduct (input: {categoryID: $categoryID, title: $title, description: $description}) {</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> id</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> }</span></span>
<span><span style="color: var(--shiki-token-string-expression)"> }</span><span style="color: var(--shiki-color-text)">"""</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> PostScript: </span><span style="color: var(--shiki-color-text)">"</span><span style="color: var(--shiki-token-string-expression)">args.createProduct</span><span style="color: var(--shiki-color-text)">"</span></span>
<span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span></span>
We dynamically crafted the input of the GraphQL query in PreScript
, setting the categoryID property with the created category.
Finally, in postScript
we return the result of the createProduct
mutation.
5. Apply changes
Now that our pipeline is ready, we can deploy it using:
<span><span style="color: var(--shiki-token-function)">tailorctl</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">workspace</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">apply</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">-m</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">./workspace.cue</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">-a</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">pipeline</span></span>
<span></span>
6. Confirm the creation of the pipeline
You can view the created pipeline in the Console.
In the console, select GraphQL Playground
to call the pipeline using a mutation such as:
<span><span style="color: var(--shiki-token-keyword)">mutation</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> createCategoryForProduct(input: </span></span>
<span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">categoryName</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"Jackets"</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">productTitle</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"Recycled Polyester Jacket"</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">productDescription</span><span style="color: var(--shiki-color-text)">: </span><span style="color: var(--shiki-token-string-expression)">"Warm, lightweight jacket made from recycled polyester"</span></span>
<span><span style="color: var(--shiki-color-text)"> })</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
After calling the pipeline, you can view the created Product
record in the console.
You can now view the resolver's execution logs in the console. Select the Execution Logs
tab to see the list of logs.
Click on View Details
to see the input data for each pipeline step.
Further Information
In this tutorial we only scratched the surface of what's possible to do using the Pipeline service. For a complete reference, please see the Pipeline Reference.