Skip to content

Lambda Worker contrib package#1995

Open
Sushisource wants to merge 6 commits intomainfrom
lambda-worker
Open

Lambda Worker contrib package#1995
Sushisource wants to merge 6 commits intomainfrom
lambda-worker

Conversation

@Sushisource
Copy link
Copy Markdown
Member

@Sushisource Sushisource commented Apr 4, 2026

  • New @temporalio/lambda-worker package that wraps the full per-invocation lifecycle for running
    Temporal workers inside AWS Lambda (connect, create worker, poll, graceful shutdown)
    • Ships with Lambda-tuned concurrency defaults, automatic deadline management, structured JSON
      logging via Powertools, and optional OTEL helpers for ADOT integration
    • Worker Deployment Versioning is always enabled (defaults to PINNED)
    • Includes comprehensive unit tests (~625 lines) covering config/defaults, shutdown hooks, deadline
      handling, connection lifecycle, and connection options

@Sushisource Sushisource requested a review from a team as a code owner April 4, 2026 01:13
@Sushisource Sushisource force-pushed the lambda-worker branch 2 times, most recently from 3ecf038 to 8040631 Compare April 6, 2026 21:15
Copy link
Copy Markdown
Member

@chris-olszewski chris-olszewski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, just a few concerns around the otel code configuration setup.

case 'ERROR':
this.ptLogger.error(message, meta as Record<string, unknown>);
break;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I ever expect us to add a new log level

Suggested change
}
default:
level satisfies never;
break;
}


export const handler = runWorker({ deploymentName: 'my-service', buildId: 'v1.0' }, (config) => {
config.workerOptions.taskQueue = 'my-task-queue';
config.workerOptions.workflowBundle = require('./workflow-bundle.json');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
config.workerOptions.workflowBundle = require('./workflow-bundle.json');
config.workerOptions.workflowBundle = { code: require('./workflow-bundle.js') };

Comment on lines +21 to +28
* You must set at least `taskQueue` (or set the `TEMPORAL_TASK_QUEUE` env var). Typically you'll
* also set `activities` and `workflowBundle` (prefer pre-bundled workflows over `workflowsPath`
* to avoid bundling overhead on Lambda cold starts).
*
* The following fields are managed by settings elsewhere and will be overridden per-invocation:
* `connection`, `namespace`, `identity`, `workerDeploymentOptions`.
*/
workerOptions: Partial<WorkerOptions>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how beneficial this would be, but could convey that connection etc aren't respected via the type

Suggested change
* You must set at least `taskQueue` (or set the `TEMPORAL_TASK_QUEUE` env var). Typically you'll
* also set `activities` and `workflowBundle` (prefer pre-bundled workflows over `workflowsPath`
* to avoid bundling overhead on Lambda cold starts).
*
* The following fields are managed by settings elsewhere and will be overridden per-invocation:
* `connection`, `namespace`, `identity`, `workerDeploymentOptions`.
*/
workerOptions: Partial<WorkerOptions>;
* You must set at least `taskQueue` (or set the `TEMPORAL_TASK_QUEUE` env var). Typically you'll
* also set `activities` and `workflowBundle` (prefer pre-bundled workflows over `workflowsPath`
* to avoid bundling overhead on Lambda cold starts).
*
* The following fields are managed by settings elsewhere and will be overridden per-invocation:
* `connection`, `namespace`, `identity`, `workerDeploymentOptions`.
*/
workerOptions: Partial<Omit<WorkerOptions, 'connection' | 'namespace' | 'identity' | 'workerDeploymentOptions'>>;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah good call

config.workerOptions.taskQueue = envTaskQueue;
}

const connectConfig = deps.loadConnectConfig(config.envConfigOptions);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.envConfigOptions will always be undefined at this point

Comment on lines +85 to +89
workerDeploymentOptions: {
version,
useWorkerVersioning: true,
defaultVersioningBehavior: 'PINNED',
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this to get modified by the user? If not, we should probably have a userConfig object that gets passed in and then later fully resolved into the config required to run the serverless worker.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do. They should be able to change the default behavior. They can't set useWorkerVersioning to false, though, and that's enforced at runtime. I can probably enforce that at compile time though so I'll see about doing that.

connection,
namespace,
identity,
} as WorkerOptions;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would feel better if this was a smaller cast than it currently is. I think the connection is the only type that actually needs massaging. Or beef up the connection mock definition, but that might be overkill.

Comment on lines +170 to +172
new Promise<void>((resolve) => {
setTimeout(resolve, workTimeMs);
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use the stdlib promise timers: https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options

Suggested change
new Promise<void>((resolve) => {
setTimeout(resolve, workTimeMs);
})
setTimeout(workTimeMs)

* { buildId: 'v1.0.0', deploymentName: 'my-service' },
* (config) => {
* config.workerOptions.taskQueue = 'my-task-queue';
* config.workerOptions.workflowBundle = require('./workflow-bundle.json');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* config.workerOptions.workflowBundle = require('./workflow-bundle.json');
* config.workerOptions.workflowBundle = { code: require('./workflow-bundle.js') };

* @example
* ```ts
* import { runWorker } from '@temporalio/lambda-worker';
* import { applyDefaults } from '@temporalio/lambda-worker/lib/otel';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should re-export these to as an otel module to avoid having users reach into lib. I think if you make otel it's own package entry point you could avoid the dynamic imports as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if what I did is what you're thinking of

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants