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

appsync-resolverscript

v0.1.0

Published

Write AWS appsync resolvers in JavaScript.

Downloads

13

Readme

appsync-resolverscript

Typed JavaScript abstraction for AWS AppSync resolver templates, supporting AWS CDK and Pulumi.

Build Status codecov Contributor Covenant

import { PulumiResolver, sendAppSyncRequest, operations, context } from 'appsync-resolverscript'

new PulumiResolver('getUserResolver', {
  apiId: horselingApi.id,
  dataSource: databaseDataSource.name,
  type: 'Query',
  field: 'getUser',
  template: sendAppSyncRequest(operations.dynamodb.getItem({ id: context.args.id }))
})
  • Declare AWS AppSync resolver Velocity templates in JavaScript or TypeScript.
  • Use type-checked JavaScript rather than VTL.
  • Publish patterns as NPM packages and re-use them.
  • Works with AWS CDK and Pulumi.

More Examples

import { sendAppSyncRequest, operations } from 'appsync-resolverscript'

const templateBuilder = operations.dynamodb.getItem(({ context }) => ({ id: context.args.id }))
const { requestTemplate, responseTemplate } = templateBuilder

...is equivalent to...

import { sendAppSyncRequest, vtl } from 'appsync-resolverscript'

const templateBuilder = sendAppSyncRequest(({ context, util }) => ({
  operation: 'GetItem',
  version: '2017-02-28',
  key: {
    id: util.dynamodb.toDynamoDBJson(context.args.id)
  }
})).then(({ context, util }) => util.toJson(context.result))
const { requestTemplate, responseTemplate } = templateBuilder

...is equivalent to...

import { sendAppSyncRequest, vtl } from 'appsync-resolverscript'

const templateBuilder = sendAppSyncRequest({
  operation: 'GetItem',
  version: '2017-02-28',
  key: {
    id: vtl`$util.dynamodb.toDynamoDBJson($context.args.id)`
  }
}).then(vtl`$util.toJson($context.result)`)
const { requestTemplate, responseTemplate } = templateBuilder

...is equivalent to...

const requestTemplate = `{
  "operation": "GetItem",
  "version": "2017-02-28",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($context.args.id)
  }
}`
const responseTemplate = '$util.toJson($context.result)'

Installation

With Yarn:

$ yarn add --dev appsync-resolverscript

Or NPM:

$ npm install appsync-resolverscript --save-dev

Usage

Define the Request Template

Use the sendAppSyncRequest(request) function to define the request template. E.g.

// Defines: {}
const templateBuilder = sendAppSyncRequest({})

request can be a primitive or object that will be stringified, a Velocity fragment, or a function that returns any of the above. Multiple arguments are concatenated. It returns a builder object that can be used to chain the response template.

Note that if you return a raw string as your template definition, it will be stringified to JSON. E.g.

// Defines: "#return"
sendAppSyncRequest('#return')

Define the Response Template

From the builder returned by the request, use the then(response) function to define the response template, in the same way as the request. Again, the builder is returned for further function chaining. E.g.

// Defines: {}
templateBuilder.then({})

Defining a response is optional, as it defaults to:

$util.toJson($context.result)

Velocity Fragments

For any value in the request or response definition, you can suspend JSON stringification and provide raw VTL markup by using the vtl template literal. E.g.

// Defines: #return
sendAppSyncRequest(vtl`#return`)

Alternatively, use an instance of VelocityFragment.

You can jump back into JSON by embedding the stringify() method, but make sure you use the one from this package - it handles fragments, functions and variables correctly. E.g.

// Defines: #set( $person = { "name": "Bob" })
sendAppSyncRequest(vtl`#set( $person = ${stringify({ name: 'Bob' })})`)

Function Templates

The request or response templates can be defined using a function that returns the template structure. This function gets passed the Velocity context as a parameter, providing access to variables and functions. You can implement any logic you like in the function, but remember, any JavaScript conditional logic or loops are executed at deploy time, not when the template is executed - the template definition is the value returned by the function. E.g.

// If useKey === true, defines  : $context.args.key
// If useKey === false, defines : { "id": $context.args.id }
sendAppSyncRequest(() => {
  const useKey = // ... get from somewhere.
  if (useKey) {
    return vtl`$context.args.key`
  } else {
    return {
      id: vtl`$context.args.id`
    }
  }
})

AppSync Functions

All of the standard AppSync functions are available via the Velocity context passed to function templates (* this is still WIP). Parameters passed to AppSync functions are stringified to JSON. E.g.

// Defines: $context.util.toJson(1, "two")
sendAppSyncRequest(velocityContext => velocityContext.util.toJson(1, 'two'))

