From ea4f46f5149690c73cf34e4d6d15770aabac7796 Mon Sep 17 00:00:00 2001 From: Marco Jahn Date: Wed, 11 Mar 2026 11:05:31 +0100 Subject: [PATCH] added s3-lambda-textract-bedrock-durable-cdk-ts pattern --- .../.gitignore | 14 ++ .../README.md | 163 ++++++++++++++++++ .../cdk.json | 22 +++ .../example-pattern.json | 132 ++++++++++++++ .../lambda/processor.js | 143 +++++++++++++++ .../lib/pattern-stack.ts | 142 +++++++++++++++ .../package.json | 22 +++ ...lambda-textract-bedrock-durable-cdk-ts.png | Bin 0 -> 41429 bytes .../tsconfig.json | 23 +++ 9 files changed, 661 insertions(+) create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/.gitignore create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/README.md create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/cdk.json create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/example-pattern.json create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/lambda/processor.js create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/lib/pattern-stack.ts create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/package.json create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/s3-lambda-textract-bedrock-durable-cdk-ts.png create mode 100644 s3-lambda-textract-bedrock-durable-cdk-ts/tsconfig.json diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/.gitignore b/s3-lambda-textract-bedrock-durable-cdk-ts/.gitignore new file mode 100644 index 000000000..ab01f2d2a --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/.gitignore @@ -0,0 +1,14 @@ +!jest.config.js +*.d.ts +node_modules +package-lock.json + +# CDK asset staging directory +.cdk.staging +cdk.out + +# Parcel default cache directory +.parcel-cache + +# Mac files +.DS_Store diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/README.md b/s3-lambda-textract-bedrock-durable-cdk-ts/README.md new file mode 100644 index 000000000..4eb691e1c --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/README.md @@ -0,0 +1,163 @@ +# Durable Document Processing with Amazon S3, AWS Lambda, Amazon Textract, and Amazon Bedrock + +This pattern demonstrates a durable document processing pipeline. When a document is uploaded to Amazon S3, a durable AWS Lambda function extracts text using Amazon Textract's asynchronous API, summarizes the content with Amazon Bedrock (Amazon Nova Lite), and stores the results in Amazon DynamoDB. The durable function uses checkpointing and `waitForCondition` to reliably poll for the Textract job completion without wasting compute, and automatically resumes from the last checkpoint if interrupted. + +Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/s3-lambda-textract-bedrock-durable-cdk-ts](https://serverlessland.com/patterns/s3-lambda-textract-bedrock-durable-cdk-ts) + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node.js and npm](https://nodejs.org/) installed +* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) installed +* [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Amazon Nova Lite in your AWS region + +## Important: Bedrock Inference Profile Region Prefix + +The Lambda handler uses a cross-region inference profile ID to invoke Amazon Nova Lite. The profile ID is region-specific: + +| Region | Inference Profile ID | +|--------|---------------------| +| US regions (us-east-1, us-west-2, etc.) | `us.amazon.nova-lite-v1:0` | +| EU regions (eu-central-1, eu-west-1, etc.) | `eu.amazon.nova-lite-v1:0` | +| AP regions (ap-southeast-1, ap-northeast-1, etc.) | `ap.amazon.nova-lite-v1:0` | + +The default in this pattern is `eu.amazon.nova-lite-v1:0`. If deploying to a different region, update the `modelId` in `lambda/processor.js` and the inference profile ARN in `lib/pattern-stack.ts`. + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + + ```bash + git clone https://github.com/aws-samples/serverless-patterns + ``` + +2. Change directory to the pattern directory: + + ```bash + cd s3-lambda-textract-bedrock-durable-cdk-ts + ``` + +3. Install dependencies: + + ```bash + npm install + ``` + +4. Deploy the CDK stack to your default AWS account and region: + + ```bash + cdk deploy + ``` + +5. Note the outputs from the CDK deployment process. These contain the resource names which are used for testing. + +## Deployment Outputs + +After deployment, CDK will display the following outputs. Save these values for testing: + +| Output Key | Description | Usage | +|------------|-------------|-------| +| `DocumentBucketName` | Amazon S3 bucket for document uploads | Upload documents here to trigger processing | +| `ResultsTableName` | Amazon DynamoDB table for processing results | Query this table to see extracted text and summaries | +| `ProcessorFunctionName` | Durable AWS Lambda function name | Use for monitoring and log inspection | +| `ProcessorFunctionArn` | Durable AWS Lambda function ARN | Reference for invocation and permissions | + +## How it works + +![Architecture Diagram](s3-lambda-textract-bedrock-durable-cdk-ts.png) + +This pattern creates a durable AWS Lambda function that implements a multi-step document processing pipeline with automatic checkpointing and resilient polling. + +Architecture flow: +1. A document (PDF, PNG, or JPG) is uploaded to the Amazon S3 document bucket +2. Amazon S3 sends an event notification that triggers the durable AWS Lambda function +3. The function starts an asynchronous Amazon Textract text detection job (Step 1: `start-textract`) +4. The function polls for Textract job completion using `waitForCondition` with exponential backoff (Step 2: `wait-textract-complete`) — the function suspends between polls without consuming compute +5. Once Textract completes, the function extracts text from the response blocks (Step 3: `extract-text`) +6. The extracted text is sent to Amazon Bedrock (Amazon Nova Lite) for summarization (Step 4: `bedrock-summarize`) +7. The summary and metadata are stored in Amazon DynamoDB (Step 5: `store-results`) + +Each step is checkpointed by the durable execution runtime. If the function is interrupted at any point (timeout, transient error), it resumes from the last completed step rather than starting over. + +Example use cases: +- **Invoices**: Extract line items, amounts, and vendor details automatically +- **Contracts**: Identify key clauses, obligations, and renewal dates +- **Insurance documents**: Digitize forms and extract policy information +- **Compliance reports**: Flag non-compliant sections or missing fields + +## Testing + +### Upload a Test Document + +1. Get the S3 bucket name from the stack outputs: + + ```bash + BUCKET_NAME=$(aws cloudformation describe-stacks \ + --stack-name S3LambdaTextractBedrockDurableStack \ + --query 'Stacks[0].Outputs[?OutputKey==`DocumentBucketName`].OutputValue' \ + --output text) + ``` + +2. Upload a PDF, PNG, or JPG document: + + ```bash + aws s3 cp your-document.pdf s3://$BUCKET_NAME/ + ``` + +3. The durable Lambda function is triggered automatically. You can monitor progress in CloudWatch Logs: + + ```bash + FUNCTION_NAME=$(aws cloudformation describe-stacks \ + --stack-name S3LambdaTextractBedrockDurableStack \ + --query 'Stacks[0].Outputs[?OutputKey==`ProcessorFunctionName`].OutputValue' \ + --output text) + + aws logs tail /aws/lambda/doc-processor-durable --follow + ``` + +### Check Processing Results + +1. Get the DynamoDB table name: + + ```bash + TABLE_NAME=$(aws cloudformation describe-stacks \ + --stack-name S3LambdaTextractBedrockDurableStack \ + --query 'Stacks[0].Outputs[?OutputKey==`ResultsTableName`].OutputValue' \ + --output text) + ``` + +2. Scan the table for results (allow 1-2 minutes for processing to complete): + + ```bash + aws dynamodb scan --table-name $TABLE_NAME + ``` + +3. Query a specific document result: + + ```bash + aws dynamodb get-item \ + --table-name $TABLE_NAME \ + --key '{"documentKey": {"S": "your-document.pdf"}}' + ``` + +The result includes the Textract job ID, extracted text length, Bedrock-generated summary, and processing timestamp. + +## Cleanup + +1. Empty the S3 bucket and delete the stack: + + ```bash + cdk destroy + ``` + +2. Confirm the deletion when prompted. + +--- + +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/cdk.json b/s3-lambda-textract-bedrock-durable-cdk-ts/cdk.json new file mode 100644 index 000000000..68ffb5d90 --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/cdk.json @@ -0,0 +1,22 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/s3-lambda-textract-bedrock-durable-cdk-ts.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "cdk.out" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"] + } +} diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/example-pattern.json b/s3-lambda-textract-bedrock-durable-cdk-ts/example-pattern.json new file mode 100644 index 000000000..bda888659 --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/example-pattern.json @@ -0,0 +1,132 @@ +{ + "title": "Amazon S3 to AWS Lambda (Durable) to Amazon Textract and Bedrock", + "description": "Extract text from documents with Amazon Textract and summarize with Amazon Bedrock using a durable AWS Lambda function.", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "services": { + "from": { + "serviceName": "Amazon S3", + "serviceURL": "/s3/" + }, + "to": { + "serviceName": "AWS Lambda", + "serviceURL": "/lambda/" + } + }, + "patternArch": { + "icon1": { + "x": 10, + "y": 50, + "service": "s3", + "label": "Amazon S3" + }, + "icon2": { + "x": 35, + "y": 50, + "service": "lambda", + "label": "AWS Lambda (Durable)" + }, + "icon3": { + "x": 60, + "y": 30, + "service": "textract", + "label": "Amazon Textract" + }, + "icon4": { + "x": 60, + "y": 70, + "service": "bedrock", + "label": "Amazon Bedrock" + }, + "icon5": { + "x": 85, + "y": 50, + "service": "dynamodb", + "label": "Amazon DynamoDB" + }, + "line1": { + "from": "icon1", + "to": "icon2" + }, + "line2": { + "from": "icon2", + "to": "icon3" + }, + "line3": { + "from": "icon2", + "to": "icon4" + }, + "line4": { + "from": "icon2", + "to": "icon5" + } + }, + "patternType": "Serverless", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates a durable document processing pipeline using AWS Lambda durable functions.", + "When a document (PDF, PNG, or JPG) is uploaded to Amazon S3, it triggers a durable Lambda function.", + "The function starts an asynchronous Amazon Textract text detection job and polls for completion using waitForCondition with exponential backoff.", + "Once text extraction completes, the extracted text is sent to Amazon Bedrock (Amazon Nova Lite) for summarization.", + "Results including the summary are stored in Amazon DynamoDB.", + "Durable functions provide automatic checkpointing, so if the function is interrupted during the long-running Textract polling, it resumes from the last checkpoint without re-executing completed steps.", + "Example use cases: invoice processing, contract analysis, insurance document intake, and compliance review." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/s3-lambda-textract-bedrock-durable-cdk-ts", + "templateURL": "serverless-patterns/s3-lambda-textract-bedrock-durable-cdk-ts", + "projectFolder": "s3-lambda-textract-bedrock-durable-cdk-ts", + "templateFile": "lib/pattern-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "AWS Lambda Durable Functions Documentation", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" + }, + { + "text": "Amazon Textract Asynchronous Operations", + "link": "https://docs.aws.amazon.com/textract/latest/dg/async.html" + }, + { + "text": "Amazon Bedrock Amazon Nova Models", + "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-nova.html" + }, + { + "text": "AWS Lambda Durable Execution SDK for JavaScript", + "link": "https://github.com/aws/aws-durable-execution-sdk-js" + } + ] + }, + "deploy": { + "text": [ + "Clone the repository: git clone https://github.com/aws-samples/serverless-patterns", + "Change directory: cd s3-lambda-textract-bedrock-durable-cdk-ts", + "Install dependencies: npm install", + "Deploy the CDK stack: cdk deploy" + ] + }, + "testing": { + "text": [ + "Get the S3 bucket name: BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name S3LambdaTextractBedrockDurableStack --query 'Stacks[0].Outputs[?OutputKey==`DocumentBucketName`].OutputValue' --output text)", + "Upload a test document: aws s3 cp test-document.pdf s3://$BUCKET_NAME/", + "Check DynamoDB for results: TABLE_NAME=$(aws cloudformation describe-stacks --stack-name S3LambdaTextractBedrockDurableStack --query 'Stacks[0].Outputs[?OutputKey==`ResultsTableName`].OutputValue' --output text) && aws dynamodb scan --table-name $TABLE_NAME" + ] + }, + "cleanup": { + "text": ["Delete the stack: cdk destroy"] + }, + "authors": [ + { + "name": "Marco Jahn", + "image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png", + "bio": "Senior Solutions Architect, Amazon Web Services", + "linkedin": "marcojahn" + } + ] +} diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/lambda/processor.js b/s3-lambda-textract-bedrock-durable-cdk-ts/lambda/processor.js new file mode 100644 index 000000000..2fa99894d --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/lambda/processor.js @@ -0,0 +1,143 @@ +const { withDurableExecution, createWaitStrategy } = require('@aws/durable-execution-sdk-js'); +const { TextractClient, StartDocumentTextDetectionCommand, GetDocumentTextDetectionCommand } = require('@aws-sdk/client-textract'); +const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime'); +const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb'); + +const textractClient = new TextractClient(); +const bedrockClient = new BedrockRuntimeClient(); +const dynamoClient = new DynamoDBClient(); + +const RESULTS_TABLE_NAME = process.env.RESULTS_TABLE_NAME; + +/** + * Durable document processing pipeline: + * 1. Start Amazon Textract async job + * 2. Poll for Textract job completion (using waitForCondition) + * 3. Retrieve extracted text + * 4. Summarize with Amazon Bedrock (Claude 3 Haiku) + * 5. Store results in Amazon DynamoDB + */ +exports.handler = withDurableExecution(async (event, context) => { + // Parse S3 event — get bucket and key from the first record + const s3Record = event.Records[0].s3; + const bucket = s3Record.bucket.name; + const key = decodeURIComponent(s3Record.object.key.replace(/\+/g, ' ')); + + context.logger.info(`Processing document: s3://${bucket}/${key}`); + + // Step 1: Start Textract async text detection + const jobId = await context.step('start-textract', async () => { + const response = await textractClient.send(new StartDocumentTextDetectionCommand({ + DocumentLocation: { + S3Object: { Bucket: bucket, Name: key }, + }, + })); + return response.JobId; + }); + + context.logger.info(`Textract job started: ${jobId}`); + + // Step 2: Poll for Textract job completion using waitForCondition + const textractResult = await context.waitForCondition( + 'wait-textract-complete', + async (state) => { + const response = await textractClient.send(new GetDocumentTextDetectionCommand({ + JobId: state.jobId, + })); + return { + jobId: state.jobId, + status: response.JobStatus, + blockCount: (response.Blocks || []).length, + }; + }, + { + initialState: { jobId, status: 'IN_PROGRESS', blockCount: 0 }, + waitStrategy: createWaitStrategy({ + maxAttempts: 60, + initialDelaySeconds: 3, + maxDelaySeconds: 30, + backoffRate: 1.5, + shouldContinuePolling: (result) => + result.status === 'IN_PROGRESS', + }), + timeout: { minutes: 30 }, + } + ); + + if (textractResult.status !== 'SUCCEEDED') { + throw new Error(`Textract job failed with status: ${textractResult.status}`); + } + + // Step 3: Retrieve full Textract results and extract text + const extractedText = await context.step('extract-text', async () => { + const response = await textractClient.send(new GetDocumentTextDetectionCommand({ + JobId: jobId, + })); + const lines = (response.Blocks || []) + .filter((block) => block.BlockType === 'LINE') + .map((block) => block.Text); + return lines.join('\n'); + }); + + context.logger.info(`Extracted ${extractedText.length} characters of text`); + + // Step 4: Summarize extracted text with Amazon Bedrock (Amazon Nova Lite) + const summary = await context.step('bedrock-summarize', async () => { + // Truncate text to fit within model context window + const truncatedText = extractedText.substring(0, 15000); + + const response = await bedrockClient.send(new InvokeModelCommand({ + modelId: 'eu.amazon.nova-lite-v1:0', + contentType: 'application/json', + accept: 'application/json', + body: JSON.stringify({ + schemaVersion: 'messages-v1', + messages: [ + { + role: 'user', + content: [ + { + text: `Summarize the following document text. Provide a concise summary, key topics, and any important entities (names, dates, amounts) found.\n\nDocument text:\n${truncatedText}`, + }, + ], + }, + ], + inferenceConfig: { + max_new_tokens: 1024, + }, + }), + })); + + const result = JSON.parse(new TextDecoder().decode(response.body)); + return result.output.message.content[0].text; + }); + + context.logger.info('Bedrock summarization complete'); + + // Step 5: Store results in DynamoDB + await context.step('store-results', async () => { + await dynamoClient.send(new PutItemCommand({ + TableName: RESULTS_TABLE_NAME, + Item: { + documentKey: { S: key }, + bucket: { S: bucket }, + textractJobId: { S: jobId }, + extractedTextLength: { N: String(extractedText.length) }, + summary: { S: summary }, + processedAt: { S: new Date().toISOString() }, + }, + })); + }); + + context.logger.info(`Results stored for document: ${key}`); + + return { + statusCode: 200, + body: { + document: `s3://${bucket}/${key}`, + textractJobId: jobId, + extractedCharacters: extractedText.length, + summary, + }, + }; +}); diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/lib/pattern-stack.ts b/s3-lambda-textract-bedrock-durable-cdk-ts/lib/pattern-stack.ts new file mode 100644 index 000000000..d51a54606 --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/lib/pattern-stack.ts @@ -0,0 +1,142 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as s3n from 'aws-cdk-lib/aws-s3-notifications'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import { Construct } from 'constructs'; +import * as path from 'path'; + +export class PatternStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // S3 bucket for document uploads + const documentBucket = new s3.Bucket(this, 'DocumentBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + encryption: s3.BucketEncryption.S3_MANAGED, + }); + + // DynamoDB table for processing results + const resultsTable = new dynamodb.Table(this, 'ResultsTable', { + partitionKey: { name: 'documentKey', type: dynamodb.AttributeType.STRING }, + removalPolicy: cdk.RemovalPolicy.DESTROY, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + }); + + // CloudWatch Log Group for the durable function + const logGroup = new logs.LogGroup(this, 'ProcessorLogGroup', { + logGroupName: `/aws/lambda/doc-processor-durable`, + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Durable Lambda function for document processing + const processorFunction = new lambda.Function(this, 'ProcessorFunction', { + runtime: lambda.Runtime.NODEJS_24_X, + handler: 'processor.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '../lambda')), + timeout: cdk.Duration.minutes(15), + memorySize: 512, + logGroup, + environment: { + RESULTS_TABLE_NAME: resultsTable.tableName, + DOCUMENT_BUCKET_NAME: documentBucket.bucketName, + BEDROCK_MODEL_ID: 'us.amazon.nova-lite-v1:0', + }, + }); + + // Enable durable execution via L1 escape hatch + const cfnFunction = processorFunction.node.defaultChild as cdk.CfnResource; + cfnFunction.addPropertyOverride('DurableConfig', { + ExecutionTimeout: 3600, // 1 hour max execution time + RetentionPeriodInDays: 3, // Keep execution state for 3 days + }); + + // Grant the durable execution managed policy + processorFunction.role?.addManagedPolicy( + iam.ManagedPolicy.fromAwsManagedPolicyName( + 'service-role/AWSLambdaBasicDurableExecutionRolePolicy' + ) + ); + + // Grant S3 read access for Textract and the Lambda function + documentBucket.grantRead(processorFunction); + + // Grant DynamoDB write access + resultsTable.grantWriteData(processorFunction); + + // Grant Amazon Textract permissions + // Textract does not support resource-level permissions — wildcard is required. + // Actions are scoped to only the two operations needed by this handler. + // See: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazontextract.html + processorFunction.addToRolePolicy(new iam.PolicyStatement({ + actions: [ + 'textract:StartDocumentTextDetection', + 'textract:GetDocumentTextDetection', + ], + resources: ['*'], + })); + + // Grant Amazon Bedrock invoke model permission + // Cross-region inference profiles route to any region in the geo area, + // so we allow the foundation model in all regions via wildcard + processorFunction.addToRolePolicy(new iam.PolicyStatement({ + actions: ['bedrock:InvokeModel'], + resources: [ + `arn:aws:bedrock:*::foundation-model/amazon.nova-lite-v1:0`, + `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/eu.amazon.nova-lite-v1:0`, + ], + })); + + // Create a version and alias for durable function invocation + // Durable functions require qualified ARNs (version or alias) + const version = processorFunction.currentVersion; + const alias = new lambda.Alias(this, 'ProcessorAlias', { + aliasName: 'live', + version, + }); + + // S3 event notification triggers the durable Lambda alias on object creation + documentBucket.addEventNotification( + s3.EventType.OBJECT_CREATED, + new s3n.LambdaDestination(alias), + { suffix: '.pdf' } + ); + + documentBucket.addEventNotification( + s3.EventType.OBJECT_CREATED, + new s3n.LambdaDestination(alias), + { suffix: '.png' } + ); + + documentBucket.addEventNotification( + s3.EventType.OBJECT_CREATED, + new s3n.LambdaDestination(alias), + { suffix: '.jpg' } + ); + + // Outputs + new cdk.CfnOutput(this, 'DocumentBucketName', { + value: documentBucket.bucketName, + description: 'S3 bucket for document uploads', + }); + + new cdk.CfnOutput(this, 'ResultsTableName', { + value: resultsTable.tableName, + description: 'DynamoDB table for processing results', + }); + + new cdk.CfnOutput(this, 'ProcessorFunctionName', { + value: processorFunction.functionName, + description: 'Durable Lambda function name', + }); + + new cdk.CfnOutput(this, 'ProcessorFunctionArn', { + value: processorFunction.functionArn, + description: 'Durable Lambda function ARN', + }); + } +} diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/package.json b/s3-lambda-textract-bedrock-durable-cdk-ts/package.json new file mode 100644 index 000000000..46bc9b752 --- /dev/null +++ b/s3-lambda-textract-bedrock-durable-cdk-ts/package.json @@ -0,0 +1,22 @@ +{ + "name": "s3-lambda-textract-bedrock-durable-cdk-ts", + "version": "0.1.0", + "bin": { + "s3-lambda-textract-bedrock-durable-cdk-ts": "bin/s3-lambda-textract-bedrock-durable-cdk-ts.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@types/node": "22.7.9", + "aws-cdk": "2.1110.0", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "aws-cdk-lib": "2.242.0", + "constructs": "^10.0.0" + } +} diff --git a/s3-lambda-textract-bedrock-durable-cdk-ts/s3-lambda-textract-bedrock-durable-cdk-ts.png b/s3-lambda-textract-bedrock-durable-cdk-ts/s3-lambda-textract-bedrock-durable-cdk-ts.png new file mode 100644 index 0000000000000000000000000000000000000000..7182b52023342ae6b39af9da7bde331f5f1ca6f4 GIT binary patch literal 41429 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