Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit beadaa9

Browse files
committed
Added samples for infrequent polling, frequent polling, and stateful activities
- added samples for - infrequent polling - frequent polling - stateful activities - removed husky from the nextjs ecommerce app - added vscode launch.json to gitignore [DSE Jira Ticket](https://temporalio.atlassian.net/browse/DSE-21) Regarding the stateful activities: I added this because i learned how to do it while trying to implement the polling We can consider [this PR](temporalio#410) finished when we merge this one. Thomas fixed the other parts of it when he merged [this](https://github.com/temporalio/samples-typescript/pull/412/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519L38) <!--- add/delete as needed ---> 1. How was this tested: I ran these three in my local and got expected behavior 2. Code cleanliness: ran linter and formatter ✅
1 parent 4f5a53f commit beadaa9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1240
-9
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,6 @@ lib/
109109
yarn.lock
110110
package-lock.json
111111
*/**/pnpm-lock.yaml
112+
113+
# vs code launch.json
114+
.vscode/launch.json

.scripts/list-of-samples.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"activities-cancellation-heartbeating",
44
"activities-dependency-injection",
55
"activities-examples",
6+
"activities-stateful",
67
"child-workflows",
78
"continue-as-new",
89
"cron-workflows",
@@ -26,6 +27,8 @@
2627
"nestjs-exchange-rates",
2728
"nextjs-ecommerce-oneclick",
2829
"patching-api",
30+
"polling-frequent",
31+
"polling-infrequent",
2932
"production",
3033
"protobufs",
3134
"query-subscriptions",

activities-stateful/.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
lib
3+
.eslintrc.js

activities-stateful/.eslintrc.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const { builtinModules } = require('module');
2+
3+
const ALLOWED_NODE_BUILTINS = new Set(['assert']);
4+
5+
module.exports = {
6+
root: true,
7+
parser: '@typescript-eslint/parser',
8+
parserOptions: {
9+
project: './tsconfig.json',
10+
tsconfigRootDir: __dirname,
11+
},
12+
plugins: ['@typescript-eslint', 'deprecation'],
13+
extends: [
14+
'eslint:recommended',
15+
'plugin:@typescript-eslint/eslint-recommended',
16+
'plugin:@typescript-eslint/recommended',
17+
'prettier',
18+
],
19+
rules: {
20+
// recommended for safety
21+
'@typescript-eslint/no-floating-promises': 'error', // forgetting to await Activities and Workflow APIs is bad
22+
'deprecation/deprecation': 'warn',
23+
24+
// code style preference
25+
'object-shorthand': ['error', 'always'],
26+
27+
// relaxed rules, for convenience
28+
'@typescript-eslint/no-unused-vars': [
29+
'warn',
30+
{
31+
argsIgnorePattern: '^_',
32+
varsIgnorePattern: '^_',
33+
},
34+
],
35+
'@typescript-eslint/no-explicit-any': 'off',
36+
},
37+
overrides: [
38+
{
39+
files: ['src/workflows.ts', 'src/workflows-*.ts', 'src/workflows/*.ts'],
40+
rules: {
41+
'no-restricted-imports': [
42+
'error',
43+
...builtinModules.filter((m) => !ALLOWED_NODE_BUILTINS.has(m)).flatMap((m) => [m, `node:${m}`]),
44+
],
45+
},
46+
},
47+
],
48+
};

activities-stateful/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lib
2+
node_modules

activities-stateful/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

activities-stateful/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

activities-stateful/.post-create

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
To begin development, install the Temporal CLI:
2+
3+
Mac: {cyan brew install temporal}
4+
Other: Download and extract the latest release from https://github.com/temporalio/cli/releases/latest
5+
6+
Start Temporal Server:
7+
8+
{cyan temporal server start-dev}
9+
10+
Use Node version 18+ (v22.x is recommended):
11+
12+
Mac: {cyan brew install node@22}
13+
Other: https://nodejs.org/en/download/
14+
15+
Then, in the project directory, using two other shells, run these commands:
16+
17+
{cyan npm run start.watch}
18+
{cyan npm run workflow}

activities-stateful/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib

activities-stateful/.prettierrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
printWidth: 120
2+
singleQuote: true

activities-stateful/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Stateful Activity sample
2+
3+
This sample shows how to make and use stateful activity.
4+
5+
There are a few key changes compared to a normal, stateless activity.
6+
7+
- The Activity Definition is a class method rather than a plain function
8+
(and the state is within the class)
9+
- In the Workflow Definition, instead of importing all the activities,
10+
you can just import the class. And when you proxy the activities,
11+
you'd just proxy the class.
12+
- In the worker code, when passing in the activities, make sure to do the
13+
binding so the `this` call in the activity definition works.
14+
15+
### Running this sample
16+
17+
1. `temporal server start-dev` to start [Temporal Server](https://github.com/temporalio/cli/#installation).
18+
1. `npm install` to install dependencies.
19+
1. `npm run start.watch` to start the Worker.
20+
1. In another shell, `npm run workflow` to run the Workflow Client.
21+
22+
The Workflow should try four times, throwing a `not yet` error each time,
23+
and then on the fifth try, it should log `Hello Temporal!` to the
24+
console and complete.

activities-stateful/package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "temporal-activities-stateful",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"build": "tsc --build",
7+
"build.watch": "tsc --build --watch",
8+
"format": "prettier --write .",
9+
"format:check": "prettier --check .",
10+
"lint": "eslint .",
11+
"start": "ts-node src/worker.ts",
12+
"start.watch": "nodemon src/worker.ts",
13+
"workflow": "ts-node src/client.ts",
14+
"test": "mocha --exit --require ts-node/register --require source-map-support/register src/mocha/*.test.ts"
15+
},
16+
"nodemonConfig": {
17+
"execMap": {
18+
"ts": "ts-node"
19+
},
20+
"ext": "ts",
21+
"watch": [
22+
"src"
23+
]
24+
},
25+
"dependencies": {
26+
"@temporalio/activity": "^1.11.6",
27+
"@temporalio/client": "^1.11.6",
28+
"@temporalio/worker": "^1.11.6",
29+
"@temporalio/workflow": "^1.11.6",
30+
"nanoid": "3.x"
31+
},
32+
"devDependencies": {
33+
"@temporalio/testing": "^1.11.6",
34+
"@tsconfig/node18": "^18.2.4",
35+
"@types/mocha": "8.x",
36+
"@types/node": "^22.9.1",
37+
"@typescript-eslint/eslint-plugin": "^8.18.0",
38+
"@typescript-eslint/parser": "^8.18.0",
39+
"eslint": "^8.57.1",
40+
"eslint-config-prettier": "^9.1.0",
41+
"eslint-plugin-deprecation": "^3.0.0",
42+
"mocha": "8.x",
43+
"nodemon": "^3.1.7",
44+
"prettier": "^3.4.2",
45+
"ts-node": "^10.9.2",
46+
"typescript": "^5.6.3"
47+
}
48+
}

