@temporalio/common 1.11.3 → 1.11.5
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.
- package/lib/activity-options.d.ts +8 -6
- package/lib/activity-options.js +13 -12
- package/lib/activity-options.js.map +1 -1
- package/lib/converter/failure-converter.js +8 -6
- package/lib/converter/failure-converter.js.map +1 -1
- package/lib/converter/payload-converter.js +7 -7
- package/lib/converter/payload-converter.js.map +1 -1
- package/lib/deprecated-time.js +9 -10
- package/lib/deprecated-time.js.map +1 -1
- package/lib/encoding.js +3 -3
- package/lib/encoding.js.map +1 -1
- package/lib/failure.d.ts +51 -23
- package/lib/failure.js +65 -32
- package/lib/failure.js.map +1 -1
- package/lib/index.js +4 -5
- package/lib/index.js.map +1 -1
- package/lib/interceptors.js +1 -2
- package/lib/interceptors.js.map +1 -1
- package/lib/interfaces.d.ts +5 -4
- package/lib/interfaces.js +4 -5
- package/lib/interfaces.js.map +1 -1
- package/lib/internal-non-workflow/codec-helpers.js +22 -23
- package/lib/internal-non-workflow/codec-helpers.js.map +1 -1
- package/lib/internal-non-workflow/data-converter-helpers.js +3 -4
- package/lib/internal-non-workflow/data-converter-helpers.js.map +1 -1
- package/lib/internal-non-workflow/parse-host-uri.js +3 -4
- package/lib/internal-non-workflow/parse-host-uri.js.map +1 -1
- package/lib/internal-non-workflow/proxy-config.js +1 -2
- package/lib/internal-non-workflow/proxy-config.js.map +1 -1
- package/lib/internal-non-workflow/tls-config.d.ts +0 -1
- package/lib/internal-non-workflow/tls-config.js +1 -2
- package/lib/internal-non-workflow/tls-config.js.map +1 -1
- package/lib/internal-non-workflow/utils.js +1 -2
- package/lib/internal-non-workflow/utils.js.map +1 -1
- package/lib/internal-workflow/enums-helpers.d.ts +170 -0
- package/lib/internal-workflow/enums-helpers.js +172 -0
- package/lib/internal-workflow/enums-helpers.js.map +1 -0
- package/lib/internal-workflow/index.d.ts +1 -0
- package/lib/internal-workflow/index.js +18 -0
- package/lib/internal-workflow/index.js.map +1 -0
- package/lib/proto-utils.js +4 -5
- package/lib/proto-utils.js.map +1 -1
- package/lib/retry-policy.js +2 -3
- package/lib/retry-policy.js.map +1 -1
- package/lib/time.js +12 -13
- package/lib/time.js.map +1 -1
- package/lib/type-helpers.d.ts +18 -0
- package/lib/type-helpers.js +12 -13
- package/lib/type-helpers.js.map +1 -1
- package/lib/versioning-intent-enum.js +2 -2
- package/lib/versioning-intent-enum.js.map +1 -1
- package/lib/workflow-options.d.ts +69 -17
- package/lib/workflow-options.js +64 -23
- package/lib/workflow-options.js.map +1 -1
- package/package.json +3 -3
- package/src/activity-options.ts +24 -13
- package/src/converter/failure-converter.ts +10 -6
- package/src/converter/payload-converter.ts +1 -1
- package/src/failure.ts +95 -28
- package/src/interfaces.ts +5 -4
- package/src/internal-non-workflow/data-converter-helpers.ts +1 -1
- package/src/internal-workflow/enums-helpers.ts +301 -0
- package/src/internal-workflow/index.ts +1 -0
- package/src/type-helpers.ts +79 -1
- package/src/workflow-options.ts +110 -23
package/src/failure.ts
CHANGED
|
@@ -1,38 +1,105 @@
|
|
|
1
1
|
import type { temporal } from '@temporalio/proto';
|
|
2
|
-
import {
|
|
2
|
+
import { errorMessage, isRecord, SymbolBasedInstanceOfError } from './type-helpers';
|
|
3
3
|
import { Duration } from './time';
|
|
4
|
+
import { makeProtoEnumConverters } from './internal-workflow';
|
|
4
5
|
|
|
5
6
|
export const FAILURE_SOURCE = 'TypeScriptSDK';
|
|
6
7
|
export type ProtoFailure = temporal.api.failure.v1.IFailure;
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
TIMEOUT_TYPE_SCHEDULE_TO_START = 2,
|
|
14
|
-
TIMEOUT_TYPE_SCHEDULE_TO_CLOSE = 3,
|
|
15
|
-
TIMEOUT_TYPE_HEARTBEAT = 4,
|
|
16
|
-
}
|
|
9
|
+
export const TimeoutType = {
|
|
10
|
+
START_TO_CLOSE: 'START_TO_CLOSE',
|
|
11
|
+
SCHEDULE_TO_START: 'SCHEDULE_TO_START',
|
|
12
|
+
SCHEDULE_TO_CLOSE: 'SCHEDULE_TO_CLOSE',
|
|
13
|
+
HEARTBEAT: 'HEARTBEAT',
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
15
|
+
/** @deprecated Use {@link START_TO_CLOSE} instead. */
|
|
16
|
+
TIMEOUT_TYPE_START_TO_CLOSE: 'START_TO_CLOSE', // eslint-disable-line deprecation/deprecation
|
|
17
|
+
|
|
18
|
+
/** @deprecated Use {@link SCHEDULE_TO_START} instead. */
|
|
19
|
+
TIMEOUT_TYPE_SCHEDULE_TO_START: 'SCHEDULE_TO_START', // eslint-disable-line deprecation/deprecation
|
|
20
|
+
|
|
21
|
+
/** @deprecated Use {@link SCHEDULE_TO_CLOSE} instead. */
|
|
22
|
+
TIMEOUT_TYPE_SCHEDULE_TO_CLOSE: 'SCHEDULE_TO_CLOSE', // eslint-disable-line deprecation/deprecation
|
|
23
|
+
|
|
24
|
+
/** @deprecated Use {@link HEARTBEAT} instead. */
|
|
25
|
+
TIMEOUT_TYPE_HEARTBEAT: 'HEARTBEAT', // eslint-disable-line deprecation/deprecation
|
|
26
|
+
|
|
27
|
+
/** @deprecated Use `undefined` instead. */
|
|
28
|
+
TIMEOUT_TYPE_UNSPECIFIED: undefined, // eslint-disable-line deprecation/deprecation
|
|
29
|
+
} as const;
|
|
30
|
+
export type TimeoutType = (typeof TimeoutType)[keyof typeof TimeoutType];
|
|
31
|
+
|
|
32
|
+
export const [encodeTimeoutType, decodeTimeoutType] = makeProtoEnumConverters<
|
|
33
|
+
temporal.api.enums.v1.TimeoutType,
|
|
34
|
+
typeof temporal.api.enums.v1.TimeoutType,
|
|
35
|
+
keyof typeof temporal.api.enums.v1.TimeoutType,
|
|
36
|
+
typeof TimeoutType,
|
|
37
|
+
'TIMEOUT_TYPE_'
|
|
38
|
+
>(
|
|
39
|
+
{
|
|
40
|
+
[TimeoutType.START_TO_CLOSE]: 1,
|
|
41
|
+
[TimeoutType.SCHEDULE_TO_START]: 2,
|
|
42
|
+
[TimeoutType.SCHEDULE_TO_CLOSE]: 3,
|
|
43
|
+
[TimeoutType.HEARTBEAT]: 4,
|
|
44
|
+
UNSPECIFIED: 0,
|
|
45
|
+
} as const,
|
|
46
|
+
'TIMEOUT_TYPE_'
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export const RetryState = {
|
|
50
|
+
IN_PROGRESS: 'IN_PROGRESS',
|
|
51
|
+
NON_RETRYABLE_FAILURE: 'NON_RETRYABLE_FAILURE',
|
|
52
|
+
TIMEOUT: 'TIMEOUT',
|
|
53
|
+
MAXIMUM_ATTEMPTS_REACHED: 'MAXIMUM_ATTEMPTS_REACHED',
|
|
54
|
+
RETRY_POLICY_NOT_SET: 'RETRY_POLICY_NOT_SET',
|
|
55
|
+
INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
|
|
56
|
+
CANCEL_REQUESTED: 'CANCEL_REQUESTED',
|
|
57
|
+
|
|
58
|
+
/** @deprecated Use {@link IN_PROGRESS} instead. */
|
|
59
|
+
RETRY_STATE_IN_PROGRESS: 'IN_PROGRESS', // eslint-disable-line deprecation/deprecation
|
|
60
|
+
|
|
61
|
+
/** @deprecated Use {@link NON_RETRYABLE_FAILURE} instead. */
|
|
62
|
+
RETRY_STATE_NON_RETRYABLE_FAILURE: 'NON_RETRYABLE_FAILURE', // eslint-disable-line deprecation/deprecation
|
|
63
|
+
|
|
64
|
+
/** @deprecated Use {@link TIMEOUT} instead. */
|
|
65
|
+
RETRY_STATE_TIMEOUT: 'TIMEOUT', // eslint-disable-line deprecation/deprecation
|
|
66
|
+
|
|
67
|
+
/** @deprecated Use {@link MAXIMUM_ATTEMPTS_REACHED} instead. */
|
|
68
|
+
RETRY_STATE_MAXIMUM_ATTEMPTS_REACHED: 'MAXIMUM_ATTEMPTS_REACHED', // eslint-disable-line deprecation/deprecation
|
|
69
|
+
|
|
70
|
+
/** @deprecated Use {@link RETRY_POLICY_NOT_SET} instead. */
|
|
71
|
+
RETRY_STATE_RETRY_POLICY_NOT_SET: 'RETRY_POLICY_NOT_SET', // eslint-disable-line deprecation/deprecation
|
|
72
|
+
|
|
73
|
+
/** @deprecated Use {@link INTERNAL_SERVER_ERROR} instead. */
|
|
74
|
+
RETRY_STATE_INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR', // eslint-disable-line deprecation/deprecation
|
|
75
|
+
|
|
76
|
+
/** @deprecated Use {@link CANCEL_REQUESTED} instead. */
|
|
77
|
+
RETRY_STATE_CANCEL_REQUESTED: 'CANCEL_REQUESTED', // eslint-disable-line deprecation/deprecation
|
|
78
|
+
|
|
79
|
+
/** @deprecated Use `undefined` instead. */
|
|
80
|
+
RETRY_STATE_UNSPECIFIED: undefined, // eslint-disable-line deprecation/deprecation
|
|
81
|
+
} as const;
|
|
82
|
+
export type RetryState = (typeof RetryState)[keyof typeof RetryState];
|
|
33
83
|
|
|
34
|
-
|
|
35
|
-
|
|
84
|
+
export const [encodeRetryState, decodeRetryState] = makeProtoEnumConverters<
|
|
85
|
+
temporal.api.enums.v1.RetryState,
|
|
86
|
+
typeof temporal.api.enums.v1.RetryState,
|
|
87
|
+
keyof typeof temporal.api.enums.v1.RetryState,
|
|
88
|
+
typeof RetryState,
|
|
89
|
+
'RETRY_STATE_'
|
|
90
|
+
>(
|
|
91
|
+
{
|
|
92
|
+
[RetryState.IN_PROGRESS]: 1,
|
|
93
|
+
[RetryState.NON_RETRYABLE_FAILURE]: 2,
|
|
94
|
+
[RetryState.TIMEOUT]: 3,
|
|
95
|
+
[RetryState.MAXIMUM_ATTEMPTS_REACHED]: 4,
|
|
96
|
+
[RetryState.RETRY_POLICY_NOT_SET]: 5,
|
|
97
|
+
[RetryState.INTERNAL_SERVER_ERROR]: 6,
|
|
98
|
+
[RetryState.CANCEL_REQUESTED]: 7,
|
|
99
|
+
UNSPECIFIED: 0,
|
|
100
|
+
} as const,
|
|
101
|
+
'RETRY_STATE_'
|
|
102
|
+
);
|
|
36
103
|
|
|
37
104
|
export type WorkflowExecution = temporal.api.common.v1.IWorkflowExecution;
|
|
38
105
|
|
|
@@ -279,7 +346,7 @@ export class ChildWorkflowFailure extends TemporalFailure {
|
|
|
279
346
|
|
|
280
347
|
/**
|
|
281
348
|
* This exception is thrown in the following cases:
|
|
282
|
-
* - Workflow with the same Workflow
|
|
349
|
+
* - Workflow with the same Workflow ID is currently running and the {@link WorkflowOptions.workflowIdConflictPolicy} is `WORKFLOW_ID_CONFLICT_POLICY_FAIL`
|
|
283
350
|
* - There is a closed Workflow with the same Workflow Id and the {@link WorkflowOptions.workflowIdReusePolicy}
|
|
284
351
|
* is `WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE`
|
|
285
352
|
* - There is closed Workflow in the `Completed` state with the same Workflow Id and the {@link WorkflowOptions.workflowIdReusePolicy}
|
package/src/interfaces.ts
CHANGED
|
@@ -128,11 +128,11 @@ export interface HistoryAndWorkflowId {
|
|
|
128
128
|
* Policy defining actions taken when a workflow exits while update or signal handlers are running.
|
|
129
129
|
* The workflow exit may be due to successful return, failure, cancellation, or continue-as-new.
|
|
130
130
|
*/
|
|
131
|
-
export
|
|
131
|
+
export const HandlerUnfinishedPolicy = {
|
|
132
132
|
/**
|
|
133
133
|
* Issue a warning in addition to abandoning the handler execution. The warning will not be issued if the workflow fails.
|
|
134
134
|
*/
|
|
135
|
-
WARN_AND_ABANDON
|
|
135
|
+
WARN_AND_ABANDON: 'WARN_AND_ABANDON',
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
138
|
* Abandon the handler execution.
|
|
@@ -140,5 +140,6 @@ export enum HandlerUnfinishedPolicy {
|
|
|
140
140
|
* In the case of an update handler this means that the client will receive an error rather than
|
|
141
141
|
* the update result.
|
|
142
142
|
*/
|
|
143
|
-
ABANDON
|
|
144
|
-
}
|
|
143
|
+
ABANDON: 'ABANDON',
|
|
144
|
+
} as const;
|
|
145
|
+
export type HandlerUnfinishedPolicy = (typeof HandlerUnfinishedPolicy)[keyof typeof HandlerUnfinishedPolicy];
|
|
@@ -37,7 +37,7 @@ function requireConverter<T>(
|
|
|
37
37
|
): T {
|
|
38
38
|
let module;
|
|
39
39
|
try {
|
|
40
|
-
module = require(path); // eslint-disable-line @typescript-eslint/no-
|
|
40
|
+
module = require(path); // eslint-disable-line @typescript-eslint/no-require-imports
|
|
41
41
|
} catch (error) {
|
|
42
42
|
if (errorCode(error) === 'MODULE_NOT_FOUND') {
|
|
43
43
|
throw new ValueError(`Could not find a file at the specified ${type}Path: '${path}'.`);
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { ValueError } from '../errors';
|
|
2
|
+
import { Exact, RemovePrefix, UnionToIntersection } from '../type-helpers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create encoding and decoding functions to convert between the numeric `enum` types produced by our
|
|
6
|
+
* Protobuf compiler and "const object of strings" enum values that we expose in our public APIs.
|
|
7
|
+
*
|
|
8
|
+
* ### Usage
|
|
9
|
+
*
|
|
10
|
+
* Newly introduced enums should follow the following pattern:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* type ParentClosePolicy = (typeof ParentClosePolicy)[keyof typeof ParentClosePolicy];
|
|
14
|
+
* const ParentClosePolicy = {
|
|
15
|
+
* TERMINATE: 'TERMINATE',
|
|
16
|
+
* ABANDON: 'ABANDON',
|
|
17
|
+
* REQUEST_CANCEL: 'REQUEST_CANCEL',
|
|
18
|
+
* } as const;
|
|
19
|
+
*
|
|
20
|
+
* const [encodeParentClosePolicy, decodeParentClosePolicy] = //
|
|
21
|
+
* makeProtoEnumConverters<
|
|
22
|
+
* coresdk.child_workflow.ParentClosePolicy,
|
|
23
|
+
* typeof coresdk.child_workflow.ParentClosePolicy,
|
|
24
|
+
* keyof typeof coresdk.child_workflow.ParentClosePolicy,
|
|
25
|
+
* typeof ParentClosePolicy,
|
|
26
|
+
* 'PARENT_CLOSE_POLICY_' // This may be an empty string if the proto enum doesn't add a repeated prefix on values
|
|
27
|
+
* >(
|
|
28
|
+
* {
|
|
29
|
+
* [ParentClosePolicy.TERMINATE]: 1, // These numbers must match the ones in the proto enum
|
|
30
|
+
* [ParentClosePolicy.ABANDON]: 2,
|
|
31
|
+
* [ParentClosePolicy.REQUEST_CANCEL]: 3,
|
|
32
|
+
*
|
|
33
|
+
* UNSPECIFIED: 0,
|
|
34
|
+
* } as const,
|
|
35
|
+
* 'PARENT_CLOSE_POLICY_'
|
|
36
|
+
* );
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* `makeProtoEnumConverters` supports other usage patterns, but they are only meant for
|
|
40
|
+
* backward compatibility with former enum definitions and should not be used for new enums.
|
|
41
|
+
*
|
|
42
|
+
* ### Context
|
|
43
|
+
*
|
|
44
|
+
* Temporal's Protobuf APIs define several `enum` types; our Protobuf compiler transforms these to
|
|
45
|
+
* traditional (i.e. non-const) [TypeScript numeric `enum`s](https://www.typescriptlang.org/docs/handbook/enums.html#numeric-enums).
|
|
46
|
+
*
|
|
47
|
+
* For various reasons, this is far from ideal:
|
|
48
|
+
*
|
|
49
|
+
* - Due to the dual nature of non-const TypeScript `enum`s (they are both a type and a value),
|
|
50
|
+
* it is not possible to refer to an enum value from code without a "real" import of the enum type
|
|
51
|
+
* (i.e. can't simply do `import type ...`). In Workflow code, such an import would result in
|
|
52
|
+
* loading our entire Protobuf definitions into the workflow sandbox, adding several megabytes to
|
|
53
|
+
* the per-workflow memory footprint, which is unacceptable; to avoid that, we need to maintain
|
|
54
|
+
* a mirror copy of each enum types used by in-workflow APIs, and export these from either
|
|
55
|
+
* `@temporalio/common` or `@temporalio/workflow`.
|
|
56
|
+
* - It is not desirable for users to need an explicit dependency on `@temporalio/proto` just to
|
|
57
|
+
* get access to these enum types; we therefore made it a common practice to reexport these enums
|
|
58
|
+
* from our public facing packages. However, experience demontrated that these reexports effectively
|
|
59
|
+
* resulted in poor and inconsistent documentation coverage compared to mirrored enums types.
|
|
60
|
+
* - Our Protobuf enum types tend to follow a verbose and redundant naming convention, which feels
|
|
61
|
+
* unatural and excessive according to most TypeScript style guides; e.g. instead of
|
|
62
|
+
* `workflowIdReusePolicy: WorkflowIdReusePolicy.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE`,
|
|
63
|
+
* a TypeScript developer would generally expect to be able to write something similar to
|
|
64
|
+
* `workflowIdReusePolicy: 'REJECT_DUPLICATE'`.
|
|
65
|
+
* - Because of the way Protobuf works, many of our enum types contain an `UNSPECIFIED` value, which
|
|
66
|
+
* is used to explicitly identify a value that is unset. In TypeScript code, the `undefined` value
|
|
67
|
+
* already serves that purpose, and is definitely more idiomatic to TS developers, whereas these
|
|
68
|
+
* `UNSPECIFIED` values create noise and confusion in our APIs.
|
|
69
|
+
* - TypeScript editors generally do a very bad job at providing autocompletion that implies reaching
|
|
70
|
+
* for values of a TypeScript enum type, forcing developers to explicitly type in at least part
|
|
71
|
+
* of the name of the enum type before they can get autocompletion for its values. On the other
|
|
72
|
+
* hand, all TS editors immediately provide autocompletion for string union types.
|
|
73
|
+
* - The [TypeScript's official documentation](https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums)
|
|
74
|
+
* itself suggests that, in modern TypeScript, the use of `as const` objects may generally suffice
|
|
75
|
+
* and may be advantageous over the use of `enum` types.
|
|
76
|
+
*
|
|
77
|
+
* A const object of strings, combined with a union type of possible string values, provides a much
|
|
78
|
+
* more idiomatic syntax and a better DX for TypeScript developers. This however requires a way to
|
|
79
|
+
* convert back and forth between the `enum` values produced by the Protobuf compiler and the
|
|
80
|
+
* equivalent string values.
|
|
81
|
+
*
|
|
82
|
+
* This helper dynamically creates these conversion functions for a given Protobuf enum type,
|
|
83
|
+
* strongly building upon specific conventions that we have adopted in our Protobuf definitions.
|
|
84
|
+
*
|
|
85
|
+
* ### Validations
|
|
86
|
+
*
|
|
87
|
+
* The complex type signature of this helper is there to prevent most potential incoherencies
|
|
88
|
+
* that could result from having to manually synchronize the const object of strings enum and the
|
|
89
|
+
* conversion table with the proto enum, while not requiring a regular import on the Protobuf enum
|
|
90
|
+
* itself (so it can be used safely for enums meant to be used from workflow code).
|
|
91
|
+
*
|
|
92
|
+
* In particular, failing any of the following invariants will result in build time errors:
|
|
93
|
+
*
|
|
94
|
+
* - For every key of the form `PREFIX_KEY: number` in the proto enum, excluding the `UNSPECIFIED` key:
|
|
95
|
+
* - There MUST be a corresponding `KEY: 'KEY'` entry in the const object of strings enum;
|
|
96
|
+
* - There MAY be a corresponding `PREFIX_KEY: 'KEY'` in the const object of strings enum
|
|
97
|
+
* (this is meant to preserve backward compatibility with the former syntax; such aliases should
|
|
98
|
+
* not be added for new enums and enum entries introduced going forward);
|
|
99
|
+
* - There MUST be a corresponding `KEY: number` in the mapping table.
|
|
100
|
+
* - If the proto enum contains a `PREFIX_UNSPECIFIED` entry, then:
|
|
101
|
+
* - There MAY be a corresponding `PREFIX_UNSPECIFIED: undefined` and/or `UNSPECIFIED: undefined`
|
|
102
|
+
* entries in the const object of strings enum — this is meant to preserve backward compatibility
|
|
103
|
+
* with the former syntax; this alias should not be added for new enums introduced going forward;
|
|
104
|
+
* - There MUST be an `UNSPECIFIED: 0` in the mapping table.
|
|
105
|
+
* - The const object of strings enum MUST NOT contain any other keys than the ones mandated or
|
|
106
|
+
* optionally allowed be the preceeding rules.
|
|
107
|
+
* - The mapping table MUST NOT contain any other keys than the ones mandated above.
|
|
108
|
+
*
|
|
109
|
+
* These rules notably ensure that whenever a new value is added to an existing Proto enum, the code
|
|
110
|
+
* will fail to compile until the corresponding entry is added on the const object of strings enum
|
|
111
|
+
* and the mapping table.
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
export function makeProtoEnumConverters<
|
|
116
|
+
ProtoEnumValue extends number,
|
|
117
|
+
ProtoEnum extends { [k in ProtoEnumKey]: ProtoEnumValue },
|
|
118
|
+
ProtoEnumKey extends `${Prefix}${string}`,
|
|
119
|
+
StringEnumTypeActual extends Exact<StringEnumType, StringEnumTypeActual>,
|
|
120
|
+
Prefix extends string,
|
|
121
|
+
//
|
|
122
|
+
// Parameters after this point will be inferred; they're not meant not to be specified by developers
|
|
123
|
+
Unspecified = ProtoEnumKey extends `${Prefix}UNSPECIFIED` ? 'UNSPECIFIED' : never,
|
|
124
|
+
ShortStringEnumKey extends RemovePrefix<Prefix, ProtoEnumKey> = Exclude<
|
|
125
|
+
RemovePrefix<Prefix, ProtoEnumKey>,
|
|
126
|
+
Unspecified
|
|
127
|
+
>,
|
|
128
|
+
StringEnumType extends ProtoConstObjectOfStringsEnum<
|
|
129
|
+
ShortStringEnumKey,
|
|
130
|
+
Prefix,
|
|
131
|
+
Unspecified
|
|
132
|
+
> = ProtoConstObjectOfStringsEnum<ShortStringEnumKey, Prefix, Unspecified>,
|
|
133
|
+
MapTable extends ProtoEnumToConstObjectOfStringMapTable<
|
|
134
|
+
StringEnumType,
|
|
135
|
+
ProtoEnumValue,
|
|
136
|
+
ProtoEnum,
|
|
137
|
+
ProtoEnumKey,
|
|
138
|
+
Prefix,
|
|
139
|
+
Unspecified,
|
|
140
|
+
ShortStringEnumKey
|
|
141
|
+
> = ProtoEnumToConstObjectOfStringMapTable<
|
|
142
|
+
StringEnumType,
|
|
143
|
+
ProtoEnumValue,
|
|
144
|
+
ProtoEnum,
|
|
145
|
+
ProtoEnumKey,
|
|
146
|
+
Prefix,
|
|
147
|
+
Unspecified,
|
|
148
|
+
ShortStringEnumKey
|
|
149
|
+
>,
|
|
150
|
+
>(
|
|
151
|
+
mapTable: MapTable,
|
|
152
|
+
prefix: Prefix
|
|
153
|
+
): [
|
|
154
|
+
(
|
|
155
|
+
input: ShortStringEnumKey | `${Prefix}${ShortStringEnumKey}` | ProtoEnumValue | null | undefined
|
|
156
|
+
) => ProtoEnumValue | undefined, //
|
|
157
|
+
(input: ProtoEnumValue | null | undefined) => ShortStringEnumKey | undefined, //
|
|
158
|
+
] {
|
|
159
|
+
const reverseTable: Record<ProtoEnumValue, ShortStringEnumKey> = Object.fromEntries(
|
|
160
|
+
Object.entries(mapTable).map(([k, v]) => [v, k])
|
|
161
|
+
);
|
|
162
|
+
const hasUnspecified = (mapTable as any)['UNSPECIFIED'] === 0 || (mapTable as any)[`${prefix}UNSPECIFIED`] === 0;
|
|
163
|
+
|
|
164
|
+
function isShortStringEnumKeys(x: unknown): x is ShortStringEnumKey {
|
|
165
|
+
return typeof x === 'string' && x in mapTable;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isNumericEnumValue(x: unknown): x is ProtoEnum[keyof ProtoEnum] {
|
|
169
|
+
return typeof x === 'number' && x in reverseTable;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function encode(
|
|
173
|
+
input: ShortStringEnumKey | `${Prefix}${ShortStringEnumKey}` | ProtoEnumValue | null | undefined
|
|
174
|
+
): ProtoEnumValue | undefined {
|
|
175
|
+
if (input == null) {
|
|
176
|
+
return undefined;
|
|
177
|
+
} else if (typeof input === 'string') {
|
|
178
|
+
let shorten: string = input;
|
|
179
|
+
if (shorten.startsWith(prefix)) {
|
|
180
|
+
shorten = shorten.slice(prefix.length);
|
|
181
|
+
}
|
|
182
|
+
if (isShortStringEnumKeys(shorten)) {
|
|
183
|
+
return mapTable[shorten];
|
|
184
|
+
}
|
|
185
|
+
throw new ValueError(`Invalid enum value: '${input}'`);
|
|
186
|
+
} else if (typeof input === 'number') {
|
|
187
|
+
return input;
|
|
188
|
+
} else {
|
|
189
|
+
throw new ValueError(`Invalid enum value: '${input}' of type ${typeof input}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function decode(input: ProtoEnumValue | null | undefined): ShortStringEnumKey | undefined {
|
|
194
|
+
if (input == null) {
|
|
195
|
+
return undefined;
|
|
196
|
+
} else if (typeof input === 'number') {
|
|
197
|
+
if (hasUnspecified && input === 0) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (isNumericEnumValue(input)) {
|
|
202
|
+
return reverseTable[input];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// We got a proto enum value that we don't yet know about (i.e. it didn't exist when this code
|
|
206
|
+
// was compiled). This is certainly a possibility, but given how our APIs evolve, this is is
|
|
207
|
+
// unlikely to be a terribly bad thing by itself (we avoid adding new enum values in places
|
|
208
|
+
// that would break backward compatibility with existing deployed code). Therefore, throwing
|
|
209
|
+
// on "unexpected" values is likely to end up causing more problems than it might avoid,
|
|
210
|
+
// especially given that the decoded value may actually never get read anwyay.
|
|
211
|
+
//
|
|
212
|
+
// Therefore, we instead cheat on type constraints and return a string of the form "unknown_23".
|
|
213
|
+
// That somewhat mirrors the behavior we'd get with the pure numerical approach.
|
|
214
|
+
return `unknown_${input}` as ShortStringEnumKey;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
throw new ValueError(`Invalid proto enum value: '${input}' of type ${typeof input}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return [encode, decode] as const;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Given the exploded parameters of a proto enum (i.e. short keys, prefix, and short key of the
|
|
225
|
+
* unspecified value), make a type that _exactly_ corresponds to the const object of strings enum,
|
|
226
|
+
* e.g. the type that the developer is expected to write.
|
|
227
|
+
*
|
|
228
|
+
* For example, for coresdk.child_workflow.ParentClosePolicy, this evaluates to:
|
|
229
|
+
*
|
|
230
|
+
* {
|
|
231
|
+
* TERMINATE: "TERMINATE";
|
|
232
|
+
* ABANDON: "ABANDON";
|
|
233
|
+
* REQUEST_CANCEL: "REQUEST_CANCEL";
|
|
234
|
+
*
|
|
235
|
+
* PARENT_CLOSE_POLICY_TERMINATE?: "TERMINATE";
|
|
236
|
+
* PARENT_CLOSE_POLICY_ABANDON?: "ABANDON";
|
|
237
|
+
* PARENT_CLOSE_POLICY_REQUEST_CANCEL?: "REQUEST_CANCEL";
|
|
238
|
+
*
|
|
239
|
+
* PARENT_CLOSE_POLICY_UNSPECIFIED?: undefined;
|
|
240
|
+
* }
|
|
241
|
+
*/
|
|
242
|
+
type ProtoConstObjectOfStringsEnum<
|
|
243
|
+
ShortStringEnumKey extends string,
|
|
244
|
+
Prefix extends string,
|
|
245
|
+
Unspecified, // e.g. 'UNSPECIFIED'
|
|
246
|
+
> = UnionToIntersection<
|
|
247
|
+
| {
|
|
248
|
+
// e.g.: "TERMINATE": "TERMINATE"
|
|
249
|
+
readonly [k in ShortStringEnumKey]: k;
|
|
250
|
+
}
|
|
251
|
+
| {
|
|
252
|
+
[k in ShortStringEnumKey]: Prefix extends ''
|
|
253
|
+
? object
|
|
254
|
+
: {
|
|
255
|
+
// e.g.: "PARENT_CLOSE_POLICY_TERMINATE"?: "TERMINATE"
|
|
256
|
+
readonly [kk in `${Prefix}${k}`]?: k;
|
|
257
|
+
};
|
|
258
|
+
}[ShortStringEnumKey]
|
|
259
|
+
| (Unspecified extends string
|
|
260
|
+
? {
|
|
261
|
+
// e.g.: "PARENT_CLOSE_POLICY_UNSPECIFIED"?: undefined
|
|
262
|
+
[k in `${Prefix}${Unspecified}`]?: undefined;
|
|
263
|
+
}
|
|
264
|
+
: object)
|
|
265
|
+
| (Unspecified extends string
|
|
266
|
+
? {
|
|
267
|
+
// e.g.: "UNSPECIFIED"?: undefined
|
|
268
|
+
[k in `${Unspecified}`]?: undefined;
|
|
269
|
+
}
|
|
270
|
+
: object)
|
|
271
|
+
>;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Given the exploded parameters of a proto enum (i.e. short keys, prefix, and short key of the
|
|
275
|
+
* unspecified value), make a type that _exactly_ corresponds to the mapping table that the user is
|
|
276
|
+
* expected to provide.
|
|
277
|
+
*
|
|
278
|
+
* For example, for coresdk.child_workflow.ParentClosePolicy, this evaluates to:
|
|
279
|
+
*
|
|
280
|
+
* {
|
|
281
|
+
* UNSPECIFIED: 0,
|
|
282
|
+
* TERMINATE: 1,
|
|
283
|
+
* ABANDON: 2,
|
|
284
|
+
* REQUEST_CANCEL: 3,
|
|
285
|
+
* }
|
|
286
|
+
*/
|
|
287
|
+
type ProtoEnumToConstObjectOfStringMapTable<
|
|
288
|
+
_StringEnum extends ProtoConstObjectOfStringsEnum<ShortStringEnumKey, Prefix, Unspecified>,
|
|
289
|
+
ProtoEnumValue extends number,
|
|
290
|
+
ProtoEnum extends { [k in ProtoEnumKey]: ProtoEnumValue },
|
|
291
|
+
ProtoEnumKey extends `${Prefix}${string}`,
|
|
292
|
+
Prefix extends string,
|
|
293
|
+
Unspecified,
|
|
294
|
+
ShortStringEnumKey extends RemovePrefix<Prefix, ProtoEnumKey>,
|
|
295
|
+
> = UnionToIntersection<
|
|
296
|
+
{
|
|
297
|
+
[k in ProtoEnumKey]: {
|
|
298
|
+
[kk in RemovePrefix<Prefix, k>]: ProtoEnum[k] extends number ? ProtoEnum[k] : never;
|
|
299
|
+
};
|
|
300
|
+
}[ProtoEnumKey]
|
|
301
|
+
>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './enums-helpers';
|
package/src/type-helpers.ts
CHANGED
|
@@ -17,6 +17,84 @@ export function checkExtends<_Orig, _Copy extends _Orig>(): void {
|
|
|
17
17
|
|
|
18
18
|
export type Replace<Base, New> = Omit<Base, keyof New> & New;
|
|
19
19
|
|
|
20
|
+
// From https://github.com/sindresorhus/type-fest/blob/main/source/union-to-intersection.d.ts
|
|
21
|
+
// MIT or CC0-1.0 — It is meant to be copied into your codebase rather than being used as a dependency.
|
|
22
|
+
export type UnionToIntersection<Union> =
|
|
23
|
+
// `extends unknown` is always going to be the case and is used to convert the `Union` into a
|
|
24
|
+
// [distributive conditional type](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
|
|
25
|
+
(
|
|
26
|
+
Union extends unknown
|
|
27
|
+
? // The union type is used as the only argument to a function since the union
|
|
28
|
+
// of function arguments is an intersection.
|
|
29
|
+
(distributedUnion: Union) => void
|
|
30
|
+
: // This won't happen.
|
|
31
|
+
never
|
|
32
|
+
) extends // Infer the `Intersection` type since TypeScript represents the positional
|
|
33
|
+
// arguments of unions of functions as an intersection of the union.
|
|
34
|
+
(mergedIntersection: infer Intersection) => void
|
|
35
|
+
? // The `& Union` is to allow indexing by the resulting type
|
|
36
|
+
Intersection & Union
|
|
37
|
+
: never;
|
|
38
|
+
|
|
39
|
+
type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <G>() => G extends B ? 1 : 2 ? true : false;
|
|
40
|
+
|
|
41
|
+
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
|
|
42
|
+
|
|
43
|
+
type IsNull<T> = [T] extends [null] ? true : false;
|
|
44
|
+
|
|
45
|
+
type IsUnknown<T> = unknown extends T // `T` can be `unknown` or `any`
|
|
46
|
+
? IsNull<T> extends false // `any` can be `null`, but `unknown` can't be
|
|
47
|
+
? true
|
|
48
|
+
: false
|
|
49
|
+
: false;
|
|
50
|
+
|
|
51
|
+
type ObjectValue<T, K> = K extends keyof T
|
|
52
|
+
? T[K]
|
|
53
|
+
: ToString<K> extends keyof T
|
|
54
|
+
? T[ToString<K>]
|
|
55
|
+
: K extends `${infer NumberK extends number}`
|
|
56
|
+
? NumberK extends keyof T
|
|
57
|
+
? T[NumberK]
|
|
58
|
+
: never
|
|
59
|
+
: never;
|
|
60
|
+
|
|
61
|
+
type ToString<T> = T extends string | number ? `${T}` : never;
|
|
62
|
+
|
|
63
|
+
type KeysOfUnion<ObjectType> = ObjectType extends unknown ? keyof ObjectType : never;
|
|
64
|
+
|
|
65
|
+
type ArrayElement<T> = T extends readonly unknown[] ? T[0] : never;
|
|
66
|
+
|
|
67
|
+
type ExactObject<ParameterType, InputType> = {
|
|
68
|
+
[Key in keyof ParameterType]: Exact<ParameterType[Key], ObjectValue<InputType, Key>>;
|
|
69
|
+
} & Record<Exclude<keyof InputType, KeysOfUnion<ParameterType>>, never>;
|
|
70
|
+
|
|
71
|
+
export type Exact<ParameterType, InputType> =
|
|
72
|
+
// Before distributing, check if the two types are equal and if so, return the parameter type immediately
|
|
73
|
+
IsEqual<ParameterType, InputType> extends true
|
|
74
|
+
? ParameterType
|
|
75
|
+
: // If the parameter is a primitive, return it as is immediately to avoid it being converted to a complex type
|
|
76
|
+
ParameterType extends Primitive
|
|
77
|
+
? ParameterType
|
|
78
|
+
: // If the parameter is an unknown, return it as is immediately to avoid it being converted to a complex type
|
|
79
|
+
IsUnknown<ParameterType> extends true
|
|
80
|
+
? unknown
|
|
81
|
+
: // If the parameter is a Function, return it as is because this type is not capable of handling function, leave it to TypeScript
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
83
|
+
ParameterType extends Function
|
|
84
|
+
? ParameterType
|
|
85
|
+
: // Convert union of array to array of union: A[] & B[] => (A & B)[]
|
|
86
|
+
ParameterType extends unknown[]
|
|
87
|
+
? Array<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
|
88
|
+
: // In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray.
|
|
89
|
+
ParameterType extends readonly unknown[]
|
|
90
|
+
? ReadonlyArray<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
|
91
|
+
: ExactObject<ParameterType, InputType>;
|
|
92
|
+
// End of borrow from https://github.com/sindresorhus/type-fest/blob/main/source/union-to-intersection.d.ts
|
|
93
|
+
|
|
94
|
+
export type RemovePrefix<Prefix extends string, Keys extends string> = {
|
|
95
|
+
[k in Keys]: k extends `${Prefix}${infer Suffix}` ? Suffix : never;
|
|
96
|
+
}[Keys];
|
|
97
|
+
|
|
20
98
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
21
99
|
return typeof value === 'object' && value !== null;
|
|
22
100
|
}
|
|
@@ -153,7 +231,7 @@ export function deepFreeze<T>(object: T): T {
|
|
|
153
231
|
if (value && typeof value === 'object') {
|
|
154
232
|
try {
|
|
155
233
|
deepFreeze(value);
|
|
156
|
-
} catch (
|
|
234
|
+
} catch (_err) {
|
|
157
235
|
// This is okay, there are some typed arrays that cannot be frozen (encodingKeys)
|
|
158
236
|
}
|
|
159
237
|
} else if (typeof value === 'function') {
|