Basic Activity with the TypeScript SDK
How to develop a basic Activity
One of the primary things that Workflows do is orchestrate the execution of Activities. An Activity is a normal function or method execution that's intended to execute a single, well-defined action (either short or long-running), such as querying a database, calling a third-party API, or transcoding a media file. An Activity can interact with world outside the Temporal Platform or use a Temporal Client to interact with a Temporal Service. For the Workflow to be able to execute the Activity, we must define the Activity Definition.
- Activities execute in the standard Node.js environment.
- Activities cannot be in the same file as Workflows and must be separately registered.
- Activities may be retried repeatedly, so you may need to use idempotency keys for critical side effects.
Activities are just functions. The following is an Activity that accepts a string parameter and returns a string.
export async function greet(name: string): Promise<string> {
return `👋 Hello, ${name}!`;
}
How to develop Activity Parameters
There is no explicit limit to the total number of parameters that an Activity Definition may support. However, there is a limit to the total size of the data that ends up encoded into a gRPC message Payload.
A single argument is limited to a maximum size of 2 MB. And the total size of a gRPC message, which includes all the arguments, is limited to a maximum of 4 MB.
Also, keep in mind that all Payload data is recorded in the Workflow Execution Event History and large Event Histories can affect Worker performance. This is because the entire Event History could be transferred to a Worker Process with a Workflow Task.
Some SDKs require that you pass context objects, others do not. When it comes to your application data—that is, data that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the application data passed to Activities. This is so that you can change what data is passed to the Activity without breaking a function or method signature.
This Activity takes a single name parameter of type string.
export async function greet(name: string): Promise<string> {
return `👋 Hello, ${name}!`;
}
How to define Activity return values
All data returned from an Activity must be serializable.
Activity return values are subject to payload size limits in Temporal. The default payload size limit is 2MB, and there is a hard limit of 4MB for any gRPC message size in the Event History transaction (see Cloud limits here). Keep in mind that all return values are recorded in a Workflow Execution Event History.
In TypeScript, the return value is always a Promise.
In the following example, Promise<string> is the return value.
export async function greet(name: string): Promise<string> {
return `👋 Hello, ${name}!`;
}
How to customize your Activity Type
Activities have a Type that are referred to as the Activity name. The following examples demonstrate how to set a custom name for your Activity Type.
You can customize the name of the Activity when you register it with the Worker. In the following example, the Activity
Name is activityFoo.
snippets/src/worker-activity-type-custom.ts
import { Worker } from '@temporalio/worker';
import { greet } from './activities';
async function run() {
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
taskQueue: 'snippets',
activities: {
activityFoo: greet,
},
});
await worker.run();
}
Important design patterns for Activities
The following are some important (and frequently requested) patterns for using our Activities APIs. These patterns address common needs and use cases.
Share dependencies in Activity functions (dependency injection)
Because Activities are "just functions," you can also create functions that create Activities. This is a helpful pattern for using closures to do the following:
- Store expensive dependencies for sharing, such as database connections.
- Inject secret keys (such as environment variables) from the Worker to the Activity.
activities-dependency-injection/src/activities.ts
export interface DB {
get(key: string): Promise<string>;
}
export const createActivities = (db: DB) => ({
async greet(msg: string): Promise<string> {
const name = await db.get('name'); // simulate read from db
return `${msg}: ${name}`;
},
async greet_es(mensaje: string): Promise<string> {
const name = await db.get('name'); // simulate read from db
return `${mensaje}: ${name}`;
},
});
See full example
When you register these in the Worker, pass your shared dependencies accordingly:
import { createActivities } from './activities';
async function run() {
// Mock DB connection initialization in Worker
const db = {
async get(_key: string) {
return 'Temporal';
},
};
const worker = await Worker.create({
taskQueue: 'dependency-injection',
workflowsPath: require.resolve('./workflows'),
activities: createActivities(db),
});
await worker.run();
}
run().catch((err) => {
console.error(err);
process.exit(1);
});
Because Activities are always referenced by name, inside the Workflow they can be proxied as normal, although the types need some adjustment:
activities-dependency-injection/src/workflows.ts
import type { createActivities } from './activities';
// Note usage of ReturnType<> generic since createActivities is a factory function
const { greet, greet_es } = proxyActivities<ReturnType<typeof createActivities>>({
startToCloseTimeout: '30 seconds',
});
Import multiple Activities simultaneously
You can proxy multiple Activities from the same proxyActivities call if you want them to share the same timeouts,
retries, and options:
export async function Workflow(name: string): Promise<string> {
// destructuring multiple activities with the same options
const { act1, act2, act3 } = proxyActivities<typeof activities>();
/* activityOptions */
await act1();
await Promise.all([act2, act3]);
}
Dynamically reference Activities
Because Activities are referenced only by their string names, you can reference them dynamically if needed:
export async function DynamicWorkflow(activityName, ...args) {
const acts = proxyActivities(/* activityOptions */);
// these are equivalent
await acts.activity1();
await acts['activity1']();
// dynamic reference to activities using activityName
let result = await acts[activityName](...args);
}
Type safety is still supported here, but we encourage you to validate and handle mismatches in Activity names. An
invalid Activity name leads to a NotFoundError with a message that looks like this:
ApplicationFailure: Activity function actC is not registered on this Worker, available activities: ["actA", "actB"]