Skip to content

Commit cfd9c1f

Browse files
committed
Address small nits and update tests.
1 parent 165d454 commit cfd9c1f

3 files changed

Lines changed: 20 additions & 8 deletions

File tree

contrib/external-storage-s3/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> ⚠️ **This package is experimental and may be subject to change.** ⚠️
44
5-
`@temporalio/external-storage-s3` stores and retrieves Temporal payloads in Amazon S3 via the [External Storage](../../README.md) feature.
5+
`@temporalio/external-storage-s3` stores and retrieves Temporal payloads in Amazon S3 via the [External Storage](https://docs.temporal.io/external-storage) feature.
66

77
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.
88

contrib/external-storage-s3/src/__tests__/test-driver.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,12 @@ test('key is content-addressed and segmented by the store context', async (t) =>
7171
const [claim] = await driver.store(workflowContext, [payload]);
7272
assert(claim?.claimData.key);
7373

74-
// Content-addressed: the digest segment is the SHA-256 of the serialized payload
75-
// bytes, and the store context fields form the preceding path segments in order.
7674
const digest = sha256Hex(payloadBytes(payload));
7775
t.is(claim.claimData.key, `v0/ns/my-ns/wt/MyWorkflow/wi/wf-1/ri/run-1/d/sha256/${digest}`);
7876
t.is(claim.claimData.hashValue, digest);
7977
t.is(claim.claimData.hashAlgorithm, 'sha256');
8078
t.is(claim.claimData.bucket, 'b');
8179

82-
// Different content under the same context changes only the trailing digest segment.
8380
const [other] = await driver.store(workflowContext, [makePayload('"world"')]);
8481
assert(other?.claimData.key);
8582
t.not(other.claimData.key, claim.claimData.key);
@@ -120,6 +117,15 @@ test('a target with no identity falls back to a bare digest key', async (t) => {
120117
t.regex(claim.claimData.key, /^v0\/d\/sha256\/[0-9a-f]{64}$/);
121118
});
122119

120+
test('missing context segments are encoded as the literal "null"', async (t) => {
121+
const driver = new S3StorageDriver({ client: new FakeS3Client(), bucket: 'b' });
122+
123+
const [claim] = await driver.store({ target: { kind: 'workflow', namespace: 'my-ns' } }, [makePayload('"x"')]);
124+
assert(claim?.claimData.key);
125+
126+
t.true(claim.claimData.key.startsWith('v0/ns/my-ns/wt/null/wi/null/ri/null/d/sha256/'));
127+
});
128+
123129
test('identical payloads in the same scope deduplicate to one upload', async (t) => {
124130
const client = new FakeS3Client();
125131
const driver = new S3StorageDriver({ client, bucket: 'b' });

contrib/external-storage-s3/src/driver.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ const PayloadProto = proto.temporal.api.common.v1.Payload;
1515

1616
const DRIVER_TYPE = 'aws.s3driver';
1717
const DEFAULT_MAX_PAYLOAD_SIZE = 50 * 1024 * 1024;
18+
/** Key segment written when a context value is empty or absent. */
19+
const NULL_SEGMENT = 'null';
1820

1921
/** Picks the destination bucket for a given payload. Enables dynamic per-payload routing. */
2022
export type BucketSelector = (context: StorageDriverStoreContext, payload: Payload) => string;
2123

22-
/** @experimental */
24+
/**
25+
* Configuration for an {@link S3StorageDriver}.
26+
*
27+
* @experimental
28+
*/
2329
export interface S3StorageDriverOptions {
2430
/**
2531
* An {@link S3StorageDriverClient} that performs the underlying requests,
@@ -47,7 +53,7 @@ export interface S3StorageDriverOptions {
4753
* literal `null`.
4854
*/
4955
function encodeKeySegment(value: string | undefined): string {
50-
if (!value) return 'null';
56+
if (!value) return NULL_SEGMENT;
5157
return encodeURIComponent(value).replace(/~/g, '%7E');
5258
}
5359

@@ -124,13 +130,13 @@ export class S3StorageDriver implements StorageDriver {
124130
private readonly maxPayloadSize: number;
125131

126132
constructor(options: S3StorageDriverOptions) {
127-
const { client, bucket, driverName, maxPayloadSize = DEFAULT_MAX_PAYLOAD_SIZE } = options;
133+
const { client, bucket, driverName = DRIVER_TYPE, maxPayloadSize = DEFAULT_MAX_PAYLOAD_SIZE } = options;
128134
if (!Number.isFinite(maxPayloadSize) || maxPayloadSize <= 0) {
129135
throw new ValueError(`maxPayloadSize must be a positive finite number, got ${String(maxPayloadSize)}`);
130136
}
131137
this.client = client;
132138
this.bucket = typeof bucket === 'string' ? () => bucket : bucket;
133-
this.name = driverName || DRIVER_TYPE;
139+
this.name = driverName;
134140
this.maxPayloadSize = maxPayloadSize;
135141
}
136142

0 commit comments

Comments
 (0)