activities-stateful/src/activities.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// in a stateless activity, you would do something like this
2+
// export async function greet(name: string): Promise<string> {
3+
// return `Hello, ${name}!`;
4+
// }
5+
6+
// in a stateful activity, you would do something like this
7+
export class GreetingActivities {
8+
private count: number;
9+
10+
constructor() {
11+
this.count = 0;
12+
}
13+
14+
async greet(name: string): Promise<string> {
15+
this.count++;
16+
if (this.count === 5) {
17+
return `Hello, ${name}!`;
18+
} else {
19+
throw new Error('not yet');
20+
}
21+
}
22+
}

activities-stateful/src/client.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Connection, Client } from '@temporalio/client';
2+
import { example } from './workflows';
3+
import { nanoid } from 'nanoid';
4+
5+
async function run() {
6+
const connection = await Connection.connect({ address: 'localhost:7233' });
7+
8+
const client = new Client({
9+
connection,
10+
});
11+
12+
const handle = await client.workflow.start(example, {
13+
taskQueue: 'hello-world',
14+
args: ['Temporal'],
15+
workflowId: 'workflow-' + nanoid(),
16+
});
17+
console.log(`Started workflow ${handle.workflowId}`);
18+
19+
console.log(await handle.result());
20+
}
21+
22+
run().catch((err) => {
23+
console.error(err);
24+
process.exit(1);
25+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MockActivityEnvironment } from '@temporalio/testing';
2+
import { describe, it } from 'mocha';
3+
import * as activities from '../activities';
4+
import assert from 'assert';
5+
6+
describe('greet activity', async () => {
7+
it('successfully greets the user', async () => {
8+
const env = new MockActivityEnvironment();
9+
const name = 'Temporal';
10+
const result = await env.run(activities.greet, name);
11+
assert.equal(result, 'Hello, Temporal!');
12+
});
13+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { TestWorkflowEnvironment } from '@temporalio/testing';
2+
import { after, before, it } from 'mocha';
3+
import { Worker } from '@temporalio/worker';
4+
import { example } from '../workflows';
5+
import assert from 'assert';
6+
7+
describe('Example workflow with mocks', () => {
8+
let testEnv: TestWorkflowEnvironment;
9+
10+
before(async () => {
11+
testEnv = await TestWorkflowEnvironment.createLocal();
12+
});
13+
14+
after(async () => {
15+
await testEnv?.teardown();
16+
});
17+
18+
it('successfully completes the Workflow with a mocked Activity', async () => {
19+
const { client, nativeConnection } = testEnv;
20+
const taskQueue = 'test';
21+
22+
const worker = await Worker.create({
23+
connection: nativeConnection,
24+
taskQueue,
25+
workflowsPath: require.resolve('../workflows'),
26+
activities: {
27+
greet: async () => 'Hello, Temporal!',
28+
},
29+
});
30+
31+
const result = await worker.runUntil(
32+
client.workflow.execute(example, {
33+
args: ['Temporal'],
34+
workflowId: 'test',
35+
taskQueue,
36+
}),
37+
);
38+
assert.equal(result, 'Hello, Temporal!');
39+
});
40+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// @@@SNIPSTART hello-world-project-template-ts-workflow-test
2+
import { TestWorkflowEnvironment } from '@temporalio/testing';
3+
import { before, describe, it } from 'mocha';
4+
import { Worker } from '@temporalio/worker';
5+
import { example } from '../workflows';
6+
import * as activities from '../activities';
7+
import assert from 'assert';
8+
9+
describe('Example workflow', () => {
10+
let testEnv: TestWorkflowEnvironment;
11+
12+
before(async () => {
13+
testEnv = await TestWorkflowEnvironment.createLocal();
14+
});
15+
16+
after(async () => {
17+
await testEnv?.teardown();
18+
});
19+
20+
it('successfully completes the Workflow', async () => {
21+
const { client, nativeConnection } = testEnv;
22+
const taskQueue = 'test';
23+
24+
const worker = await Worker.create({
25+
connection: nativeConnection,
26+
taskQueue,
27+
workflowsPath: require.resolve('../workflows'),
28+
activities,
29+
});
30+
31+
const result = await worker.runUntil(
32+
client.workflow.execute(example, {
33+
args: ['Temporal'],
34+
workflowId: 'test',
35+
taskQueue,
36+
}),
37+
);
38+
assert.equal(result, 'Hello, Temporal!');
39+
});
40+
});
41+
// @@@SNIPEND

activities-stateful/src/worker.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { NativeConnection, Worker, WorkerOptions } from '@temporalio/worker';
2+
// in a stateless activity, you would do something like this
3+
//import * as activities from './activities';
4+
// in a stateful activity, you would do something like this
5+
import { GreetingActivities } from './activities';
6+
7+
async function run() {
8+
const connection = await NativeConnection.connect({
9+
address: 'localhost:7233',
10+
});
11+
try {
12+
const greeter = new GreetingActivities();
13+
14+
const x: WorkerOptions = {
15+
connection,
16+
namespace: 'default',
17+
taskQueue: 'hello-world',
18+
workflowsPath: require.resolve('./workflows'),
19+
// in a stateless activity, you would do something like this
20+
// activities
21+
// which is the same as
22+
// activities: activities
23+
// in a stateful activity, you would do something like this
24+
activities: { greet: greeter.greet.bind(greeter) },
25+
// your first attempt may be what's written below, but that won't work because
26+
// you need to bind the method to the instance for `this` to work, as shown above.
27+
// activities: {greet: greeter.greet},
28+
};
29+
30+
const worker = await Worker.create(x);
31+
32+
await worker.run();
33+
} finally {
34+
await connection.close();
35+
}
36+
}
37+
38+
run().catch((err) => {
39+
console.error(err);
40+
process.exit(1);
41+
});

activities-stateful/src/workflows.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { proxyActivities, ActivityOptions, RetryPolicy } from '@temporalio/workflow';
2+
// in a stateless activity, you would do something like this
3+
// import type * as activities from './activities';
4+
// in a stateful activity, you would do something like this
5+
import { GreetingActivities } from './activities';
6+
7+
const retryPolicy: RetryPolicy = {
8+
backoffCoefficient: 1,
9+
initialInterval: 5 * 1000,
10+
};
11+
12+
const activityOptions: ActivityOptions = {
13+
retry: retryPolicy,
14+
startToCloseTimeout: 2 * 1000,
15+
};
16+
17+
// in a stateless activity, you would do something like this
18+
// const { greet } = proxyActivities<typeof activities>(activityOptions);
19+
// in a stateful activity, you would do something like this
20+
const greeter = new GreetingActivities();
21+
const { greet } = proxyActivities<{ greet: typeof greeter.greet }>(activityOptions);
22+
23+
export async function example(name: string): Promise<string> {
24+
return await greet(name);
25+
}

0 commit comments

Comments
 (0)