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

@vyriy/stack

v0.7.5

Published

AWS CDK stack helpers for Vyriy infrastructure

Readme

@vyriy/stack

AWS CDK stack helpers for Vyriy projects.

Purpose

This package keeps small, reusable CDK construction helpers close to the AWS primitives they wrap. The helpers are intentionally thin: they provide calm defaults for common Vyriy infrastructure while leaving the full CDK prop objects available to callers.

Install

With npm:

npm install @vyriy/stack aws-cdk-lib

With Yarn:

yarn add @vyriy/stack aws-cdk-lib

The aws-cdk-lib package is listed because CDK apps and bin entrypoints use CDK stack, app, prop, and resource types directly.

API

  • s3.createBucket(scope, id, props?) creates a private S3 bucket with static-site-friendly defaults.
  • cf.createDefaultBehavior(bucket, options?) creates a CloudFront S3 origin behavior that redirects viewers to HTTPS.
  • cf.createWebsiteRedirectBehavior(bucket, options?) creates a CloudFront behavior for an S3 website redirect bucket.
  • cf.createCloudFrontFunction(scope, id, props) creates a CloudFront Function from inline JavaScript source.
  • cf.createIndexRewriteFunctionCode({ rootDomain, wwwDomain }) creates CloudFront Function source for www redirects and clean URL index rewrites.
  • cf.createFunctionAssociations(scope, id, { rootDomain, wwwDomain, ...props }) creates a viewer-request CloudFront Function association for www redirects and index rewrites.
  • cf.createDistribution(scope, id, props) creates a CloudFront distribution.
  • route53.getHostedZone(scope, id, props) looks up an existing Route 53 hosted zone.
  • route53.createARecord(scope, id, props) creates a Route 53 A record.
  • route53.createMxRecord(scope, id, props) creates a Route 53 MX record.
  • route53.createCloudFrontTarget(distribution) creates a Route 53 alias target for CloudFront.
  • acm.createCertificate(scope, id, props) creates an ACM certificate.
  • ses.createEmailReceiving(scope, id, props) creates an SES domain identity, receipt rule set, receipt rule, raw email bucket permissions, S3-to-Lambda notification, and optional SSM parameter read access.
  • deployment.createBucketDeployment(scope, id, props) deploys files to S3 with a 512 MB default memory limit.
  • deployment.createHtmlCacheControl() creates cache-control headers for HTML files that should revalidate before reuse.
  • deployment.createImmutableCacheControl(days?) creates long-lived immutable cache-control headers.
  • deployment.Source and deployment.CacheControl are re-exported from aws-cdk-lib/aws-s3-deployment.

Static Site Example

This example wires the package helpers into a static website stack:

  • find an existing hosted zone
  • create a private S3 bucket for site.com
  • create a DNS-validated ACM certificate
  • create a CloudFront distribution for the site
  • create a Route 53 alias record
  • deploy immutable assets and revalidating HTML files into the bucket
  • invalidate CloudFront after each deployment

CloudFront requires ACM certificates for custom aliases to be in us-east-1, so deploy this stack in us-east-1 or split the certificate into a dedicated us-east-1 stack.

import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
import type { Construct } from 'constructs';

import { stack } from '@vyriy/cdk';
import { acm, cf, deployment, route53, s3 } from '@vyriy/stack';
import { path } from '@vyriy/path';

