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

@comicrelief/lambda-wrapper

v2.1.1

Published

Lambda wrapper for all Comic Relief Serverless Projects

Downloads

267

Readme

Lambda Wrapper

GitHub Actions semantic-release semantic-release

When writing Serverless applications, we have found ourselves replicating a lot of boilerplate code to do basic actions, such as reading request data or sending messages to SQS. The aim of this package is to provide a wrapper for our Lambda functions, to provide some level of dependency and configuration injection and to reduce time spent on project setup.

If you're coming from v1 and updating to v2, check out the v2 migration guide.

Getting started

Install via npm or Yarn:

npm i @comicrelief/lambda-wrapper
# or
yarn add @comicrelief/lambda-wrapper

You can then wrap your Lambda handler functions like this:

// src/action/Hello.ts
import lambdaWrapper, {
  ResponseModel,
  RequestService,
} from '@comicrelief/lambda-wrapper';

export default lambdaWrapper.wrap(async (di) => {
  const request = di.get(RequestService);
  return ResponseModel.generate(
    {},
    200,
    `hello ${request.get('name', 'nobody')}`,
  );
});

Here we've used the default export lambdaWrapper which is a preconfigured instance that can be used out of the box. You'll likely want to add your own dependencies and service config using the configure method:

// src/config/LambdaWrapper.ts
import lambdaWrapper from '@comicrelief/lambda-wrapper';

export default lambdaWrapper.configure({
  // your config goes here
});

configure returns a new Lambda Wrapper instance with the given configuration. You'll want to export it and then use this when wrapping your handler functions.

Read the next section to see what goes inside the config object!

If you want to start from scratch without the built-in dependencies, you can use the LambdaWrapper constructor directly.

// src/config/LambdaWrapper.ts
import { LambdaWrapper } from '@comicrelief/lambda-wrapper';

export default new LambdaWrapper({
  // your config goes here
});

Dependencies

Lambda Wrapper comes with some commonly used dependencies built in:

Access these via dependency injection. You've already seen an example of this where we got RequestService. Pass the dependency class to di.get() to get its instance:

export default lambdaWrapper.wrap(async (di) => {
  const request = di.get(RequestService);
  const sqs = di.get(SQSService);
  // ...
});

To add your own dependencies, first extend DependencyAwareClass.

// src/services/MyService.ts
import { DependencyAwareClass } from '@comicrelief/lambda-wrapper';

export default class MyService extends DependencyAwareClass {
  doSomething() {
    // ...
  }
}

If you need to override the constructor, it must take a DependencyInjection instance and pass it to super.

export default class MyService extends DependencyAwareClass {
  constructor(di: DependencyInjection) {
    super(di);
    // now do your other constructor stuff
  }
}

Then add it to your Lambda Wrapper configuration in the dependencies key.

// src/config/LambdaWrapper.ts
import lambdaWrapper from '@comicrelief/lambda-wrapper';

import MyService from '@/src/services/MyService';

export default lambdaWrapper.configure({
  dependencies: {
    MyService,
  },
});

Now you can use it inside your handler functions and other dependencies!

// src/action/DoSomething.ts
import lambdaWrapper from '@/src/config/LambdaWrapper';
import MyService from '@/src/services/MyService';

export default lambdaWrapper.wrap(async (di) => {
  di.get(MyService).doSomething();
});

Service config

Some dependencies need their own config. This goes in per-service keys within your Lambda Wrapper config. For an example, see SQSService which uses the sqs key.

export default lambdaWrapper.configure({
  dependencies: {
    // your dependencies
  },
  sqs: {
    // your SQSService config
  },
  // ... other configs ...
});

To use config with your own dependencies, you need to do three things:

  1. Define the key and type of your config object.

    Using SQSService as an example, we have the sqs key which has the SQSServiceConfig type:

    export interface SQSServiceConfig {
      queues?: Record<string, string>;
      queueConsumers?: Record<string, string>;
    }
  2. Define a type that can be applied to a Lambda Wrapper config.

    This simply combines the key and type defined in step 1. Conventionally we name these With... types.

    export interface WithSQSServiceConfig {
      sqs?: SQSServiceConfig;
    }

    In the case of SQSService, the sqs key is optional because this dependency is included by default and not all applications need it. If your dependency requires config in order to work, you can make this a required key.

  3. In your dependency constructor, cast the config to this type.

    export default class SQSService extends DependencyAwareClass {
      constructor(di: DependencyInjection) {
        super(di);
    
        const config = (this.di.config as WithSQSServiceConfig).sqs;
        // Bear in mind that because the `sqs` key is optional, the type of
        // `config` will be `SQSServiceConfig | undefined`. Take care when
        // accessing its properties! You can use optional chaining:
        const queues = config?.queues || {};
        // ...
      }
    }

When you go to configure your Lambda Wrapper, you can now include your dependency's config type in the generic for configure to get IntelliSense completions and type checking for your config keys.

lambdaWrapper.configure<WithSQSServiceConfig>({
  sqs: {
    queues: 42 // Oops! This will be flagged as a type error by TypeScript
  },
});

You can combine types for multiple dependencies if needed using &:

lambdaWrapper.configure<WithSQSServiceConfig & WithOtherServiceConfig>({
  sqs: {
    // SQSService config
  },
  other: {
    // OtherService config
  },
});

Monitoring

At Comic Relief we use Lumigo for monitoring and observability of our deployed services. Lambda Wrapper includes the Lumigo tracer to allow us to tag traces with custom labels and metrics (execution tags).

Lumigo integration works out-of-the-box with Lumigo's auto-trace feature. If you prefer manual tracing, enable it by setting LUMIGO_TRACER_TOKEN in your Lambda environment variables.

And if you don't use Lumigo, don't worry, their tracer will not be instantiated in your functions and no calls will be made to their servers unless LUMIGO_TRACER_TOKEN is set.

Notes

Lambda Wrapper's dependency injection relies on class names being preserved. If your build process includes minifying or uglifying your code, you'll need to disable these transformations.

In many of our projects we use serverless-webpack to bundle service code prior to deployment. To disable name mangling, set optimization.minimize to false in your webpack config:

// webpack.config.js
module.exports = {
  // ...
  optimization: {
    minimize: false,
  },

Development

Testing

Run yarn test to run the unit tests, and yarn test:types to run the type tests.

When writing a bugfix, start by writing a test that reproduces the problem. It should fail with the current version of Lambda Wrapper, and pass once you've implemented the fix.

When adding a feature, ensure it's covered by tests that adequately define its behaviour.

Linting

Run yarn lint to check code style complies to our standard. Many problems can be auto-fixed using yarn lint --fix.

Releases

Release management is automated using semantic-release.