You may want to use object-destructuring on the velocityContext parameter to make this a little less verbose, especially if you are calling functions in many places:

// Defines: '$context.util.toJson(1, "two")'
sendAppSyncRequest(({ util }) => util.toJson(1, 'two'))

AppSync functions can also be imported at module scope, which allows you avoid the boilerplate of defining your request or response as a function:

import { sendAppSyncRequest, util } from 'appsync-resolverscript'

// Defines: '$context.util.toJson(1, "two")'
sendAppSyncRequest(util.toJson(1, 'two'))

AppSync Context Variables

The standard AppSync context object is available as a context property on the Velocity context passed to function templates (* this is still WIP). Sorry, overloading the term context is a bit confusing. E.g.

// Defines: { "id": $context.args.id }
sendAppSyncRequest(velocityContext => {
  id: velocityContext.context.args.id
})

or

// Defines: { "id": $context.args.id }
sendAppSyncRequest(({ context }) => ({ id: context.args.id }))

Once you get to an args value or result, you can navigate through to any sub-properties (although the sub-properties are not type-checked, as the TypeScript doesn't know the shape of your args or results). E.g.

// Defines: { "id": $context.args.id }
then(({ context, util }) => util.toJson(context.result.items))

Note that the ctx abbreviation is not supported.

The AppSync context object can also be imported at module scope, The downside of this approach is that the context object is a superset of request, response and pipeline function contexts, and so not all properties are appropriate for all mapping types (e.g. context.result could be mis-used in a request). The advantage is that it allows you avoid the boilerplate of defining your request or response as a function. E.g.

import { sendAppSyncRequest, context } from 'appsync-resolverscript'

// Defines: { "id": $context.args.id }
sendAppSyncRequest({ id: context.args.id })

Velocity Types

If you are working in Typescript, AppSync function parameters and return values are typed. So too are the non-dynamic properties from the Velocity context. E.g.

// Typescript errors:
// - defaultIfNullOrEmpty(string, string)
// - $context.identity.claims is an object
// - false is a boolean
sendAppSyncRequest(util.defaultIfNullOrEmpty(context.identity.claims, false))

The default type of a Velocity fragment is AnyType, which matches any other type. This means that you don't have to worry about the type of any fragments, although you can choose to type them if you want to enable the type-checking. E.g.

// Typescript errors:
// - defaultIfNullOrEmpty(string, string)
// - $context.identity.claims is an object
sendAppSyncRequest(util.defaultIfNullOrEmpty(vtl<object>`$context.identity.claims`, '[]'))

Get the VTL Markup

The builder returned by sendAppSyncRequest(request) has requestTemplate and responseTemplate properties to get the declared request and response mapping template as Velocity Template Language markup. E.g.

const templateBuilder = sendAppSyncRequest('myRequest').then('myResponse')
assert.deepEqual('"myRequest"', templateBuilder.requestTemplate)
assert.deepEqual('"myResponse"', templateBuilder.responseTemplate)

Note: you don't generally need to use these properties if you use the CDK or Pulumi-specific classes.

Operations

Higher-level abstractions of the AppSync operations are available under operations. They all accept functions and VTL markup, and return a mapping definition that can be used in sendAppSyncRequest().

DynamoDB

GetItem:

const templateBuilder = sentAppSyncRequest(operations.dynamodb.getItem({ id: context.args.id }))

Pulumi

Pulumi is supported via the PulumiResolver class. It has the same usage as aws.appsync.Resolver, but the requestTemplate and responseTemplate properties are replaced with template: ResolverTemplateBuilder, and can be used as follows:

new PulumiResolver('getUserResolver', {
  apiId: horselingApi.id,
  dataSource: databaseDataSource.name,
  type: 'Query',
  field: 'getUser',
  template: sendAppSyncRequest(operations.dynamodb.getItem({ id: context.args.id }))
})

Roadmap

  • Add typing to fragments and util functions.
  • Add typing where possible to the appsync context.
  • Pre-populate Velocity variables for Unit and Pipeline templates.
  • Add ability to set Velocity variables.
  • Complete mapping of all core util functions.
  • Complete mapping of all dynamodb functions.
  • Add higher-level abstractions for DynamoDB API.
  • Support sendRequest().catch().
  • Support map() and filter() on variables.
  • Add explicit support for pipeline resolvers.
  • Review namespacing of modules - don't import everything at root.
  • Add explicit support for AWS CDK.
  • Explore using JSX to build more complex templates from components.
  • Add examples.

Contributions

Contributions with unit tests are welcome, but please raise an issue and get feedback before spending any significant effort.