stack(
  class StaticSiteStack extends Stack {
    constructor(scope: Construct, id: string, props: StackProps & { env: { account: string; region: string } }) {
      super(scope, id, props);

      const domain = 'site.com';

      const hostedZone = route53.getHostedZone(this, 'HostedZone', {
        domainName: domain,
      });

      const siteBucket = s3.createBucket(this, 'Bucket', {
        bucketName: domain,
      });

      const certificate = acm.createCertificate(this, 'Certificate', {
        domainName: domain,
        subjectAlternativeNames: [`*.${domain}`],
        validation: acm.CertificateValidation.fromDns(hostedZone),
      });

      const siteDistribution = cf.createDistribution(this, 'Distribution', {
        certificate,
        defaultBehavior: cf.createDefaultBehavior(siteBucket, {
          functionAssociations: cf.createFunctionAssociations(this, 'IndexRewriteFunction', {
            rootDomain: domain,
            wwwDomain: `www.${domain}`,
          }),
        }),
        defaultRootObject: 'index.html',
        domainNames: [domain, `www.${domain}`],
        errorResponses: [
          {
            httpStatus: 403,
            responseHttpStatus: 404,
            responsePagePath: '/404.html',
          },
          {
            httpStatus: 404,
            responseHttpStatus: 404,
            responsePagePath: '/404.html',
          },
        ],
      });

      route53.createARecord(this, 'RootRecord', {
        target: route53.createCloudFrontTarget(siteDistribution),
        zone: hostedZone,
      });

      const assetDeployment = deployment.createBucketDeployment(this, 'DeploySiteAssets', {
        cacheControl: deployment.createImmutableCacheControl(),
        destinationBucket: siteBucket,
        distribution: siteDistribution,
        exclude: ['index.html', '404.html'],
        distributionPaths: ['/*'],
        sources: [deployment.Source.asset(path('dist'))],
      });

      const htmlDeployment = deployment.createBucketDeployment(this, 'DeploySiteHtml', {
        cacheControl: deployment.createHtmlCacheControl(),
        destinationBucket: siteBucket,
        distribution: siteDistribution,
        distributionPaths: ['/*'],
        exclude: ['*'],
        include: ['index.html', '404.html'],
        prune: false,
        sources: [deployment.Source.asset(path('dist'))],
      });

      htmlDeployment.node.addDependency(assetDeployment);

      new CfnOutput(this, 'Account', { value: props.env.account });
      new CfnOutput(this, 'Region', { value: props.env.region });
      new CfnOutput(this, 'Tags', { value: JSON.stringify(props.tags ?? {}) });

      new CfnOutput(this, 'BucketName', { value: siteBucket.bucketName });

      new CfnOutput(this, 'DistributionDomainName', { value: siteDistribution.domainName });
      new CfnOutput(this, 'DistributionId', { value: siteDistribution.distributionId });
      new CfnOutput(this, 'DistributionUrl', { value: `https://${siteDistribution.domainName}/` });

      new CfnOutput(this, 'SiteUrl', { value: `https://${domain}/` });
    }
  },
);

SES Email Receiving Example

This example configures a domain to receive email through SES, store raw MIME messages in S3, and invoke a Lambda for application-specific processing such as sending a Telegram notification.

SES inbound receiving is region-limited, so deploy this in a region that supports SES email receiving and point your domain MX record to that regional SES inbound endpoint.

import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
import type { Construct } from 'constructs';

import { stack } from '@vyriy/cdk';
import { lambda, s3, ses } from '@vyriy/stack';
import { path } from '@vyriy/path';

