Skip to content

Commit 288e97b

Browse files
committed
feat(extstore): add generic s3 driver interface.
1 parent 1b6c2b9 commit 288e97b

10 files changed

Lines changed: 688 additions & 0 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Amazon S3 External Storage Driver for the Temporal TypeScript SDK
2+
3+
> ⚠️ **This package is experimental and may be subject to change.** ⚠️
4+
5+
`@temporalio/external-storage-s3` stores and retrieves Temporal payloads in Amazon S3 via the [External Storage](../../README.md) feature.
6+
7+
This package has no AWS dependency: it defines the driver and the `S3StorageDriverClient` interface, and you supply the S3 client. Use the companion [`@temporalio/external-storage-s3-aws-sdk`](../external-storage-s3-aws-sdk) package for an [`@aws-sdk/client-s3`](https://www.npmjs.com/package/@aws-sdk/client-s3)-backed client, or implement the interface yourself.
8+
9+
## Using the AWS SDK client
10+
11+
Install the adapter package alongside this one:
12+
13+
npm install @temporalio/external-storage-s3 @temporalio/external-storage-s3-aws-sdk @aws-sdk/client-s3
14+
15+
```ts
16+
import { S3Client } from '@aws-sdk/client-s3';
17+
import { S3StorageDriver } from '@temporalio/external-storage-s3';
18+
import { AwsSdkS3StorageDriverClient } from '@temporalio/external-storage-s3-aws-sdk';
19+
20+
const s3Client = new S3Client({ region: 'us-east-1' });
21+
const driver = new S3StorageDriver({
22+
client: new AwsSdkS3StorageDriverClient(s3Client),
23+
bucket: 'my-temporal-payloads',
24+
});
25+
```
26+
27+
Register the resulting driver with the SDK's External Storage configuration so the
28+
client and worker offload eligible payloads to it.
29+
30+
## Custom S3 client implementations
31+
32+
To use a different S3 library, implement `S3StorageDriverClient`.
33+
34+
```ts
35+
import type { S3StorageDriverClient } from '@temporalio/external-storage-s3';
36+
37+
const myClient: S3StorageDriverClient = {
38+
async putObject(bucket, key, data, options) {
39+
/* ... */
40+
},
41+
async objectExists(bucket, key, options) {
42+
/* ... */
43+
return false;
44+
},
45+
async getObject(bucket, key, options) {
46+
/* ... */
47+
return new Uint8Array();
48+
},
49+
};
50+
```
51+
## Dynamic bucket selection
52+
53+
Pass a callable as `bucket` to choose the destination per payload:
54+
55+
```ts
56+
const driver = new S3StorageDriver({
57+
client: new AwsSdkS3StorageDriverClient(s3Client),
58+
bucket: (_context, payload) =>
59+
(payload.data?.length ?? 0) > 10 * 1024 * 1024 ? 'large-payloads' : 'small-payloads',
60+
});
61+
```
62+
63+
## Required IAM permissions
64+
65+
The credentials used by your S3 client must have these S3 permissions on the target bucket and its objects:
66+
67+
```json
68+
{
69+
"Effect": "Allow",
70+
"Action": ["s3:PutObject", "s3:GetObject"],
71+
"Resource": "arn:aws:s3:::my-temporal-payloads/*"
72+
}
73+
```
74+
75+
`s3:PutObject` is required by components that store payloads (typically the client and worker sending workflow/activity inputs); `s3:GetObject` is required by components that retrieve them. Components that only retrieve do not need `s3:PutObject`, and vice versa.
76+
77+
## S3 Storage Key Specification
78+
79+
All Temporal S3 drivers generate S3 keys in a consistent manner.
80+
81+
### Key format
82+
83+
Workflow key:
84+
```text
85+
v0/ns/{namespace}/wt/{workflow-type}/wi/{workflow-id}/ri/{run-id}/d/{hash-algorithm}/{hex-digest}
86+
```
87+
88+
Activity key:
89+
```text
90+
v0/ns/{namespace}/at/{activity-type}/ai/{activity-id}/ri/{run-id}/d/{hash-algorithm}/{hex-digest}
91+
```
92+
93+
Fallback key (unknown target):
94+
```text
95+
v0/d/{hash-algorithm}/{hex-digest}
96+
```
97+
98+
- If no namespace, workflow, or activity information is available, the fallback is used.
99+
- Dynamic path segments are percent-encoded (rules below).
100+
- Missing values (including a missing `run-id`) are encoded as `null`.
101+
- `hex-digest` is lower-case SHA-256 hex (64 characters).
102+
103+
### Percent-encoding rules
104+
105+
The Temporal SDKs escape anything that isn't listed in S3's safe character set: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
106+
107+
Safe Characters:
108+
```text
109+
Alphanumeric characters
110+
0-9
111+
a-z
112+
A-Z
113+
114+
Special characters
115+
Exclamation point (!)
116+
Hyphen (-)
117+
Underscore (_)
118+
Period (.)
119+
Asterisk (*)
120+
Single quotation mark (')
121+
Opening parenthesis (()
122+
Closing parenthesis ())
123+
```
124+
125+
### Examples
126+
127+
Workflow key example:
128+
129+
```text
130+
input:
131+
namespace=payments prod
132+
workflow-type=ChargeWorkflow
133+
workflow-id=order+123=abc
134+
run-id=3f1d6c7a-8b2e-4f7a-9d0a-87a6f95e4d31
135+
hash-algorithm=sha256
136+
hex-digest=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
137+
138+
output:
139+
v0/ns/payments%20prod/wt/ChargeWorkflow/wi/order%2B123%3Dabc/ri/3f1d6c7a-8b2e-4f7a-9d0a-87a6f95e4d31/d/sha256/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
140+
```
141+
142+
Activity key example:
143+
144+
```text
145+
input:
146+
namespace=payments prod
147+
activity-type=Capture/Charge
148+
activity-id=activity id+42
149+
run-id=9e1d1fd9-2f8a-4c40-93e2-731f31b9268b
150+
hash-algorithm=sha256
151+
hex-digest=2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
152+
153+
output:
154+
v0/ns/payments%20prod/at/Capture%2FCharge/ai/activity%20id%2B42/ri/9e1d1fd9-2f8a-4c40-93e2-731f31b9268b/d/sha256/2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
155+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "@temporalio/external-storage-s3",
3+
"version": "1.17.2",
4+
"description": "Amazon S3 external storage driver for the Temporal TypeScript SDK",
5+
"main": "lib/index.js",
6+
"types": "./lib/index.d.ts",
7+
"keywords": [
8+
"temporal",
9+
"workflow",
10+
"external storage",
11+
"payload",
12+
"s3",
13+
"aws"
14+
],
15+
"author": "Temporal Technologies Inc. <sdk@temporal.io>",
16+
"license": "MIT",
17+
"scripts": {
18+
"build": "tsc --build",
19+
"test": "ava ./lib/__tests__/test-*.js"
20+
},
21+
"ava": {
22+
"timeout": "60s"
23+
},
24+
"dependencies": {
25+
"@temporalio/common": "workspace:*",
26+
"@temporalio/proto": "workspace:*"
27+
},
28+
"devDependencies": {
29+
"ava": "^5.3.1"
30+
},
31+
"engines": {
32+
"node": ">= 20.3.0"
33+
},
34+
"bugs": {
35+
"url": "https://github.com/temporalio/sdk-typescript/issues"
36+
},
37+
"repository": {
38+
"type": "git",
39+
"url": "git+https://github.com/temporalio/sdk-typescript.git",
40+
"directory": "contrib/external-storage-s3"
41+
},
42+
"homepage": "https://github.com/temporalio/sdk-typescript/tree/main/contrib/external-storage-s3",
43+
"publishConfig": {
44+
"access": "public"
45+
},
46+
"files": [
47+
"src",
48+
"lib",
49+
"!src/__tests__",
50+
"!lib/__tests__"
51+
]
52+
}

0 commit comments

Comments
 (0)