Skip to content

Commit b6e8397

Browse files
authored
Use custom symbol-based impl of instanceof (#1166)
1 parent 251f965 commit b6e8397

27 files changed

+366
-330
lines changed

packages/activity/src/index.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import 'abort-controller/polyfill'; // eslint-disable-line import/no-unassigned-
7373
import { AsyncLocalStorage } from 'node:async_hooks';
7474
import { Logger, Duration } from '@temporalio/common';
7575
import { msToNumber } from '@temporalio/common/lib/time';
76+
import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
7677

7778
export {
7879
ActivityFunction,
@@ -82,8 +83,6 @@ export {
8283
UntypedActivities,
8384
} from '@temporalio/common';
8485

85-
const isCompleteAsyncError = Symbol.for('__temporal_isCompleteAsyncError');
86-
8786
/**
8887
* Throw this error from an Activity in order to make the Worker forget about this Activity.
8988
*
@@ -101,25 +100,8 @@ const isCompleteAsyncError = Symbol.for('__temporal_isCompleteAsyncError');
101100
*}
102101
*```
103102
*/
104-
export class CompleteAsyncError extends Error {
105-
public readonly name: string = 'CompleteAsyncError';
106-
107-
constructor() {
108-
super();
109-
}
110-
111-
/**
112-
* Marker to determine whether an error is an instance of CompleteAsyncError.
113-
*/
114-
protected readonly [isCompleteAsyncError] = true;
115-
116-
/**
117-
* Instanceof check that works when multiple versions of @temporalio/activity are installed.
118-
*/
119-
static is(error: unknown): error is CompleteAsyncError {
120-
return error instanceof CompleteAsyncError || (error as any)?.[isCompleteAsyncError] === true;
121-
}
122-
}
103+
@SymbolBasedInstanceOfError('CompleteAsyncError')
104+
export class CompleteAsyncError extends Error {}
123105

124106
// Make it safe to use @temporalio/activity with multiple versions installed.
125107
const asyncLocalStorageSymbol = Symbol.for('__temporal_activity_context_storage__');

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
encodeToPayloads,
77
filterNullAndUndefined,
88
} from '@temporalio/common/lib/internal-non-workflow';
9+
import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
910
import {
1011
BaseClient,
1112
BaseClientOptions,
@@ -21,25 +22,22 @@ import { rethrowKnownErrorTypes } from './helpers';
2122
* Thrown by {@link AsyncCompletionClient} when trying to complete or heartbeat an Activity that does not exist in the
2223
* system.
2324
*/
24-
export class ActivityNotFoundError extends Error {
25-
public readonly name = 'ActivityNotFoundError';
26-
}
25+
@SymbolBasedInstanceOfError('ActivityNotFoundError')
26+
export class ActivityNotFoundError extends Error {}
2727

2828
/**
2929
* Thrown by {@link AsyncCompletionClient} when trying to complete or heartbeat
3030
* an Activity for any reason apart from {@link ActivityNotFoundError}.
3131
*/
32-
export class ActivityCompletionError extends Error {
33-
public readonly name = 'ActivityCompletionError';
34-
}
32+
@SymbolBasedInstanceOfError('ActivityCompletionError')
33+
export class ActivityCompletionError extends Error {}
3534

3635
/**
3736
* Thrown by {@link AsyncCompletionClient.heartbeat} when the Workflow has
3837
* requested to cancel the reporting Activity.
3938
*/
40-
export class ActivityCancelledError extends Error {
41-
public readonly name = 'ActivityCancelledError';
42-
}
39+
@SymbolBasedInstanceOfError('ActivityCancelledError')
40+
export class ActivityCancelledError extends Error {}
4341

4442
/**
4543
* Options used to configure {@link AsyncCompletionClient}

packages/client/src/errors.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { ServiceError as GrpcServiceError } from '@grpc/grpc-js';
22
import { RetryState, TemporalFailure } from '@temporalio/common';
3+
import { isError, isRecord, SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
34

45
/**
56
* Generic Error class for errors coming from the service
67
*/
8+
@SymbolBasedInstanceOfError('ServiceError')
79
export class ServiceError extends Error {
8-
public readonly name: string = 'ServiceError';
910
public readonly cause?: Error;
1011

1112
constructor(message: string, opts?: { cause: Error }) {
@@ -23,8 +24,8 @@ export class ServiceError extends Error {
2324
* For example if the workflow is cancelled, `cause` will be set to
2425
* {@link CancelledFailure}.
2526
*/
27+
@SymbolBasedInstanceOfError('WorkflowFailedError')
2628
export class WorkflowFailedError extends Error {
27-
public readonly name: string = 'WorkflowFailedError';
2829
public constructor(
2930
message: string,
3031
public readonly cause: TemporalFailure | undefined,
@@ -40,15 +41,19 @@ export class WorkflowFailedError extends Error {
4041
*
4142
* Only thrown if asked not to follow the chain of execution (see {@link WorkflowOptions.followRuns}).
4243
*/
44+
@SymbolBasedInstanceOfError('WorkflowExecutionContinuedAsNewError')
4345
export class WorkflowContinuedAsNewError extends Error {
44-
public readonly name: string = 'WorkflowExecutionContinuedAsNewError';
4546
public constructor(message: string, public readonly newExecutionRunId: string) {
4647
super(message);
4748
}
4849
}
4950

5051
export function isGrpcServiceError(err: unknown): err is GrpcServiceError {
51-
return err instanceof Error && (err as any).details !== undefined && (err as any).metadata !== undefined;
52+
return (
53+
isError(err) &&
54+
typeof (err as GrpcServiceError)?.details === 'string' &&
55+
isRecord((err as GrpcServiceError).metadata)
56+
);
5257
}
5358

5459
/**

packages/client/src/iterators-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'abort-controller/polyfill'; // eslint-disable-line import/no-unassigned-import
22
import { EventEmitter, on, once } from 'node:events';
3+
import { isAbortError } from '@temporalio/common/lib/type-helpers';
34

45
export interface MapAsyncOptions {
56
/**
@@ -108,7 +109,7 @@ export async function* mapAsyncIterable<A, B>(
108109
yield res;
109110
}
110111
} catch (err: unknown) {
111-
if ((err as Error)?.name === 'AbortError') {
112+
if (isAbortError(err)) {
112113
return;
113114
}
114115
throw err;

packages/client/src/schedule-client.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '@temporalio/common/lib/internal-non-workflow';
1010
import { temporal } from '@temporalio/proto';
1111
import { optionalDateToTs, optionalTsToDate, optionalTsToMs, tsToDate } from '@temporalio/common/lib/time';
12+
import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
1213
import { CreateScheduleInput, CreateScheduleOutput, ScheduleClientInterceptor } from './interceptors';
1314
import { WorkflowService } from './types';
1415
import { isGrpcServiceError, ServiceError } from './errors';
@@ -527,9 +528,8 @@ export class ScheduleClient extends BaseClient {
527528
*
528529
* @experimental
529530
*/
531+
@SymbolBasedInstanceOfError('ScheduleAlreadyRunning')
530532
export class ScheduleAlreadyRunning extends Error {
531-
public readonly name: string = 'ScheduleAlreadyRunning';
532-
533533
constructor(message: string, public readonly scheduleId: string) {
534534
super(message);
535535
}
@@ -543,9 +543,8 @@ export class ScheduleAlreadyRunning extends Error {
543543
*
544544
* @experimental
545545
*/
546+
@SymbolBasedInstanceOfError('ScheduleNotFoundError')
546547
export class ScheduleNotFoundError extends Error {
547-
public readonly name: string = 'ScheduleNotFoundError';
548-
549548
constructor(message: string, public readonly scheduleId: string) {
550549
super(message);
551550
}

packages/client/src/task-queue-client.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { status } from '@grpc/grpc-js';
22
import { filterNullAndUndefined } from '@temporalio/common/lib/internal-non-workflow';
3-
import { assertNever, RequireAtLeastOne } from '@temporalio/common/lib/type-helpers';
3+
import { assertNever, SymbolBasedInstanceOfError, RequireAtLeastOne } from '@temporalio/common/lib/type-helpers';
44
import { temporal } from '@temporalio/proto';
55
import { BaseClient, BaseClientOptions, defaultBaseClientOptions, LoadedWithDefaults } from './base-client';
66
import { WorkflowService } from './types';
@@ -288,10 +288,5 @@ function reachabilityTypeFromProto(rtype: temporal.api.enums.v1.TaskReachability
288288
*
289289
* @experimental
290290
*/
291-
export class BuildIdNotFoundError extends Error {
292-
public readonly name: string = 'BuildIdNotFoundError';
293-
294-
constructor(message: string) {
295-
super(message);
296-
}
297-
}
291+
@SymbolBasedInstanceOfError('BuildIdNotFoundError')
292+
export class BuildIdNotFoundError extends Error {}

packages/client/src/workflow-client.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '@temporalio/common';
2323
import { composeInterceptors } from '@temporalio/common/lib/interceptors';
2424
import { History } from '@temporalio/common/lib/proto-utils';
25+
import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
2526
import {
2627
decodeArrayFromPayloads,
2728
decodeFromPayloadsAtIndex,
@@ -992,15 +993,15 @@ export class WorkflowClient extends BaseClient {
992993
}
993994
}
994995

996+
@SymbolBasedInstanceOfError('QueryRejectedError')
995997
export class QueryRejectedError extends Error {
996-
public readonly name: string = 'QueryRejectedError';
997998
constructor(public readonly status: temporal.api.enums.v1.WorkflowExecutionStatus) {
998999
super('Query rejected');
9991000
}
10001001
}
10011002

1003+
@SymbolBasedInstanceOfError('QueryNotRegisteredError')
10021004
export class QueryNotRegisteredError extends Error {
1003-
public readonly name: string = 'QueryNotRegisteredError';
10041005
constructor(message: string, public readonly code: grpcStatus) {
10051006
super(message);
10061007
}

packages/common/src/converter/failure-converter.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
TimeoutFailure,
1313
TimeoutType,
1414
} from '../failure';
15-
import { hasOwnProperties, isRecord } from '../type-helpers';
15+
import { isError } from '../type-helpers';
1616
import { arrayFromPayloads, fromPayloadsAtIndex, PayloadConverter, toPayloads } from './payload-converter';
1717

1818
/**
@@ -219,7 +219,7 @@ export class DefaultFailureConverter implements FailureConverter {
219219
}
220220

221221
errorToFailureInner(err: unknown, payloadConverter: PayloadConverter): ProtoFailure {
222-
if (TemporalFailure.is(err)) {
222+
if (err instanceof TemporalFailure) {
223223
if (err.failure) return err.failure;
224224
const base = {
225225
message: err.message,
@@ -228,7 +228,7 @@ export class DefaultFailureConverter implements FailureConverter {
228228
source: FAILURE_SOURCE,
229229
};
230230

231-
if (ActivityFailure.is(err)) {
231+
if (err instanceof ActivityFailure) {
232232
return {
233233
...base,
234234
activityFailureInfo: {
@@ -237,7 +237,7 @@ export class DefaultFailureConverter implements FailureConverter {
237237
},
238238
};
239239
}
240-
if (ChildWorkflowFailure.is(err)) {
240+
if (err instanceof ChildWorkflowFailure) {
241241
return {
242242
...base,
243243
childWorkflowExecutionFailureInfo: {
@@ -247,7 +247,7 @@ export class DefaultFailureConverter implements FailureConverter {
247247
},
248248
};
249249
}
250-
if (ApplicationFailure.is(err)) {
250+
if (err instanceof ApplicationFailure) {
251251
return {
252252
...base,
253253
applicationFailureInfo: {
@@ -260,7 +260,7 @@ export class DefaultFailureConverter implements FailureConverter {
260260
},
261261
};
262262
}
263-
if (CancelledFailure.is(err)) {
263+
if (err instanceof CancelledFailure) {
264264
return {
265265
...base,
266266
canceledFailureInfo: {
@@ -271,7 +271,7 @@ export class DefaultFailureConverter implements FailureConverter {
271271
},
272272
};
273273
}
274-
if (TimeoutFailure.is(err)) {
274+
if (err instanceof TimeoutFailure) {
275275
return {
276276
...base,
277277
timeoutFailureInfo: {
@@ -282,13 +282,13 @@ export class DefaultFailureConverter implements FailureConverter {
282282
},
283283
};
284284
}
285-
if (ServerFailure.is(err)) {
285+
if (err instanceof ServerFailure) {
286286
return {
287287
...base,
288288
serverFailureInfo: { nonRetryable: err.nonRetryable },
289289
};
290290
}
291-
if (TerminatedFailure.is(err)) {
291+
if (err instanceof TerminatedFailure) {
292292
return {
293293
...base,
294294
terminatedFailureInfo: {},
@@ -302,12 +302,12 @@ export class DefaultFailureConverter implements FailureConverter {
302302
source: FAILURE_SOURCE,
303303
};
304304

305-
if (isRecord(err) && hasOwnProperties(err, ['message', 'stack'])) {
305+
if (isError(err)) {
306306
return {
307307
...base,
308308
message: String(err.message) ?? '',
309-
stackTrace: cutoffStackTrace(String(err.stack)),
310-
cause: this.optionalErrorToOptionalFailure(err.cause, payloadConverter),
309+
stackTrace: cutoffStackTrace(err.stack),
310+
cause: this.optionalErrorToOptionalFailure((err as any).cause, payloadConverter),
311311
};
312312
}
313313

packages/common/src/converter/payload-converter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export class SearchAttributePayloadConverter implements PayloadConverter {
260260
validNonDateTypes = ['string', 'number', 'boolean'];
261261

262262
public toPayload(values: unknown): Payload {
263-
if (!(values instanceof Array)) {
263+
if (!Array.isArray(values)) {
264264
throw new ValueError(`SearchAttribute value must be an array`);
265265
}
266266

@@ -309,7 +309,7 @@ export class SearchAttributePayloadConverter implements PayloadConverter {
309309
}
310310

311311
const value = this.jsonConverter.fromPayload(payload);
312-
let arrayWrappedValue = value instanceof Array ? value : [value];
312+
let arrayWrappedValue = Array.isArray(value) ? value : [value];
313313

314314
const searchAttributeType = decode(payload.metadata.type);
315315
if (searchAttributeType === 'Datetime') {

0 commit comments

Comments
 (0)