stack(
  class EmailStack extends Stack {
    constructor(scope: Construct, id: string, props: StackProps & { env: { account: string; region: string } }) {
      super(scope, id, props);

      const domainName = 'site.com';
      const recipient = `consulting@${domainName}`;
      const processor = lambda.createLambda(this, 'EmailProcessor', {
        code: lambda.Code.fromAsset(path('dist', 'email-processor')),
        description: `Processes raw emails sent to ${recipient}`,
        environment: {
          RAW_EMAIL_PREFIX: 'incoming/',
          TELEGRAM_BOT_TOKEN_PARAMETER_NAME: '/site/consulting-email/telegram/bot-token',
          TELEGRAM_CHAT_ID_PARAMETER_NAME: '/site/consulting-email/telegram/chat-id',
        },
        handler: 'index.handler',
      });
      const emailBucket = s3.createBucket(this, 'RawEmailBucket');

      const emailReceiving = ses.createEmailReceiving(this, 'ConsultingEmail', {
        domainName,
        emailBucket,
        processor,
        rawEmailPrefix: 'incoming/',
        receiptRuleName: 'consulting',
        receiptRuleSetName: 'site-consulting-email',
        recipients: [recipient],
        ssmParameterNames: [
          '/site/consulting-email/telegram/bot-token',
          '/site/consulting-email/telegram/chat-id',
        ],
      });

      processor.addEnvironment('RAW_EMAIL_BUCKET_NAME', emailReceiving.emailBucket.bucketName);

      new CfnOutput(this, 'RawEmailBucketName', { value: emailReceiving.emailBucket.bucketName });
      new CfnOutput(this, 'ConsultingEmail', { value: recipient });
      new CfnOutput(this, 'EmailReceiptRuleSetName', { value: 'site-consulting-email' });
    }
  },
);

API Gateway Lambda Example

This example wires a Lambda-backed REST API to an API Gateway custom domain:

  • find an existing hosted zone
  • create a DNS-validated ACM certificate
  • create a Lambda from built API assets
  • create an edge REST API with proxy Lambda integration
  • map the API to a custom domain
  • create a Route 53 alias record for the API Gateway domain

Edge API Gateway custom domains use edge-optimized CloudFront distributions, so keep the certificate in us-east-1.

import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
import { ApiGatewayDomain } from 'aws-cdk-lib/aws-route53-targets';
import type { Construct } from 'constructs';

import { stack } from '@vyriy/cdk';
import { acm, apigateway, lambda, route53 } from '@vyriy/stack';
import { getPackage } from '@vyriy/package';
import { path } from '@vyriy/path';

stack(
  class ApiStack extends Stack {
    constructor(scope: Construct, id: string, props: StackProps & { env: { account: string; region: string } }) {
      super(scope, id, props);

      const domain = 'api.site.com';
      const hostedZoneName = 'site.com';
      const { description } = getPackage();

      const zone = route53.getHostedZone(this, 'HostedZone', {
        zoneName: hostedZoneName,
      });

      const certificate = acm.createCertificate(this, 'Certificate', {
        domainName: domain,
        validation: acm.CertificateValidation.fromDns(zone),
      });

      const apiLambda = lambda.createLambda(this, 'ApiLambda', {
        code: lambda.Code.fromAsset(path('dist', 'api')),
        description,
        functionName: 'api',
        handler: 'index.handler',
      });

      const apiGateway = apigateway.createApiGateway(this, 'ApiGateway', {
        defaultIntegration: apigateway.createIntegration(apiLambda, { proxy: true }),
        description,
        restApiName: `${id}-api-gateway`,
      });

      const apiDomainName = apigateway.createDomainName(this, 'ApiGatewayDomain', {
        certificate,
        domainName: domain,
      });

      apigateway.createBasePathMapping(this, 'ApiBasePathMapping', {
        domainName: apiDomainName,
        restApi: apiGateway,
      });

      route53.createARecord(this, 'ARecord', {
        recordName: domain,
        target: route53.RecordTarget.fromAlias(new ApiGatewayDomain(apiDomainName)),
        zone,
      });

      new CfnOutput(this, 'Account', { value: props.env.account });
      new CfnOutput(this, 'Region', { value: props.env.region });

      new CfnOutput(this, 'LambdaName', { value: apiLambda.functionName });

      new CfnOutput(this, 'ApiGatewayId', { value: apiGateway.restApiId });
      new CfnOutput(this, 'ApiGatewayUrl', { value: apiGateway.url });

      new CfnOutput(this, 'ApiUrl', { value: `https://${domain}/` });
    }
  },
);

Fargate Worker Example

