Skip to content

Commit 5080344

Browse files
authored
chore(client): Extract a BaseClient super class (#957)
1 parent 615c0bc commit 5080344

File tree

7 files changed

+165
-318
lines changed

7 files changed

+165
-318
lines changed

packages/client/src/async-completion-client.ts

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { Status } from '@grpc/grpc-js/build/src/constants';
2-
import { DataConverter, ensureTemporalFailure, LoadedDataConverter } from '@temporalio/common';
2+
import { ensureTemporalFailure } from '@temporalio/common';
33
import {
44
encodeErrorToFailure,
55
encodeToPayloads,
66
filterNullAndUndefined,
7-
isLoadedDataConverter,
8-
loadDataConverter,
97
} from '@temporalio/common/lib/internal-non-workflow';
10-
import { Replace } from '@temporalio/common/lib/type-helpers';
11-
import os from 'os';
12-
import { Connection } from './connection';
8+
import {
9+
BaseClient,
10+
BaseClientOptions,
11+
defaultBaseClientOptions,
12+
LoadedWithDefaults,
13+
WithDefaults,
14+
} from './base-client';
1315
import { isServerErrorResponse } from './errors';
14-
import { ConnectionLike, WorkflowService } from './types';
16+
import { WorkflowService } from './types';
1517

1618
/**
1719
* Thrown by {@link AsyncCompletionClient} when trying to complete or heartbeat an Activity that does not exist in the
@@ -40,46 +42,12 @@ export class ActivityCancelledError extends Error {
4042
/**
4143
* Options used to configure {@link AsyncCompletionClient}
4244
*/
43-
export interface AsyncCompletionClientOptions {
44-
/**
45-
* {@link DataConverter} or {@link LoadedDataConverter} to use for serializing and deserializing payloads
46-
*/
47-
dataConverter?: DataConverter | LoadedDataConverter;
45+
export type AsyncCompletionClientOptions = BaseClientOptions;
4846

49-
/**
50-
* Identity to report to the server
51-
*
52-
* @default `${process.pid}@${os.hostname()}`
53-
*/
54-
identity?: string;
55-
56-
connection?: ConnectionLike;
47+
export type LoadedAsyncCompletionClientOptions = LoadedWithDefaults<AsyncCompletionClientOptions>;
5748

58-
/**
59-
* Server namespace
60-
*
61-
* @default default
62-
*/
63-
namespace?: string;
64-
}
65-
66-
export type AsyncCompletionClientOptionsWithDefaults = Replace<
67-
Required<AsyncCompletionClientOptions>,
68-
{
69-
connection?: ConnectionLike;
70-
}
71-
>;
72-
73-
export type LoadedAsyncCompletionClientOptions = AsyncCompletionClientOptionsWithDefaults & {
74-
loadedDataConverter: LoadedDataConverter;
75-
};
76-
77-
export function defaultAsyncCompletionClientOptions(): AsyncCompletionClientOptionsWithDefaults {
78-
return {
79-
dataConverter: {},
80-
identity: `${process.pid}@${os.hostname()}`,
81-
namespace: 'default',
82-
};
49+
function defaultAsyncCompletionClientOptions(): WithDefaults<AsyncCompletionClientOptions> {
50+
return defaultBaseClientOptions();
8351
}
8452

8553
/**
@@ -101,18 +69,15 @@ export interface FullActivityId {
10169
* Typically this client should not be instantiated directly, instead create the high level {@link Client} and use
10270
* {@link Client.activity} to complete async activities.
10371
*/
104-
export class AsyncCompletionClient {
72+
export class AsyncCompletionClient extends BaseClient {
10573
public readonly options: LoadedAsyncCompletionClientOptions;
106-
public readonly connection: ConnectionLike;
10774

10875
constructor(options?: AsyncCompletionClientOptions) {
109-
this.connection = options?.connection ?? Connection.lazy();
110-
const dataConverter = options?.dataConverter;
111-
const loadedDataConverter = isLoadedDataConverter(dataConverter) ? dataConverter : loadDataConverter(dataConverter);
76+
super(options);
11277
this.options = {
11378
...defaultAsyncCompletionClientOptions(),
11479
...filterNullAndUndefined(options ?? {}),
115-
loadedDataConverter,
80+
loadedDataConverter: this.dataConverter,
11681
};
11782
}
11883

@@ -126,10 +91,6 @@ export class AsyncCompletionClient {
12691
return this.connection.workflowService;
12792
}
12893

129-
protected get dataConverter(): LoadedDataConverter {
130-
return this.options.loadedDataConverter;
131-
}
132-
13394
/**
13495
* Transforms grpc errors into well defined TS errors.
13596
*/

packages/client/src/base-client.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { DataConverter, LoadedDataConverter } from '@temporalio/common';
2+
import { isLoadedDataConverter, loadDataConverter } from '@temporalio/common/lib/internal-non-workflow';
3+
import { Connection } from './connection';
4+
import { ConnectionLike, Metadata } from './types';
5+
import os from 'os';
6+
7+
export interface BaseClientOptions {
8+
/**
9+
* {@link DataConverter} to use for serializing and deserializing payloads
10+
*/
11+
dataConverter?: DataConverter;
12+
13+
/**
14+
* Identity to report to the server
15+
*
16+
* @default `${process.pid}@${os.hostname()}`
17+
*/
18+
identity?: string;
19+
20+
/**
21+
* Connection to use to communicate with the server.
22+
*
23+
* By default, connects to localhost.
24+
*
25+
* Connections are expensive to construct and should be reused.
26+
*/
27+
connection?: ConnectionLike;
28+
29+
/**
30+
* Server namespace
31+
*
32+
* @default default
33+
*/
34+
namespace?: string;
35+
}
36+
37+
export type WithDefaults<Options extends BaseClientOptions> = //
38+
Required<Omit<Options, 'connection'>> & Pick<Options, 'connection'>;
39+
40+
export type LoadedWithDefaults<Options extends BaseClientOptions> = //
41+
WithDefaults<Options> & {
42+
loadedDataConverter: LoadedDataConverter;
43+
};
44+
45+
export function defaultBaseClientOptions(): WithDefaults<BaseClientOptions> {
46+
return {
47+
dataConverter: {},
48+
identity: `${process.pid}@${os.hostname()}`,
49+
namespace: 'default',
50+
};
51+
}
52+
53+
export class BaseClient {
54+
public readonly connection: ConnectionLike;
55+
private readonly loadedDataConverter: LoadedDataConverter;
56+
57+
protected constructor(options?: BaseClientOptions) {
58+
this.connection = options?.connection ?? Connection.lazy();
59+
const dataConverter = options?.dataConverter ?? {};
60+
this.loadedDataConverter = isLoadedDataConverter(dataConverter) ? dataConverter : loadDataConverter(dataConverter);
61+
}
62+
63+
/**
64+
* Set the deadline for any service requests executed in `fn`'s scope.
65+
*/
66+
public async withDeadline<R>(deadline: number | Date, fn: () => Promise<R>): Promise<R> {
67+
return await this.connection.withDeadline(deadline, fn);
68+
}
69+
70+
/**
71+
* Set metadata for any service requests executed in `fn`'s scope.
72+
*
73+
* @returns returned value of `fn`
74+
*
75+
* @see {@link Connection.withMetadata}
76+
*/
77+
public async withMetadata<R>(metadata: Metadata, fn: () => Promise<R>): Promise<R> {
78+
return await this.connection.withMetadata(metadata, fn);
79+
}
80+
81+
protected get dataConverter(): LoadedDataConverter {
82+
return this.loadedDataConverter;
83+
}
84+
}

packages/client/src/client.ts

Lines changed: 30 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,20 @@
1-
import { DataConverter, LoadedDataConverter } from '@temporalio/common';
2-
import { filterNullAndUndefined, loadDataConverter } from '@temporalio/common/lib/internal-non-workflow';
3-
import { Replace } from '@temporalio/common/lib/type-helpers';
1+
import { filterNullAndUndefined } from '@temporalio/common/lib/internal-non-workflow';
42
import { temporal } from '@temporalio/proto';
5-
import os from 'os';
63
import { AsyncCompletionClient } from './async-completion-client';
7-
import { Connection } from './connection';
4+
import { BaseClient, BaseClientOptions, defaultBaseClientOptions, LoadedWithDefaults } from './base-client';
85
import { ClientInterceptors } from './interceptors';
96
import { ScheduleClient } from './schedule-client';
10-
import { ConnectionLike, Metadata, WorkflowService } from './types';
7+
import { WorkflowService } from './types';
118
import { WorkflowClient } from './workflow-client';
129

13-
export interface ClientOptions {
14-
/**
15-
* {@link DataConverter} to use for serializing and deserializing payloads
16-
*/
17-
dataConverter?: DataConverter;
18-
10+
export interface ClientOptions extends BaseClientOptions {
1911
/**
2012
* Used to override and extend default Connection functionality
2113
*
2214
* Useful for injecting auth headers and tracing Workflow executions
2315
*/
2416
interceptors?: ClientInterceptors;
2517

26-
/**
27-
* Identity to report to the server
28-
*
29-
* @default `${process.pid}@${os.hostname()}`
30-
*/
31-
identity?: string;
32-
33-
/**
34-
* Connection to use to communicate with the server.
35-
*
36-
* By default `WorkflowClient` connects to localhost.
37-
*
38-
* Connections are expensive to construct and should be reused.
39-
*/
40-
connection?: ConnectionLike;
41-
42-
/**
43-
* Server namespace
44-
*
45-
* @default default
46-
*/
47-
namespace?: string;
48-
4918
workflow?: {
5019
/**
5120
* Should a query be rejected by closed and failed workflows
@@ -56,37 +25,12 @@ export interface ClientOptions {
5625
};
5726
}
5827

59-
export type ClientOptionsWithDefaults = Replace<
60-
Required<ClientOptions>,
61-
{
62-
connection?: ConnectionLike;
63-
}
64-
>;
65-
66-
export type LoadedClientOptions = ClientOptionsWithDefaults & {
67-
loadedDataConverter: LoadedDataConverter;
68-
};
69-
70-
export function defaultClientOptions(): ClientOptionsWithDefaults {
71-
return {
72-
dataConverter: {},
73-
identity: `${process.pid}@${os.hostname()}`,
74-
interceptors: {},
75-
namespace: 'default',
76-
workflow: {
77-
queryRejectCondition: temporal.api.enums.v1.QueryRejectCondition.QUERY_REJECT_CONDITION_UNSPECIFIED,
78-
},
79-
};
80-
}
28+
export type LoadedClientOptions = LoadedWithDefaults<ClientOptions>;
8129

8230
/**
8331
* High level SDK client.
8432
*/
85-
export class Client {
86-
/**
87-
* Underlying gRPC connection to the Temporal service
88-
*/
89-
public readonly connection: ConnectionLike;
33+
export class Client extends BaseClient {
9034
public readonly options: LoadedClientOptions;
9135
/**
9236
* Workflow sub-client - use to start and interact with Workflows
@@ -104,35 +48,43 @@ export class Client {
10448
public readonly schedule: ScheduleClient;
10549

10650
constructor(options?: ClientOptions) {
107-
this.connection = options?.connection ?? Connection.lazy();
108-
this.options = {
109-
...defaultClientOptions(),
110-
...filterNullAndUndefined(options ?? {}),
111-
loadedDataConverter: loadDataConverter(options?.dataConverter),
112-
};
51+
super(options);
11352

114-
const { workflow, loadedDataConverter, interceptors, ...base } = this.options;
53+
const { interceptors, workflow, ...commonOptions } = options ?? {};
11554

11655
this.workflow = new WorkflowClient({
117-
...base,
118-
...workflow,
56+
...commonOptions,
57+
...(workflow ?? {}),
11958
connection: this.connection,
120-
dataConverter: loadedDataConverter,
121-
interceptors: interceptors.workflow,
59+
dataConverter: this.dataConverter,
60+
interceptors: interceptors?.workflow,
12261
});
12362

12463
this.activity = new AsyncCompletionClient({
125-
...base,
64+
...commonOptions,
12665
connection: this.connection,
127-
dataConverter: loadedDataConverter,
66+
dataConverter: this.dataConverter,
12867
});
12968

13069
this.schedule = new ScheduleClient({
131-
...base,
70+
...commonOptions,
13271
connection: this.connection,
133-
dataConverter: loadedDataConverter,
134-
interceptors: interceptors.schedule,
72+
dataConverter: this.dataConverter,
73+
interceptors: interceptors?.schedule,
13574
});
75+
76+
this.options = {
77+
...defaultBaseClientOptions(),
78+
...filterNullAndUndefined(commonOptions),
79+
loadedDataConverter: this.dataConverter,
80+
interceptors: {
81+
workflow: this.workflow.options.interceptors,
82+
schedule: this.schedule.options.interceptors,
83+
},
84+
workflow: {
85+
queryRejectCondition: this.workflow.options.queryRejectCondition,
86+
},
87+
};
13688
}
13789

13890
/**
@@ -144,22 +96,4 @@ export class Client {
14496
get workflowService(): WorkflowService {
14597
return this.connection.workflowService;
14698
}
147-
148-
/**
149-
* Set the deadline for any service requests executed in `fn`'s scope.
150-
*/
151-
async withDeadline<R>(deadline: number | Date, fn: () => Promise<R>): Promise<R> {
152-
return await this.connection.withDeadline(deadline, fn);
153-
}
154-
155-
/**
156-
* Set metadata for any service requests executed in `fn`'s scope.
157-
*
158-
* @returns returned value of `fn`
159-
*
160-
* @see {@link Connection.withMetadata}
161-
*/
162-
async withMetadata<R>(metadata: Metadata, fn: () => Promise<R>): Promise<R> {
163-
return await this.connection.withMetadata(metadata, fn);
164-
}
16599
}

packages/client/src/interceptors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export interface WorkflowClientCallsInterceptorFactoryInput {
128128
* @deprecated: Please define interceptors directly, without factory
129129
*/
130130
export interface WorkflowClientCallsInterceptorFactory {
131+
// eslint-disable-next-line deprecation/deprecation
131132
(input: WorkflowClientCallsInterceptorFactoryInput): WorkflowClientCallsInterceptor;
132133
}
133134

@@ -138,6 +139,7 @@ export interface WorkflowClientCallsInterceptorFactory {
138139
*/
139140
export interface WorkflowClientInterceptors {
140141
/** @deprecated */
142+
// eslint-disable-next-line deprecation/deprecation
141143
calls?: WorkflowClientCallsInterceptorFactory[];
142144
}
143145

@@ -173,6 +175,7 @@ export type CreateScheduleOutput = {
173175
* NOTE: Currently only for {@link WorkflowClient} and {@link ScheduleClient}. More will be added later as needed.
174176
*/
175177
export interface ClientInterceptors {
178+
// eslint-disable-next-line deprecation/deprecation
176179
workflow?: WorkflowClientInterceptors | WorkflowClientInterceptor[];
177180

178181
/**

0 commit comments

Comments
 (0)