@temporalio/common 1.8.1 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,6 @@ export function isRecord(value: unknown): value is Record<string, unknown> {
21
21
  return typeof value === 'object' && value !== null;
22
22
  }
23
23
 
24
- // ts-prune-ignore-next
25
24
  export function hasOwnProperty<X extends Record<string, unknown>, Y extends PropertyKey>(
26
25
  record: X,
27
26
  prop: Y
@@ -36,15 +35,27 @@ export function hasOwnProperties<X extends Record<string, unknown>, Y extends Pr
36
35
  return props.every((prop) => prop in record);
37
36
  }
38
37
 
38
+ export function isError(error: unknown): error is Error {
39
+ return (
40
+ isRecord(error) &&
41
+ typeof error.name === 'string' &&
42
+ typeof error.message === 'string' &&
43
+ (error.stack == null || typeof error.stack === 'string')
44
+ );
45
+ }
46
+
47
+ export function isAbortError(error: unknown): error is Error & { name: 'AbortError' } {
48
+ return isError(error) && error.name === 'AbortError';
49
+ }
50
+
39
51
  /**
40
52
  * Get `error.message` (or `undefined` if not present)
41
53
  */
42
54
  export function errorMessage(error: unknown): string | undefined {
43
- if (typeof error === 'string') {
44
- return error;
45
- }
46
- if (error instanceof Error) {
55
+ if (isError(error)) {
47
56
  return error.message;
57
+ } else if (typeof error === 'string') {
58
+ return error;
48
59
  }
49
60
  return undefined;
50
61
  }
@@ -52,16 +63,17 @@ export function errorMessage(error: unknown): string | undefined {
52
63
  interface ErrorWithCode {
53
64
  code: string;
54
65
  }
66
+
67
+ function isErrorWithCode(error: unknown): error is ErrorWithCode {
68
+ return isRecord(error) && typeof error.code === 'string';
69
+ }
70
+
55
71
  /**
56
72
  * Get `error.code` (or `undefined` if not present)
57
73
  */
58
74
  export function errorCode(error: unknown): string | undefined {
59
- if (
60
- typeof error === 'object' &&
61
- (error as ErrorWithCode).code !== undefined &&
62
- typeof (error as ErrorWithCode).code === 'string'
63
- ) {
64
- return (error as ErrorWithCode).code;
75
+ if (isErrorWithCode(error)) {
76
+ return error.code;
65
77
  }
66
78
 
67
79
  return undefined;
@@ -73,3 +85,58 @@ export function errorCode(error: unknown): string | undefined {
73
85
  export function assertNever(msg: string, x: never): never {
74
86
  throw new TypeError(msg + ': ' + x);
75
87
  }
88
+
89
+ export type Class<E extends Error> = {
90
+ new (...args: any[]): E;
91
+ prototype: E;
92
+ };
93
+
94
+ /**
95
+ * A decorator to be used on error classes. It adds the 'name' property AND provides a custom
96
+ * 'instanceof' handler that works correctly across execution contexts.
97
+ *
98
+ * ### Details ###
99
+ *
100
+ * According to the EcmaScript's spec, the default behavior of JavaScript's `x instanceof Y` operator is to walk up the
101
+ * prototype chain of object 'x', checking if any constructor in that hierarchy is _exactly the same object_ as the
102
+ * constructor function 'Y'.
103
+ *
104
+ * Unfortunately, it happens in various situations that different constructor function objects get created for what
105
+ * appears to be the very same class. This leads to surprising behavior where `instanceof` returns false though it is
106
+ * known that the object is indeed an instance of that class. One particular case where this happens is when constructor
107
+ * 'Y' belongs to a different realm than the constuctor with which 'x' was instantiated. Another case is when two copies
108
+ * of the same library gets loaded in the same realm.
109
+ *
110
+ * In practice, this tends to cause issues when crossing the workflow-sandboxing boundary (since Node's vm module
111
+ * really creates new execution realms), as well as when running tests using Jest (see https://github.com/jestjs/jest/issues/2549
112
+ * for some details on that one).
113
+ *
114
+ * This function injects a custom 'instanceof' handler into the prototype of 'clazz', which is both cross-realm safe and
115
+ * cross-copies-of-the-same-lib safe. It works by adding a special symbol property to the prototype of 'clazz', and then
116
+ * checking for the presence of that symbol.
117
+ */
118
+ export function SymbolBasedInstanceOfError<E extends Error>(markerName: string): (clazz: Class<E>) => void {
119
+ return (clazz: Class<E>): void => {
120
+ const marker = Symbol.for(`__temporal_is${markerName}`);
121
+
122
+ Object.defineProperty(clazz.prototype, 'name', { value: markerName, enumerable: true });
123
+ Object.defineProperty(clazz.prototype, marker, { value: true, enumerable: false });
124
+ Object.defineProperty(clazz, Symbol.hasInstance, {
125
+ // eslint-disable-next-line object-shorthand
126
+ value: function (this: any, error: object): boolean {
127
+ if (this === clazz) {
128
+ return isRecord(error) && (error as any)[marker] === true;
129
+ } else {
130
+ // 'this' must be a _subclass_ of clazz that doesn't redefined [Symbol.hasInstance], so that it inherited
131
+ // from clazz's [Symbol.hasInstance]. If we don't handle this particular situation, then
132
+ // `x instanceof SubclassOfParent` would return true for any instance of 'Parent', which is clearly wrong.
133
+ //
134
+ // Ideally, it'd be preferable to avoid this case entirely, by making sure that all subclasses of 'clazz'
135
+ // redefine [Symbol.hasInstance], but we can't enforce that. We therefore fallback to the default instanceof
136
+ // behavior (which is NOT cross-realm safe).
137
+ return this.prototype.isPrototypeOf(error); // eslint-disable-line no-prototype-builtins
138
+ }
139
+ },
140
+ });
141
+ };
142
+ }
@@ -0,0 +1,32 @@
1
+ import type { coresdk } from '@temporalio/proto';
2
+ import type { VersioningIntent as VersioningIntentString } from './versioning-intent';
3
+ import { assertNever, checkExtends } from './type-helpers';
4
+
5
+ // Avoid importing the proto implementation to reduce workflow bundle size
6
+ // Copied from coresdk.common.VersioningIntent
7
+ /**
8
+ * Protobuf enum representation of {@link VersioningIntentString}.
9
+ *
10
+ * @experimental
11
+ */
12
+ export enum VersioningIntent {
13
+ UNSPECIFIED = 0,
14
+ COMPATIBLE = 1,
15
+ DEFAULT = 2,
16
+ }
17
+
18
+ checkExtends<coresdk.common.VersioningIntent, VersioningIntent>();
19
+ checkExtends<VersioningIntent, coresdk.common.VersioningIntent>();
20
+
21
+ export function versioningIntentToProto(intent: VersioningIntentString | undefined): VersioningIntent {
22
+ switch (intent) {
23
+ case 'DEFAULT':
24
+ return VersioningIntent.DEFAULT;
25
+ case 'COMPATIBLE':
26
+ return VersioningIntent.COMPATIBLE;
27
+ case undefined:
28
+ return VersioningIntent.UNSPECIFIED;
29
+ default:
30
+ assertNever('Unexpected VersioningIntent', intent);
31
+ }
32
+ }
@@ -52,7 +52,7 @@ export interface BaseWorkflowOptions {
52
52
  *
53
53
  * *Note: A Workflow can never be started with a Workflow Id of a Running Workflow.*
54
54
  *
55
- * @default {@link WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY}
55
+ * @default {@link WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE}
56
56
  */
57
57
  workflowIdReusePolicy?: WorkflowIdReusePolicy;
58
58