@vendia/block-subscription-handler

A library to simplify the handling of Vendia Share Block Notifications in AWS Lambda

Usage no npm install needed!

<script type="module">
  import vendiaBlockSubscriptionHandler from 'https://cdn.skypack.dev/@vendia/block-subscription-handler';
</script>

README

@vendia/block-subscription-handler

A TypeScript library to simplify the handling of Vendia Share Block Notifications in AWS Lambda

What is it?

This library helps Vendia Share Customers build integrations using Block Notifications. Inside a Share Uni (universal application), any mutating changes (add,create,update,etc.) result in a block being written to the ledger supporting the Uni. When a block is written, notifications are emitted and can be consumed by a number of methods like AWS Lambda, providing a great opportunity for integrations.

See: Vendia Share Integrations for details regarding the configuration and permissions needed to use an AWS Lambda function with Vendia Share Unis as it is not covered in this README

The Problem?

When subscribing to block notifications from a Share Uni, the only information available regards the block itself and not the data that relates to it.

The Solution

This library parses block notifications and can provide data relating to the block through two methods. The first involves parsing the Graphql mutations that resulted in the block creation. The second involves introspecting these mutations to dynamically query the related objects from the Uni's world state. The How It Works section below shows these processes in more detail.

Usage

Install the module

//Typescript
import * as blockSubscriptionHandler from '@vendia/block-subscription-handler';

//CommonJS
const blockSubscriptionHandler = require('@vendia/block-subscription-handler');

Functions

Getting Mutations

Parse Amazon SNS or SQS messages for Block Notification Mutations

Using the library's GetMutationsFromSnsBlockEvent or GetMutationsFromSqsBlockEvent functions, the SNS or SQS messages containing block notifications can easily be parsed within the AWS Lambda service to return the GraphQL Mutations related to the block. This will require the ability to query the Uni that emitted the notifications.

The GetMutationsFromSnsBlockEvent function requires three arguments:

Argument Description
event The SNS event object presented to an AWS Lambda function
vendiaShareURL The Uni's GraphQL URL
vendiaShareKey The Uni's GraphQL API Key

The GetMutationsFromSqsBlockEvent function requires three arguments:

Argument Description
event The SQS event object presented to an AWS Lambda function
vendiaShareURL The Uni's GraphQL URL
vendiaShareKey The Uni's GraphQL API Key

Getting Block Data

Sometimes it is necessary to retrieve mutations along with Block-level fields like Owner and Commit Time. In these cases, parsing the entire block from SQS or SNS messages can be achieved with the GetBlockFromSqsBlockEvent and GetBlockFromSnsBlockEvent functions

The GetBlockFromSqsBlockEvent and GetBlockFromSnsBlockEvent functions accept the same inputs as the above GetMutationsFromSnsBlockEvent and GetMutationsFromSqsBlockEvent functions. Instead of returning a list of string mutations, these functions return an object with the following interface:

{
   _TX: [
      Owner: string,
      Mutations: ReadonlyArray<string>,
      TxId: string,
   ]
   CommitTime: string;
}

ParseMutation

Parse the object data for each mutation

The ParseMutation function is the fastest way to retrieve a typed object from the GraphQL mutation. This function uses only the Graphql Mutation AST to build a typed object. This is useful for mutations with operations like Add, Create, and Put. In the case of Update mutations, which are allowed to include partial data, the GetMutationObjectFromAPI function may be more desirable as it queries the API for all fields of a specific type regardless of their use in an Update.

The ParseMutation function requires one argument:

Argument Description
mutation A string mutation

Example Output

//input mutation
"addtopList(id:\"01797be0-cb8d-9bad-aa08-cc8356c11f95\",input: {innerList: [{name: \"innerListName\", object: {objectName: \"ObjectName\"}}], name: \"listName\"}){error}"

