@temporalio/common 1.11.8 → 1.12.0-rc.0

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.
Files changed (87) hide show
  1. package/lib/activity-options.d.ts +6 -1
  2. package/lib/activity-options.js.map +1 -1
  3. package/lib/converter/failure-converter.d.ts +3 -3
  4. package/lib/converter/failure-converter.js +3 -1
  5. package/lib/converter/failure-converter.js.map +1 -1
  6. package/lib/converter/payload-converter.d.ts +2 -15
  7. package/lib/converter/payload-converter.js +6 -69
  8. package/lib/converter/payload-converter.js.map +1 -1
  9. package/lib/converter/payload-search-attributes.d.ts +26 -0
  10. package/lib/converter/payload-search-attributes.js +171 -0
  11. package/lib/converter/payload-search-attributes.js.map +1 -0
  12. package/lib/converter/protobuf-payload-converters.js +3 -2
  13. package/lib/converter/protobuf-payload-converters.js.map +1 -1
  14. package/lib/errors.d.ts +2 -1
  15. package/lib/errors.js +6 -1
  16. package/lib/errors.js.map +1 -1
  17. package/lib/failure.d.ts +20 -1
  18. package/lib/failure.js +47 -5
  19. package/lib/failure.js.map +1 -1
  20. package/lib/index.d.ts +7 -0
  21. package/lib/index.js +9 -0
  22. package/lib/index.js.map +1 -1
  23. package/lib/interfaces.d.ts +0 -7
  24. package/lib/interfaces.js.map +1 -1
  25. package/lib/internal-non-workflow/codec-helpers.d.ts +2 -2
  26. package/lib/internal-non-workflow/index.d.ts +0 -1
  27. package/lib/internal-non-workflow/index.js +0 -1
  28. package/lib/internal-non-workflow/index.js.map +1 -1
  29. package/lib/internal-workflow/index.d.ts +1 -0
  30. package/lib/internal-workflow/index.js +1 -0
  31. package/lib/internal-workflow/index.js.map +1 -1
  32. package/lib/internal-workflow/objects-helpers.d.ts +15 -0
  33. package/lib/internal-workflow/objects-helpers.js +29 -0
  34. package/lib/internal-workflow/objects-helpers.js.map +1 -0
  35. package/lib/logger.d.ts +32 -0
  36. package/lib/logger.js +91 -1
  37. package/lib/logger.js.map +1 -1
  38. package/lib/metrics.d.ts +181 -0
  39. package/lib/metrics.js +240 -0
  40. package/lib/metrics.js.map +1 -0
  41. package/lib/priority.d.ts +36 -0
  42. package/lib/priority.js +27 -0
  43. package/lib/priority.js.map +1 -0
  44. package/lib/search-attributes.d.ts +77 -0
  45. package/lib/search-attributes.js +233 -0
  46. package/lib/search-attributes.js.map +1 -0
  47. package/lib/type-helpers.d.ts +5 -1
  48. package/lib/type-helpers.js +0 -22
  49. package/lib/type-helpers.js.map +1 -1
  50. package/lib/versioning-intent-enum.d.ts +1 -1
  51. package/lib/versioning-intent-enum.js +1 -1
  52. package/lib/versioning-intent.d.ts +1 -1
  53. package/lib/worker-deployments.d.ts +47 -0
  54. package/lib/worker-deployments.js +32 -0
  55. package/lib/worker-deployments.js.map +1 -0
  56. package/lib/workflow-definition-options.d.ts +18 -0
  57. package/lib/workflow-definition-options.js +3 -0
  58. package/lib/workflow-definition-options.js.map +1 -0
  59. package/lib/workflow-options.d.ts +22 -2
  60. package/lib/workflow-options.js.map +1 -1
  61. package/package.json +6 -3
  62. package/src/activity-options.ts +7 -1
  63. package/src/converter/failure-converter.ts +9 -4
  64. package/src/converter/payload-converter.ts +7 -71
  65. package/src/converter/payload-search-attributes.ts +220 -0
  66. package/src/errors.ts +2 -1
  67. package/src/failure.ts +38 -3
  68. package/src/index.ts +13 -0
  69. package/src/interfaces.ts +0 -8
  70. package/src/internal-non-workflow/codec-helpers.ts +2 -2
  71. package/src/internal-non-workflow/index.ts +0 -1
  72. package/src/internal-workflow/index.ts +1 -0
  73. package/src/internal-workflow/objects-helpers.ts +37 -0
  74. package/src/logger.ts +108 -0
  75. package/src/metrics.ts +443 -0
  76. package/src/priority.ts +54 -0
  77. package/src/search-attributes.ts +292 -0
  78. package/src/type-helpers.ts +11 -23
  79. package/src/versioning-intent-enum.ts +1 -1
  80. package/src/versioning-intent.ts +1 -1
  81. package/src/worker-deployments.ts +70 -0
  82. package/src/workflow-definition-options.ts +20 -0
  83. package/src/workflow-options.ts +27 -3
  84. package/lib/internal-non-workflow/utils.d.ts +0 -4
  85. package/lib/internal-non-workflow/utils.js +0 -10
  86. package/lib/internal-non-workflow/utils.js.map +0 -1
  87. package/src/internal-non-workflow/utils.ts +0 -6
