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 🙏

© 2026 – Pkg Stats / Ryan Hefner

aws-cdk-lambdaless-custom-resource

v0.0.19

Published

AWS CDK construct library for creating CloudFormation custom resources without Lambda functions, using Step Functions instead

Readme

AWS CDK Lambdaless Custom Resource

AWS CDK construct library for creating CloudFormation custom resources without Lambda functions, using Step Functions instead.

Why?

When building custom resources with AWS CDK, Lambda functions are the standard approach. But Lambda runtimes reach end-of-life, and you'll start receiving EOL notifications for stacks you deployed long ago and forgot about.

If it's your own custom resource, you can update it. But what about custom resources embedded in construct libraries someone else wrote? When the EOL notice arrives, updating them can be a hassle.

This library uses AWS Step Functions instead of Lambda, eliminating runtime maintenance entirely. Step Functions has no runtime EOL — once deployed, it just works.

When to use?

  • Stacks you want to deploy and forget
  • Sample code or workshops you publish and don't actively maintain
  • Construct libraries you distribute to others

In these cases, using this library means neither you nor your users will ever receive Lambda runtime EOL notifications.

How expressive is it?

Step Functions is less flexible than Lambda, but with AWS SDK integrations, HTTP API calls, and JSONata expressions, you can handle most use cases. See the examples for real-world patterns.

Installation

npm install aws-cdk-lambdaless-custom-resource

Usage

Basic Example

import { CustomResourceFlow, LambdalessCustomResource } from 'aws-cdk-lambdaless-custom-resource';
import { Pass, StateMachine, DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions';
import * as cdk from 'aws-cdk-lib';

const flow = new CustomResourceFlow(this, 'ExampleFlow', {
  onCreate: Pass.jsonata(this, 'Create', {
    outputs: {
      PhysicalResourceId: 'example-id',
      Data: {
        message: '{% $states.input.ResourceProperties.myProperty %}',
      },
    },
  }),
  onUpdate: Pass.jsonata(this, 'Update', {
    outputs: {
      Data: {
        message: '{% $states.input.ResourceProperties.myProperty %}',
      },
    },
  }),
  onDelete: Pass.jsonata(this, 'Delete'),
});

const customResource = new LambdalessCustomResource(this, 'MyCustomResource', {
  stateMachine: new StateMachine(this, 'StateMachine', {
    definitionBody: DefinitionBody.fromChainable(flow),
  }),
  properties: {
    myProperty: 'Hello, World!',
  },
});

// Access custom resource attributes
new cdk.CfnOutput(this, 'Message', {
  value: customResource.getAttString('message'),
});

Examples

See the examples directory for real-world patterns.

ASL Templates

The templates directory contains the ASL (Amazon States Language) definition used by the internal orchestrator. This can be a useful reference when building your own state machines.

State Machine Requirements

The state machine you pass to LambdalessCustomResource is invoked by the internal orchestrator whenever CloudFormation sends a Create, Update, or Delete event. You can use CustomResourceFlow to route these events, or build your own state machine from scratch.

Input

Your state machine receives the following input. When using CustomResourceFlow, these are also available as variables (e.g. $RequestType).

| Variable | Description | |---|---| | $RequestType | "Create", "Update", or "Delete" | | $ResourceProperties | Properties passed to the custom resource | | $OldResourceProperties | Previous properties (Update only, otherwise null) | | $PhysicalResourceId | Physical resource ID (Create: null) | | $LogicalResourceId | Logical resource ID | | $StackId | CloudFormation stack ID | | $RequestId | Unique request ID |

Output

Your state machine must return a JSON object. The orchestrator uses this to respond to CloudFormation on your behalf.

{
  "PhysicalResourceId": "unique-id",
  "Data": {
    "key1": "value1",
    "key2": "value2"
  },
  "NoEcho": false
}

| Field | Required | Description | |---|---|---| | PhysicalResourceId | Yes (on Create) | Unique identifier for the resource. If omitted, RequestId is used. | | Data | No | Key-value pairs accessible via getAtt() / getAttString(). Nested objects are flattened with dot notation. | | NoEcho | No | If true, masks the output in CloudFormation events. Defaults to false. |

Access outputs in your CDK code:

customResource.getAttString('key1')  // Returns 'value1'

Architecture

graph LR
    CFn[CloudFormation] -->|serviceToken| SNS[SNS Topic]
    SNS --> SQS[SQS Queue]
    SQS --> Pipes[EventBridge Pipes]
    Pipes -->|invoke| Express[Express State Machine]
    Express -->|startExecution| Standard[Your State Machine]
    Express -->|describeExecution| Standard
    Express -->|HttpInvoke| CFn
    Express -.->|timeout| Pipes

LambdalessWaitCondition

For long-running async operations that exceed the Custom Resource's 1-hour timeout, use LambdalessWaitCondition. It integrates with CloudFormation WaitCondition to support operations up to 12 hours.

Your state machine uses the same input/output format as LambdalessCustomResource. The output Data should typically be an explicit object with one key-value pair. If you need to return multiple values, see Advanced: Multiple key-value pairs.

const flow = new CustomResourceFlow(this, 'Flow', {
  onCreate: // ... your long-running workflow ...
    Pass.jsonata(this, 'Done', {
      outputs: {
        PhysicalResourceId: '{% $jobId %}',
        Data: {
          s3Prefix: '{% $artifactS3Prefix %}',
        },
      },
    }),
  onDelete: Pass.jsonata(this, 'Delete'),
});

const waitCondition = new LambdalessWaitCondition(this, 'CompileJob', {
  stateMachine: new StateMachine(this, 'StateMachine', {
    definitionBody: DefinitionBody.fromChainable(flow),
  }),
  timeout: Duration.hours(12),
  properties: {
    jobDefinitionArn: jobDefinition.jobDefinitionArn,
    jobQueueArn: jobQueue.jobQueueArn,
  },
});

const s3Prefix = waitCondition.getAttString('s3Prefix');

Advanced: Multiple key-value pairs

LambdalessWaitCondition uses CloudFormation WaitCondition internally. In a standard WaitCondition, count specifies the number of signals to wait for (e.g., from multiple EC2 instances), each identified by a UniqueId.

This construct repurposes that mechanism: each key in your Data object is treated as one signal, with the key name as UniqueId and the value as the signal's Data. This means:

  • count must exactly match the number of keys in Data. If count is too high, the stack waits forever. If too low, some values cannot be referenced.
  • Always return an explicit Data object with known keys. Never return an API response object directly — unknown or extra keys will cause count to mismatch.
// ✅ Good: explicit key-value pairs, count matches
Pass.jsonata(this, 'Done', {
  outputs: {
    PhysicalResourceId: '{% $jobId %}',
    Data: {
      s3Prefix: '{% $artifactS3Prefix %}',
      version: '{% $modelVersion %}',
    },
  },
});

const waitCondition = new LambdalessWaitCondition(this, 'WC', {
  stateMachine,
  count: 2, // Matches the 2 keys in Data
});

const s3Prefix = waitCondition.getAttString('s3Prefix');
const version = waitCondition.getAttString('version');
// ❌ Bad: passing API response directly — keys are unknown and count will mismatch
Pass.jsonata(this, 'Done', {
  outputs: {
    PhysicalResourceId: '{% $jobId %}',
    Data: '{% $states.result %}', // Don't do this!
  },
});

License

Apache-2.0