//output
{"__typename":"Event","arguments":{"id":"b4de7525-623b-11eb-a0cb-0db0d645b658","input":{"animal_id":"b434d448-623b-11eb-afea-59074c0526d3","organization_id":"6fe94056-5bd4-11eb-a9fc-0bb70a7f9c77","timestamp":1611929411261,"node_created":"Node-2","type":"intake","nested":{"thing":["intake"]},"sub_type":"Stray/OTC","location_description":"","three_legged":false,"address1":"","address2":"","city":"","state":"","zipcode":"","geo_location":[0,1]}},"operation":"add"}

GetMutationObjectFromAPI

Get the object data via GraphQL API Introspection

The GetMutationObjectFromAPI function uses GraphQL introspection to dynamically build a Get query for the type referenced in a mutation. This has the advantage of querying all fields on a type regardless of its mention in the mutation itself. This function requires access the to Uni's API and makes several API calls (one per type or nested type, and one for the Query).

The GetMutationObjectFromAPI function requires three arguments:

Argument Description
mutation A string mutation
vendiaShareURL The Uni's GraphQL URL
vendiaShareKey The Uni's GraphQL API Key

Example Output

//input mutation
"addtopList(id:\"01797be0-cb8d-9bad-aa08-cc8356c11f95\",input: {innerList: [{name: \"innerListName\", object: {objectName: \"ObjectName\"}}], name: \"listName\"}){error}"

//output
{"arguments":{"id":"01797be0-cb8d-9bad-aa08-cc8356c11f95","innerList":[{"name":"innerListName","object":{"objectName":"ObjectName"}}],"name":"listName"},"operation":"add","__typename":"topList"}

Full Example

import * as blockSubscriptionHandler from '@vendia/block-subscription-handler';

//or for commonjs
//const blockSubscriptionHandler = require('@vendia/block-subscription-handler');

const url = process.env.vendiaShareURL;
const key = process.env.vendiaShareKey;

exports.handler = async function (event) {
  console.log('EVENT' + JSON.stringify(event));

  //Parse mutations from AWS Lambda Event
  //Note an object like {CommitTime:string, Mutations[...string...]} is returned
  const Mutations =
    await blockSubscriptionHandler.GetMutationsFromSnsBlockEvent(
      event,
      url,
      key
    );

  /* If using  SQS and not the default SNS 
 await blockSubscriptionHandler.GetMutationsFromSqsBlockEvent(
      event,
      url,
      key
    );
*/
  console.log('Mutations ' + JSON.stringify(mutationData));
  console.log('Commit Time ' + mutationData.CommitTime);
  await Promise.all(
    Mutations.map(async function (m) {
      try {
        console.log('Processing mutation ' + m);
        const blockFromShare =
          await blockSubscriptionHandler.GetMutationObjectFromAPI(m, url, key);

        //Using ParseMutation
        const blockFromAST = blockSubscriptionHandler.ParseMutation(m);

        console.log('Block from AST Parsing: ' + JSON.stringify(blockFromAST));
        console.log('Block from API Query: ' + JSON.stringify(blockFromShare));
      } catch (ex) {
        console.error('Error ->' + ex);
      }
    })
  );
};

How it Works

This section details the inner working of each function used to parse mutations

ParseMutation

The ParseMutation function uses only the GraphQL AST of a mutation to generate a typed object. This approach uses the GraphQL-js Library to traverse the AST tree and build an typed object based upon the arguments in the mutation.

GetMutationObjectFromAPI

The GetMutationObjectFromAPI function uses GraphQL introspection to build a new Get Query for the type referenced by a graphql mutation. This process involves several steps:

  • Step 1: Parse the mutation for the Graphql Type Name and the Identifier reported in the Block mutation

  • Step 2: Introspect the input object type via Graphql Queries

  • Step 3: Observe any nested types and recursively query them via Graphql

  • Step 4: Generate a new Query to retrieve the type and its nested types and fields

  • Step 5: Execute the Query and return a typed object based upon the result.