This example wires a Fargate task foundation to an existing VPC:

  • read the VPC id from the environment
  • load package metadata for reusable descriptions
  • create a security group for the workload
  • create an ECR repository for the task image
  • create an ECS cluster in the VPC
  • create a Fargate task definition with AWS Logs configured
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
import type { Construct } from 'constructs';

import { stack } from '@vyriy/cdk';
import { getVpc } from '@vyriy/env';
import { getPackage } from '@vyriy/package';
import { ec2, ecr, ecs } from '@vyriy/stack';

stack(
  class FargateWorkerStack extends Stack {
    constructor(scope: Construct, id: string, props: StackProps & { env: { account: string; region: string } }) {
      super(scope, id, props);

      const { description } = getPackage();
      const vpc = ec2.findVpc(this, 'Vpc', { vpcId: getVpc() });
      const subnets = vpc.privateSubnets;

      const securityGroup = ec2.createSecurityGroup(this, 'SecurityGroup', {
        allowAllOutbound: true,
        description,
        securityGroupName: `${id}-${vpc.vpcId}-securityGroup`,
        vpc,
      });

      const repository = ecr.createRepository(this, 'SyncRepository', {
        repositoryName: `${id}-repository`,
      });

      const cluster = ecs.createCluster(this, 'FargateCluster', {
        clusterName: `${id}-cluster`,
        vpc,
      });

      const taskDefinition = ecs.createTaskDefinition(this, 'SyncFargateTaskDefinition', {
        cpu: 256,
        family: `${id}-task-definition`,
        memoryLimitMiB: 512,
      });

      taskDefinition.addContainer('SyncTaskDefinition', {
        cpu: 256,
        image: ecs.ContainerImage.fromEcrRepository(repository),
        logging: ecs.setLogs({ streamPrefix: `${id}-cluster-logs` }),
        memoryLimitMiB: 512,
      });

      new CfnOutput(this, 'Account', { value: props.env.account });
      new CfnOutput(this, 'Region', { value: props.env.region });

      new CfnOutput(this, 'VpcId', { value: vpc.vpcId });
      new CfnOutput(this, 'PrivateSubnetIds', { value: subnets.map((subnet) => subnet.subnetId).join(',') });
      new CfnOutput(this, 'SecurityGroupId', { value: securityGroup.securityGroupId });

      new CfnOutput(this, 'RepositoryName', { value: repository.repositoryName });
      new CfnOutput(this, 'ClusterName', { value: cluster.clusterName });

      new CfnOutput(this, 'TaskDefinitionArn', { value: taskDefinition.taskDefinitionArn });
    }
  },
);

DynamoDB Access Examples

These examples show small DynamoDB table definitions and common IAM access patterns for Lambda and Fargate workloads.

Create two country tables:

import { dynamodb } from '@vyriy/stack';

const countriesIndexTable = dynamodb.createTable(this, 'CountriesIndexTable', {
  partitionKey: {
    name: 'key',
    type: dynamodb.AttributeType.STRING,
  },
  tableName: `${id}-countries-index`,
});

const countriesTable = dynamodb.createTable(this, 'CountriesTable', {
  partitionKey: {
    name: 'key',
    type: dynamodb.AttributeType.STRING,
  },
  sortKey: {
    name: 'code',
    type: dynamodb.AttributeType.STRING,
  },
  tableName: `${id}-countries`,
});

Grant Lambda access to the tables:

countriesIndexTable.grantReadData(apiLambda);
countriesTable.grantReadWriteData(apiLambda);

// Use full access only for maintenance or sync jobs that really need table-level control.
countriesIndexTable.grantFullAccess(apiLambda);

Grant Fargate task access through the task role:

countriesIndexTable.grantReadData(taskDefinition.taskRole);
countriesTable.grantReadWriteData(taskDefinition.taskRole);

// Full access is useful for controlled backfill or indexing tasks.
countriesIndexTable.grantFullAccess(taskDefinition.taskRole);