@@ -49,7 +49,10 @@ export function toPayloads(converter: PayloadConverter, ...values: unknown[]): P
49
49
  *
50
50
  * @throws {@link ValueError} if conversion of any value in the map fails
51
51
  */
52
- export function mapToPayloads<K extends string>(converter: PayloadConverter, map: Record<K, any>): Record<K, Payload> {
52
+ export function mapToPayloads<K extends string, T = any>(
53
+ converter: PayloadConverter,
54
+ map: Record<K, T>
55
+ ): Record<K, Payload> {
53
56
  return Object.fromEntries(
54
57
  Object.entries(map).map(([k, v]): [K, Payload] => [k as K, converter.toPayload(v)])
55
58
  ) as Record<K, Payload>;
@@ -84,17 +87,17 @@ export function arrayFromPayloads(converter: PayloadConverter, payloads?: Payloa
84
87
  return payloads.map((payload: Payload) => converter.fromPayload(payload));
85
88
  }
86
89
 
87
- export function mapFromPayloads<K extends string>(
90
+ export function mapFromPayloads<K extends string, T = unknown>(
88
91
  converter: PayloadConverter,
89
92
  map?: Record<K, Payload> | null | undefined
90
- ): Record<K, unknown> | undefined {
93
+ ): Record<K, T> | undefined {
91
94
  if (map == null) return undefined;
92
95
  return Object.fromEntries(
93
96
  Object.entries(map).map(([k, payload]): [K, unknown] => {
94
97
  const value = converter.fromPayload(payload as Payload);
95
98
  return [k as K, value];
96
99
  })
97
- ) as Record<K, unknown>;
100
+ ) as Record<K, T>;
98
101
  }
99
102
 
100
103
  export interface PayloadConverterWithEncoding {
@@ -252,73 +255,6 @@ export class JsonPayloadConverter implements PayloadConverterWithEncoding {
252
255
  }
253
256
  }
254
257
 
255
- /**
256
- * Converts Search Attribute values using JsonPayloadConverter
257
- */
258
- export class SearchAttributePayloadConverter implements PayloadConverter {
259
- jsonConverter = new JsonPayloadConverter();
260
- validNonDateTypes = ['string', 'number', 'boolean'];
261
-
262
- public toPayload(values: unknown): Payload {
263
- if (!Array.isArray(values)) {
264
- throw new ValueError(`SearchAttribute value must be an array`);
265
- }
266
-
267
- if (values.length > 0) {
268
- const firstValue = values[0];
269
- const firstType = typeof firstValue;
270
- if (firstType === 'object') {
271
- for (const [idx, value] of values.entries()) {
272
- if (!(value instanceof Date)) {
273
- throw new ValueError(
274
- `SearchAttribute values must arrays of strings, numbers, booleans, or Dates. The value ${value} at index ${idx} is of type ${typeof value}`
275
- );
276
- }
277
- }
278
- } else {
279
- if (!this.validNonDateTypes.includes(firstType)) {
280
- throw new ValueError(`SearchAttribute array values must be: string | number | boolean | Date`);
281
- }
282
-
283
- for (const [idx, value] of values.entries()) {
284
- if (typeof value !== firstType) {
285
- throw new ValueError(
286
- `All SearchAttribute array values must be of the same type. The first value ${firstValue} of type ${firstType} doesn't match value ${value} of type ${typeof value} at index ${idx}`
287
- );
288
- }
289
- }
290
- }
291
- }
292
-
293
- // JSON.stringify takes care of converting Dates to ISO strings
294
- const ret = this.jsonConverter.toPayload(values);
295
- if (ret === undefined) {
296
- throw new ValueError('Could not convert search attributes to payloads');
297
- }
298
- return ret;
299
- }
300
-
301
- /**
302
- * Datetime Search Attribute values are converted to `Date`s
303
- */
304
- public fromPayload<T>(payload: Payload): T {
305
- if (payload.metadata === undefined || payload.metadata === null) {
306
- throw new ValueError('Missing payload metadata');
307
- }
308
-
309
- const value = this.jsonConverter.fromPayload(payload);
310
- let arrayWrappedValue = Array.isArray(value) ? value : [value];
311
-
312
- const searchAttributeType = decode(payload.metadata.type);
313
- if (searchAttributeType === 'Datetime') {
314
- arrayWrappedValue = arrayWrappedValue.map((dateString) => new Date(dateString));
315
- }
316
- return arrayWrappedValue as unknown as T;
317
- }
318
- }
319
-
320
- export const searchAttributePayloadConverter = new SearchAttributePayloadConverter();
321
-
322
258
  export class DefaultPayloadConverter extends CompositePayloadConverter {
323
259
  // Match the order used in other SDKs, but exclude Protobuf converters so that the code, including
324
260
  // `proto3-json-serializer`, doesn't take space in Workflow bundles that don't use Protobufs. To use Protobufs, use
@@ -0,0 +1,220 @@
1
+ import { decode, encode } from '../encoding';
2
+ import { ValueError } from '../errors';
3
+ import { Payload } from '../interfaces';
4
+ import {
5
+ TypedSearchAttributes,
6
+ SearchAttributeType,
7
+ SearchAttributes,
8
+ isValidValueForType,
9
+ TypedSearchAttributeValue,
10
+ SearchAttributePair,
11
+ SearchAttributeUpdatePair,
12
+ TypedSearchAttributeUpdateValue,
13
+ } from '../search-attributes';
14
+ import { PayloadConverter, JsonPayloadConverter, mapFromPayloads, mapToPayloads } from './payload-converter';
15
+
16
+ /**
17
+ * Converts Search Attribute values using JsonPayloadConverter
18
+ */
19
+ export class SearchAttributePayloadConverter implements PayloadConverter {
20
+ jsonConverter = new JsonPayloadConverter();
21
+ validNonDateTypes = ['string', 'number', 'boolean'];
22
+
23
+ public toPayload(values: unknown): Payload {
24
+ if (!Array.isArray(values)) {
25
+ throw new ValueError(`SearchAttribute value must be an array`);
26
+ }
27
+
28
+ if (values.length > 0) {
29
+ const firstValue = values[0];
30
+ const firstType = typeof firstValue;
31
+ if (firstType === 'object') {
32
+ for (const [idx, value] of values.entries()) {
33
+ if (!(value instanceof Date)) {
34
+ throw new ValueError(
35
+ `SearchAttribute values must arrays of strings, numbers, booleans, or Dates. The value ${value} at index ${idx} is of type ${typeof value}`
36
+ );
37
+ }
38
+ }
39
+ } else {
40
+ if (!this.validNonDateTypes.includes(firstType)) {
41
+ throw new ValueError(`SearchAttribute array values must be: string | number | boolean | Date`);
42
+ }
43
+
44
+ for (const [idx, value] of values.entries()) {
45
+ if (typeof value !== firstType) {
46
+ throw new ValueError(
47
+ `All SearchAttribute array values must be of the same type. The first value ${firstValue} of type ${firstType} doesn't match value ${value} of type ${typeof value} at index ${idx}`
48
+ );
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ // JSON.stringify takes care of converting Dates to ISO strings
55
+ const ret = this.jsonConverter.toPayload(values);
56
+ if (ret === undefined) {
57
+ throw new ValueError('Could not convert search attributes to payloads');
58
+ }
59
+ return ret;
60
+ }
61
+
62
+ /**
63
+ * Datetime Search Attribute values are converted to `Date`s
64
+ */
65
+ public fromPayload<T>(payload: Payload): T {
66
+ if (payload.metadata == null) {
67
+ throw new ValueError('Missing payload metadata');
68
+ }
69
+
70
+ const value = this.jsonConverter.fromPayload(payload);
71
+ let arrayWrappedValue = Array.isArray(value) ? value : [value];
72
+ const searchAttributeType = decode(payload.metadata.type);
73
+ if (searchAttributeType === 'Datetime') {
74
+ arrayWrappedValue = arrayWrappedValue.map((dateString) => new Date(dateString));
75
+ }
76
+ return arrayWrappedValue as unknown as T;
77
+ }
78
+ }
79
+
80
+ export const searchAttributePayloadConverter = new SearchAttributePayloadConverter();
81
+
82
+ export class TypedSearchAttributePayloadConverter implements PayloadConverter {
83
+ jsonConverter = new JsonPayloadConverter();
84
+
85
+ public toPayload<T>(attr: T): Payload {
86
+ if (!(attr instanceof TypedSearchAttributeValue || attr instanceof TypedSearchAttributeUpdateValue)) {
87
+ throw new ValueError(
88
+ `Expect input to be instance of TypedSearchAttributeValue or TypedSearchAttributeUpdateValue, got: ${JSON.stringify(
89
+ attr
90
+ )}`
91
+ );
92
+ }
93
+
94
+ // We check for deletion as well as regular typed search attributes.
95
+ if (attr.value !== null && !isValidValueForType(attr.type, attr.value)) {
96
+ throw new ValueError(`Invalid search attribute value ${attr.value} for given type ${attr.type}`);
97
+ }
98
+
99
+ // For server search attributes to work properly, we cannot set the metadata
100
+ // type when we set null
101
+ if (attr.value === null) {
102
+ const payload = this.jsonConverter.toPayload(attr.value);
103
+ if (payload === undefined) {
104
+ throw new ValueError('Could not convert typed search attribute to payload');
105
+ }
106
+ return payload;
107
+ }
108
+
109
+ // JSON.stringify takes care of converting Dates to ISO strings
110
+ const payload = this.jsonConverter.toPayload(attr.value);
111
+ if (payload === undefined) {
112
+ throw new ValueError('Could not convert typed search attribute to payload');
113
+ }
114
+
115
+ // Note: this shouldn't be the case but the compiler complains without this check.
116
+ if (payload.metadata == null) {
117
+ throw new ValueError('Missing payload metadata');
118
+ }
119
+ // Add encoded type of search attribute to metatdata
120
+ payload.metadata['type'] = encode(TypedSearchAttributes.toMetadataType(attr.type));
121
+ return payload;
122
+ }
123
+
124
+ // Note: type casting undefined values is not clear to caller.
125
+ // We can't change the typing of the method to return undefined, it's not allowed by the interface.
126
+ public fromPayload<T>(payload: Payload): T {
127
+ if (payload.metadata == null) {
128
+ throw new ValueError('Missing payload metadata');
129
+ }
130
+
131
+ // If no 'type' metadata field or no given value, we skip.
132
+ if (payload.metadata.type == null) {
133
+ return undefined as T;
134
+ }
135
+ const type = TypedSearchAttributes.toSearchAttributeType(decode(payload.metadata.type));
136
+ // Unrecognized metadata type (sanity check).
137
+ if (type === undefined) {
138
+ return undefined as T;
139
+ }
140
+
141
+ let value = this.jsonConverter.fromPayload(payload);
142
+
143
+ // Handle legacy values without KEYWORD_LIST type.
144
+ if (type !== SearchAttributeType.KEYWORD_LIST && Array.isArray(value)) {
145
+ // Cannot have an array with multiple values for non-KEYWORD_LIST type.
146
+ if (value.length > 1) {
147
+ return undefined as T;
148
+ }
149
+ // Unpack single value array.
150
+ value = value[0];
151
+ }
152
+ if (type === SearchAttributeType.DATETIME && value) {
153
+ value = new Date(value as string);
154
+ }
155
+ // Check if the value is a valid for the given type. If not, skip.
156
+ if (!isValidValueForType(type, value)) {
157
+ return undefined as T;
158
+ }
159
+ return new TypedSearchAttributeValue(type, value) as T;
160
+ }
161
+ }
162
+
163
+ export const typedSearchAttributePayloadConverter = new TypedSearchAttributePayloadConverter();
164
+
165
+ // If both params are provided, conflicting keys will be overwritten by typedSearchAttributes.
166
+ export function encodeUnifiedSearchAttributes(
167
+ searchAttributes?: SearchAttributes, // eslint-disable-line deprecation/deprecation
168
+ typedSearchAttributes?: TypedSearchAttributes | SearchAttributeUpdatePair[]
169
+ ): Record<string, Payload> {
170
+ return {
171
+ ...(searchAttributes ? mapToPayloads(searchAttributePayloadConverter, searchAttributes) : {}),
172
+ ...(typedSearchAttributes
173
+ ? mapToPayloads<string, TypedSearchAttributeUpdateValue<SearchAttributeType>>(
174
+ typedSearchAttributePayloadConverter,
175
+ Object.fromEntries(
176
+ (Array.isArray(typedSearchAttributes) ? typedSearchAttributes : typedSearchAttributes.getAll()).map(
177
+ (pair) => {
178
+ return [pair.key.name, new TypedSearchAttributeUpdateValue(pair.key.type, pair.value)];
179
+ }
180
+ )
181
+ )
182
+ )
183
+ : {}),
184
+ };
185
+ }
186
+
187
+ // eslint-disable-next-line deprecation/deprecation
188
+ export function decodeSearchAttributes(indexedFields: Record<string, Payload> | undefined | null): SearchAttributes {
189
+ if (!indexedFields) return {};
190
+ return Object.fromEntries(
191
+ // eslint-disable-next-line deprecation/deprecation
192
+ Object.entries(mapFromPayloads(searchAttributePayloadConverter, indexedFields) as SearchAttributes).filter(
193
+ ([_, v]) => v && v.length > 0
194
+ ) // Filter out empty arrays returned by pre 1.18 servers
195
+ );
196
+ }
197
+
198
+ export function decodeTypedSearchAttributes(
199
+ indexedFields: Record<string, Payload> | undefined | null
200
+ ): TypedSearchAttributes {
201
+ return new TypedSearchAttributes(
202
+ Object.entries(
203
+ mapFromPayloads<string, TypedSearchAttributeValue<SearchAttributeType> | undefined>(
204
+ typedSearchAttributePayloadConverter,
205
+ indexedFields
206
+ ) ?? {}
207
+ ).reduce<SearchAttributePair[]>((acc, [k, attr]) => {
208
+ // Filter out undefined values from converter.
209
+ if (!attr) {
210
+ return acc;
211
+ }
212
+ const key = { name: k, type: attr.type };
213
+ // Ensure is valid pair.
214
+ if (isValidValueForType(key.type, attr.value)) {
215
+ acc.push({ key, value: attr.value } as SearchAttributePair);
216
+ }
217
+ return acc;
218
+ }, [])
219
+ );
220
+ }
package/src/errors.ts CHANGED
@@ -20,7 +20,8 @@ export class ValueError extends Error {
20
20
  export class PayloadConverterError extends ValueError {}
21
21
 
22
22
  /**
23
- * Used in different parts of the SDK to note that something unexpected has happened.
23
+ * Signals that a requested operation can't be completed because it is illegal given the
24
+ * current state of the object; e.g. trying to use a resource after it has been closed.
24
25
  */
25
26
  @SymbolBasedInstanceOfError('IllegalStateError')
26
27
  export class IllegalStateError extends Error {}
package/src/failure.ts CHANGED
@@ -101,6 +101,34 @@ export const [encodeRetryState, decodeRetryState] = makeProtoEnumConverters<
101
101
  'RETRY_STATE_'
102
102
  );
103
103
 
104
+ /**
105
+ * A category to describe the severity and change the observability behavior of an application failure.
106
+ *
107
+ * Currently, observability behaviour changes are limited to:
108
+ * - activities that fail due to a BENIGN application failure emit DEBUG level logs and do not record metrics
109
+ *
110
+ * @experimental Category is a new feature and may be subject to change.
111
+ */
112
+ export const ApplicationFailureCategory = {
113
+ BENIGN: 'BENIGN',
114
+ } as const;
115
+
116
+ export type ApplicationFailureCategory = (typeof ApplicationFailureCategory)[keyof typeof ApplicationFailureCategory];
117
+
118
+ export const [encodeApplicationFailureCategory, decodeApplicationFailureCategory] = makeProtoEnumConverters<
119
+ temporal.api.enums.v1.ApplicationErrorCategory,
120
+ typeof temporal.api.enums.v1.ApplicationErrorCategory,
121
+ keyof typeof temporal.api.enums.v1.ApplicationErrorCategory,
122
+ typeof ApplicationFailureCategory,
123
+ 'APPLICATION_ERROR_CATEGORY_'
124
+ >(
125
+ {
126
+ [ApplicationFailureCategory.BENIGN]: 1,
127
+ UNSPECIFIED: 0,
128
+ } as const,
129
+ 'APPLICATION_ERROR_CATEGORY_'
130
+ );
131
+
104
132
  export type WorkflowExecution = temporal.api.common.v1.IWorkflowExecution;
105
133
 
106
134
  /**
@@ -172,7 +200,8 @@ export class ApplicationFailure extends TemporalFailure {
172
200
  public readonly nonRetryable?: boolean | undefined | null,
173
201
  public readonly details?: unknown[] | undefined | null,
174
202
  cause?: Error,
175
- public readonly nextRetryDelay?: Duration | undefined | null
203
+ public readonly nextRetryDelay?: Duration | undefined | null,
204
+ public readonly category?: ApplicationFailureCategory | undefined | null
176
205
  ) {
177
206
  super(message, cause);
178
207
  }
@@ -195,8 +224,8 @@ export class ApplicationFailure extends TemporalFailure {
195
224
  * By default, will be retryable (unless its `type` is included in {@link RetryPolicy.nonRetryableErrorTypes}).
196
225
  */
197
226
  public static create(options: ApplicationFailureOptions): ApplicationFailure {
198
- const { message, type, nonRetryable = false, details, nextRetryDelay, cause } = options;
199
- return new this(message, type, nonRetryable, details, cause, nextRetryDelay);
227
+ const { message, type, nonRetryable = false, details, nextRetryDelay, cause, category } = options;
228
+ return new this(message, type, nonRetryable, details, cause, nextRetryDelay, category);
200
229
  }
201
230
 
202
231
  /**
@@ -261,6 +290,12 @@ export interface ApplicationFailureOptions {
261
290
  * Cause of the failure
262
291
  */
263
292
  cause?: Error;
293
+
294
+ /**
295
+ * Severity category of the application error.
296
+ * Affects worker-side logging and metrics behavior of this failure.
297
+ */
298
+ category?: ApplicationFailureCategory;
264
299
  }
265
300
 
266
301
  /**
package/src/index.ts CHANGED
@@ -19,11 +19,24 @@ export * from './failure';
19
19
  export { Headers, Next } from './interceptors';
20
20
  export * from './interfaces';
21
21
  export * from './logger';
22
+ export * from './priority';
23
+ export * from './metrics';
22
24
  export * from './retry-policy';
23
25
  export type { Timestamp, Duration, StringValue } from './time';
26
+ export * from './worker-deployments';
27
+ export * from './workflow-definition-options';
24
28
  export * from './workflow-handle';
25
29
  export * from './workflow-options';
26
30
  export * from './versioning-intent';
31
+ export {
32
+ SearchAttributes, // eslint-disable-line deprecation/deprecation
33
+ SearchAttributeValue, // eslint-disable-line deprecation/deprecation
34
+ SearchAttributeType,
35
+ SearchAttributePair,
36
+ SearchAttributeUpdatePair,
37
+ TypedSearchAttributes,
38
+ defineSearchAttributeKey,
39
+ } from './search-attributes';
27
40
 
28
41
  /**
29
42
  * Encode a UTF-8 string into a Uint8Array
package/src/interfaces.ts CHANGED
@@ -93,14 +93,6 @@ export interface QueryDefinition<Ret, Args extends any[] = [], Name extends stri
93
93
  /** Get the "unwrapped" return type (without Promise) of the execute handler from Workflow type `W` */
94
94
  export type WorkflowResultType<W extends Workflow> = ReturnType<W> extends Promise<infer R> ? R : never;
95
95
 
96
- /**
97
- * If another SDK creates a Search Attribute that's not an array, we wrap it in an array.
98
- *
99
- * Dates are serialized as ISO strings.
100
- */
101
- export type SearchAttributes = Record<string, SearchAttributeValue | Readonly<SearchAttributeValue> | undefined>;
102
- export type SearchAttributeValue = string[] | number[] | boolean[] | Date[];
103
-
104
96
  export interface ActivityFunction<P extends any[] = any[], R = any> {
105
97
  (...args: P): Promise<R>;
106
98
  }
@@ -2,7 +2,7 @@ import { Payload } from '../interfaces';
2
2
  import { arrayFromPayloads, fromPayloadsAtIndex, toPayloads } from '../converter/payload-converter';
3
3
  import { PayloadConverterError } from '../errors';
4
4
  import { PayloadCodec } from '../converter/payload-codec';
5
- import { ProtoFailure, TemporalFailure } from '../failure';
5
+ import { ProtoFailure } from '../failure';
6
6
  import { LoadedDataConverter } from '../converter/data-converter';
7
7
  import { DecodedPayload, DecodedProtoFailure, EncodedPayload, EncodedProtoFailure } from './codec-types';
8
8
 
@@ -109,7 +109,7 @@ export async function decodeFromPayloadsAtIndex<T>(
109
109
  export async function decodeOptionalFailureToOptionalError(
110
110
  converter: LoadedDataConverter,
111
111
  failure: ProtoFailure | undefined | null
112
- ): Promise<TemporalFailure | undefined> {
112
+ ): Promise<Error | undefined> {
113
113
  const { failureConverter, payloadConverter, payloadCodecs } = converter;
114
114
  return failure
115
115
  ? failureConverter.failureToError(await decodeFailure(payloadCodecs, failure), payloadConverter)
@@ -9,4 +9,3 @@ export * from './data-converter-helpers';
9
9
  export * from './parse-host-uri';
10
10
  export * from './proxy-config';
11
11
  export * from './tls-config';
12
- export * from './utils';
@@ -1 +1,2 @@
1
1
  export * from './enums-helpers';
2
+ export * from './objects-helpers';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Helper to prevent `undefined` and `null` values overriding defaults when merging maps.
3
+ */
4
+ export function filterNullAndUndefined<T extends Record<string, any>>(obj: T): T {
5
+ return Object.fromEntries(Object.entries(obj).filter(([_k, v]) => v != null)) as any;
6
+ }
7
+
8
+ /**
9
+ * Merge two objects, possibly removing keys.
10
+ *
11
+ * More specifically:
12
+ * - Any key/value pair in `delta` overrides the corresponding key/value pair in `original`;
13
+ * - A key present in `delta` with value `undefined` removes the key from the resulting object;
14
+ * - If `original` is `undefined` or empty, return `delta`;
15
+ * - If `delta` is `undefined` or empty, return `original` (or undefined if `original` is also undefined);
16
+ * - If there are no changes, then return `original`.
17
+ */
18
+ export function mergeObjects<T extends Record<string, any>>(original: T, delta: T | undefined): T;
19
+ export function mergeObjects<T extends Record<string, any>>(
20
+ original: T | undefined,
21
+ delta: T | undefined
22
+ ): T | undefined {
23
+ if (original == null) return delta;
24
+ if (delta == null) return original;
25
+
26
+ const merged: Record<string, any> = { ...original };
27
+ let changed = false;
28
+ for (const [k, v] of Object.entries(delta)) {
29
+ if (v !== merged[k]) {
30
+ if (v == null) delete merged[k];
31
+ else merged[k] = v;
32
+ changed = true;
33
+ }
34
+ }
35
+
36
+ return changed ? (merged as T) : original;
37
+ }
package/src/logger.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { filterNullAndUndefined, mergeObjects } from './internal-workflow';
2
+
1
3
  export type LogLevel = 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
2
4
 
3
5
  export type LogMetadata = Record<string | symbol, any>;
@@ -53,3 +55,109 @@ export enum SdkComponent {
53
55
  */
54
56
  core = 'core',
55
57
  }
58
+
59
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
60
+
61
+ /**
62
+ * @internal
63
+ * @hidden
64
+ */
65
+ export type LogMetaOrFunc = LogMetadata | (() => LogMetadata);
66
+
67
+ /**
68
+ * A logger implementation that adds metadata before delegating calls to a parent logger.
69
+ *
70
+ * @internal
71
+ * @hidden
72
+ */
73
+ export class LoggerWithComposedMetadata implements Logger {
74
+ /**
75
+ * Return a {@link Logger} that adds metadata before delegating calls to a parent logger.
76
+ *
77
+ * New metadata may either be specified statically as a delta object, or as a function evaluated
78
+ * every time a log is emitted that will return a delta object.
79
+ *
80
+ * Some optimizations are performed to avoid creating unnecessary objects and to keep runtime
81
+ * overhead associated with resolving metadata as low as possible.
82
+ */
83
+ public static compose(logger: Logger, metaOrFunc: LogMetaOrFunc): Logger {
84
+ // Flatten recursive LoggerWithComposedMetadata instances
85
+ if (logger instanceof LoggerWithComposedMetadata) {
86
+ const contributors = appendToChain(logger.contributors, metaOrFunc);
87
+ // If the new contributor results in no actual change to the chain, then we don't need a new logger
88
+ if (contributors === undefined) return logger;
89
+ return new LoggerWithComposedMetadata(logger.parentLogger, contributors);
90
+ } else {
91
+ const contributors = appendToChain(undefined, metaOrFunc);
92
+ if (contributors === undefined) return logger;
93
+ return new LoggerWithComposedMetadata(logger, contributors);
94
+ }
95
+ }
96
+
97
+ constructor(
98
+ private readonly parentLogger: Logger,
99
+ private readonly contributors: LogMetaOrFunc[]
100
+ ) {}
101
+
102
+ log(level: LogLevel, message: string, extraMeta?: LogMetadata): void {
103
+ this.parentLogger.log(level, message, resolveMetadata(this.contributors, extraMeta));
104
+ }
105
+
106
+ trace(message: string, extraMeta?: LogMetadata): void {
107
+ this.parentLogger.trace(message, resolveMetadata(this.contributors, extraMeta));
108
+ }
109
+
110
+ debug(message: string, extraMeta?: LogMetadata): void {
111
+ this.parentLogger.debug(message, resolveMetadata(this.contributors, extraMeta));
112
+ }
113
+
114
+ info(message: string, extraMeta?: LogMetadata): void {
115
+ this.parentLogger.info(message, resolveMetadata(this.contributors, extraMeta));
116
+ }
117
+
118
+ warn(message: string, extraMeta?: LogMetadata): void {
119
+ this.parentLogger.warn(message, resolveMetadata(this.contributors, extraMeta));
120
+ }
121
+
122
+ error(message: string, extraMeta?: LogMetadata): void {
123
+ this.parentLogger.error(message, resolveMetadata(this.contributors, extraMeta));
124
+ }
125
+ }
126
+
127
+ function resolveMetadata(contributors: LogMetaOrFunc[], extraMeta?: LogMetadata): LogMetadata {
128
+ const resolved = {};
129
+ for (const contributor of contributors) {
130
+ Object.assign(resolved, typeof contributor === 'function' ? contributor() : contributor);
131
+ }
132
+ Object.assign(resolved, extraMeta);
133
+ return filterNullAndUndefined(resolved);
134
+ }
135
+
136
+ /**
137
+ * Append a metadata contributor to the chain, merging it with the former last contributor if both are plain objects
138
+ */
139
+ function appendToChain(
140
+ existingContributors: LogMetaOrFunc[] | undefined,
141
+ newContributor: LogMetaOrFunc
142
+ ): LogMetaOrFunc[] | undefined {
143
+ // If the new contributor is an empty object, then it results in no actual change to the chain
144
+ if (typeof newContributor === 'object' && Object.keys(newContributor).length === 0) {
145
+ return existingContributors;
146
+ }
147
+
148
+ // If existing chain is empty, then the new contributor is the chain
149
+ if (existingContributors == null || existingContributors.length === 0) {
150
+ return [newContributor];
151
+ }
152
+
153
+ // If both last contributor and new contributor are plain objects, merge them to a single object.
154
+ const last = existingContributors[existingContributors.length - 1];
155
+ if (typeof last === 'object' && typeof newContributor === 'object') {
156
+ const merged = mergeObjects(last, newContributor);
157
+ if (merged === last) return existingContributors;
158
+ return [...existingContributors.slice(0, -1), merged];
159
+ }
160
+
161
+ // Otherwise, just append the new contributor to the chain.
162
+ return [...existingContributors, newContributor];
163
+ }