Allow a Lambda to run the Fargate task definition:

taskDefinition.grantRun(apiLambda);

Messaging And Parameter Examples

These examples show simple SNS, SQS, and SSM helper usage for application stacks.

Create an SNS topic and grant publishers/subscribers:

import { sns } from '@vyriy/stack';

const eventsTopic = sns.createTopic(this, 'EventsTopic', {
  topicName: `${id}-events`,
});

eventsTopic.grantPublish(apiLambda);
eventsTopic.grantSubscribe(taskDefinition.taskRole);

Import an existing SNS topic:

const importedEventsTopic = sns.fromTopicArn(
  this,
  'ImportedEventsTopic',
  `arn:aws:sns:${this.region}:${this.account}:${id}-events`,
);

importedEventsTopic.grantPublish(taskDefinition.taskRole);

Create an SQS queue and allow Lambda/Fargate workers to use it:

import { Duration } from 'aws-cdk-lib';

import { sqs } from '@vyriy/stack';

const jobsQueue = sqs.createQueue(this, 'JobsQueue', {
  queueName: `${id}-jobs`,
  visibilityTimeout: Duration.seconds(60),
});

jobsQueue.grantSendMessages(apiLambda);
jobsQueue.grantConsumeMessages(taskDefinition.taskRole);

Import an existing SQS queue:

const importedJobsQueue = sqs.fromQueueAttributes(this, 'ImportedJobsQueue', {
  queueArn: `arn:aws:sqs:${this.region}:${this.account}:${id}-jobs`,
  queueUrl: `https://sqs.${this.region}.amazonaws.com/${this.account}/${id}-jobs`,
});

importedJobsQueue.grantConsumeMessages(apiLambda);

Read SSM parameter values and import parameters:

import { ssm } from '@vyriy/stack';

const manualApiKey = ssm.createManualStringParameter(this, 'ManualApiKey', {
  description: 'API key filled after first deploy',
  initialValue: 'CHANGE_ME',
  parameterName: `/${id}/api/key`,
});

const apiBaseUrl = ssm.valueForStringParameter(this, `/${id}/api/base-url`);

const importedApiKey = ssm.fromSecureStringParameterAttributes(this, 'ImportedApiKey', {
  parameterName: `/${id}/api/key`,
  version: 1,
});

manualApiKey.grantRead(apiLambda);
importedApiKey.grantRead(apiLambda);
importedApiKey.grantRead(taskDefinition.taskRole);

createManualStringParameter writes the initial value only when CloudFormation creates the resource. Later stack updates do not overwrite a manually edited value, and stack deletion removes the parameter.

Pass SSM parameter values into Lambda and Fargate environment variables:

const apiBaseUrlParameterName = `/${id}/api/base-url`;
const apiKeyParameterName = `/${id}/api/key`;

const apiBaseUrl = ssm.valueForStringParameter(this, apiBaseUrlParameterName);
const apiKey = ssm.valueForStringParameter(this, apiKeyParameterName);

const apiLambda = lambda.createLambda(this, 'ApiLambda', {
  code: lambda.Code.fromAsset(path('dist', 'api')),
  environment: {
    API_BASE_URL: apiBaseUrl,
    API_KEY_PARAMETER_NAME: apiKeyParameterName,
  },
  handler: 'index.handler',
});

taskDefinition.addContainer('WorkerContainer', {
  environment: {
    API_BASE_URL: apiBaseUrl,
    API_KEY: apiKey,
  },
  image: ecs.ContainerImage.fromEcrRepository(repository),
  logging: ecs.setLogs({ streamPrefix: `${id}-worker-logs` }),
});

const importedApiKeyForEnv = ssm.fromStringParameterName(this, 'ImportedApiKeyForEnv', apiKeyParameterName);

importedApiKeyForEnv.grantRead(apiLambda);
importedApiKeyForEnv.grantRead(taskDefinition.taskRole);