Skip to content

Commit cca601e

Browse files
authored
Typed search attributes (#1612)
1 parent 4155dad commit cca601e

29 files changed

+1378
-249
lines changed

packages/activity/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,14 @@ export {
9090
*
9191
* @example
9292
*
93-
*```ts
93+
* ```ts
9494
*import { CompleteAsyncError } from '@temporalio/activity';
9595
*
9696
*export async function myActivity(): Promise<never> {
9797
* // ...
9898
* throw new CompleteAsyncError();
9999
*}
100-
*```
100+
* ```
101101
*/
102102
@SymbolBasedInstanceOfError('CompleteAsyncError')
103103
export class CompleteAsyncError extends Error {}

packages/client/src/helpers.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { ServiceError as GrpcServiceError, status as grpcStatus } from '@grpc/grpc-js';
2+
import { LoadedDataConverter, NamespaceNotFoundError } from '@temporalio/common';
23
import {
3-
LoadedDataConverter,
4-
mapFromPayloads,
5-
NamespaceNotFoundError,
4+
decodeSearchAttributes,
5+
decodeTypedSearchAttributes,
66
searchAttributePayloadConverter,
7-
SearchAttributes,
8-
} from '@temporalio/common';
7+
} from '@temporalio/common/lib/converter/payload-search-attributes';
98
import { Replace } from '@temporalio/common/lib/type-helpers';
109
import { optionalTsToDate, requiredTsToDate } from '@temporalio/common/lib/time';
1110
import { decodeMapFromPayloads } from '@temporalio/common/lib/internal-non-workflow/codec-helpers';
@@ -71,11 +70,8 @@ export async function executionInfoFromRaw<T>(
7170
executionTime: optionalTsToDate(raw.executionTime),
7271
closeTime: optionalTsToDate(raw.closeTime),
7372
memo: await decodeMapFromPayloads(dataConverter, raw.memo?.fields),
74-
searchAttributes: Object.fromEntries(
75-
Object.entries(
76-
mapFromPayloads(searchAttributePayloadConverter, raw.searchAttributes?.indexedFields ?? {}) as SearchAttributes
77-
).filter(([_, v]) => v && v.length > 0) // Filter out empty arrays returned by pre 1.18 servers
78-
),
73+
searchAttributes: decodeSearchAttributes(raw.searchAttributes?.indexedFields),
74+
typedSearchAttributes: decodeTypedSearchAttributes(raw.searchAttributes?.indexedFields),
7975
parentExecution: raw.parentExecution
8076
? {
8177
workflowId: raw.parentExecution.workflowId!,

packages/client/src/schedule-client.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { status as grpcStatus } from '@grpc/grpc-js';
22
import { v4 as uuid4 } from 'uuid';
3-
import { mapToPayloads, searchAttributePayloadConverter, Workflow } from '@temporalio/common';
3+
import { Workflow } from '@temporalio/common';
4+
import {
5+
decodeSearchAttributes,
6+
decodeTypedSearchAttributes,
7+
encodeUnifiedSearchAttributes,
8+
} from '@temporalio/common/lib/converter/payload-search-attributes';
49
import { composeInterceptors, Headers } from '@temporalio/common/lib/interceptors';
510
import {
611
encodeMapToPayloads,
@@ -39,7 +44,6 @@ import {
3944
decodeScheduleRecentActions,
4045
decodeScheduleRunningActions,
4146
decodeScheduleSpec,
42-
decodeSearchAttributes,
4347
encodeScheduleAction,
4448
encodeSchedulePolicies,
4549
encodeScheduleSpec,
@@ -238,11 +242,12 @@ export class ScheduleClient extends BaseClient {
238242
state: encodeScheduleState(opts.state),
239243
},
240244
memo: opts.memo ? { fields: await encodeMapToPayloads(this.dataConverter, opts.memo) } : undefined,
241-
searchAttributes: opts.searchAttributes
242-
? {
243-
indexedFields: mapToPayloads(searchAttributePayloadConverter, opts.searchAttributes),
244-
}
245-
: undefined,
245+
searchAttributes:
246+
opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line deprecation/deprecation
247+
? {
248+
indexedFields: encodeUnifiedSearchAttributes(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
249+
}
250+
: undefined,
246251
initialPatch: {
247252
triggerImmediately: opts.state?.triggerImmediately
248253
? { overlapPolicy: temporal.api.enums.v1.ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_ALLOW_ALL }
@@ -388,7 +393,8 @@ export class ScheduleClient extends BaseClient {
388393
workflowType: raw.info.workflowType.name,
389394
},
390395
memo: await decodeMapFromPayloads(this.dataConverter, raw.memo?.fields),
391-
searchAttributes: decodeSearchAttributes(raw.searchAttributes),
396+
searchAttributes: decodeSearchAttributes(raw.searchAttributes?.indexedFields),
397+
typedSearchAttributes: decodeTypedSearchAttributes(raw.searchAttributes?.indexedFields),
392398
state: {
393399
paused: raw.info?.paused === true,
394400
note: raw.info?.notes ?? undefined,
@@ -425,7 +431,8 @@ export class ScheduleClient extends BaseClient {
425431
spec: decodeScheduleSpec(raw.schedule.spec),
426432
action: await decodeScheduleAction(this.client.dataConverter, raw.schedule.action),
427433
memo: await decodeMapFromPayloads(this.client.dataConverter, raw.memo?.fields),
428-
searchAttributes: decodeSearchAttributes(raw.searchAttributes),
434+
searchAttributes: decodeSearchAttributes(raw.searchAttributes?.indexedFields),
435+
typedSearchAttributes: decodeTypedSearchAttributes(raw.searchAttributes?.indexedFields),
429436
policies: {
430437
// 'overlap' should never be missing on describe, as the server will replace UNSPECIFIED by an actual value
431438
overlap: decodeScheduleOverlapPolicy(raw.schedule.policies?.overlapPolicy) ?? ScheduleOverlapPolicy.SKIP,

packages/client/src/schedule-helpers.ts

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import Long from 'long'; // eslint-disable-line import/no-named-as-default
2+
import { compileRetryPolicy, decompileRetryPolicy, extractWorkflowType, LoadedDataConverter } from '@temporalio/common';
23
import {
3-
compileRetryPolicy,
4-
decompileRetryPolicy,
5-
extractWorkflowType,
6-
LoadedDataConverter,
7-
mapFromPayloads,
8-
mapToPayloads,
9-
searchAttributePayloadConverter,
10-
SearchAttributes,
11-
} from '@temporalio/common';
4+
encodeUnifiedSearchAttributes,
5+
decodeSearchAttributes,
6+
decodeTypedSearchAttributes,
7+
} from '@temporalio/common/lib/converter/payload-search-attributes';
128
import { Headers } from '@temporalio/common/lib/interceptors';
139
import {
1410
decodeArrayFromPayloads,
@@ -260,11 +256,12 @@ export async function encodeScheduleAction(
260256
workflowTaskTimeout: msOptionalToTs(action.workflowTaskTimeout),
261257
retryPolicy: action.retry ? compileRetryPolicy(action.retry) : undefined,
262258
memo: action.memo ? { fields: await encodeMapToPayloads(dataConverter, action.memo) } : undefined,
263-
searchAttributes: action.searchAttributes
264-
? {
265-
indexedFields: mapToPayloads(searchAttributePayloadConverter, action.searchAttributes),
266-
}
267-
: undefined,
259+
searchAttributes:
260+
action.searchAttributes || action.typedSearchAttributes // eslint-disable-line deprecation/deprecation
261+
? {
262+
indexedFields: encodeUnifiedSearchAttributes(action.searchAttributes, action.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
263+
}
264+
: undefined,
268265
header: { fields: headers },
269266
},
270267
};
@@ -326,14 +323,8 @@ export async function decodeScheduleAction(
326323
args: await decodeArrayFromPayloads(dataConverter, pb.startWorkflow.input?.payloads),
327324
memo: await decodeMapFromPayloads(dataConverter, pb.startWorkflow.memo?.fields),
328325
retry: decompileRetryPolicy(pb.startWorkflow.retryPolicy),
329-
searchAttributes: Object.fromEntries(
330-
Object.entries(
331-
mapFromPayloads(
332-
searchAttributePayloadConverter,
333-
pb.startWorkflow.searchAttributes?.indexedFields ?? {}
334-
) as SearchAttributes
335-
)
336-
),
326+
searchAttributes: decodeSearchAttributes(pb.startWorkflow.searchAttributes?.indexedFields),
327+
typedSearchAttributes: decodeTypedSearchAttributes(pb.startWorkflow.searchAttributes?.indexedFields),
337328
workflowExecutionTimeout: optionalTsToMs(pb.startWorkflow.workflowExecutionTimeout),
338329
workflowRunTimeout: optionalTsToMs(pb.startWorkflow.workflowRunTimeout),
339330
workflowTaskTimeout: optionalTsToMs(pb.startWorkflow.workflowTaskTimeout),
@@ -342,17 +333,6 @@ export async function decodeScheduleAction(
342333
throw new TypeError('Unsupported schedule action');
343334
}
344335

345-
export function decodeSearchAttributes(
346-
pb: temporal.api.common.v1.ISearchAttributes | undefined | null
347-
): SearchAttributes {
348-
if (!pb?.indexedFields) return {};
349-
return Object.fromEntries(
350-
Object.entries(mapFromPayloads(searchAttributePayloadConverter, pb.indexedFields) as SearchAttributes).filter(
351-
([_, v]) => v && v.length > 0
352-
) // Filter out empty arrays returned by pre 1.18 servers
353-
);
354-
}
355-
356336
export function decodeScheduleRunningActions(
357337
pb?: temporal.api.common.v1.IWorkflowExecution[] | null
358338
): ScheduleExecutionStartWorkflowActionResult[] {

packages/client/src/schedule-types.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { checkExtends, Replace } from '@temporalio/common/lib/type-helpers';
2-
import { Duration, SearchAttributes, Workflow } from '@temporalio/common';
2+
import { Duration, SearchAttributes, Workflow, TypedSearchAttributes, SearchAttributePair } from '@temporalio/common';
33
import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
44
import type { temporal } from '@temporalio/proto';
55
import { WorkflowStartOptions } from './workflow-options';
@@ -70,8 +70,21 @@ export interface ScheduleOptions<A extends ScheduleOptionsAction = ScheduleOptio
7070
* https://docs.temporal.io/docs/typescript/search-attributes
7171
*
7272
* Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided.
73+
*
74+
* @deprecated Use {@link typedSearchAttributes} instead.
7375
*/
74-
searchAttributes?: SearchAttributes;
76+
searchAttributes?: SearchAttributes; // eslint-disable-line deprecation/deprecation
77+
78+
/**
79+
* Additional indexed information attached to the Schedule. More info:
80+
* https://docs.temporal.io/docs/typescript/search-attributes
81+
*
82+
* Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided.
83+
*
84+
* If both {@link searchAttributes} and {@link typedSearchAttributes} are provided, conflicting keys will be overwritten
85+
* by {@link typedSearchAttributes}.
86+
*/
87+
typedSearchAttributes?: SearchAttributePair[] | TypedSearchAttributes;
7588

7689
/**
7790
* The initial state of the schedule, right after creation or update.
@@ -129,7 +142,7 @@ export type CompiledScheduleOptions = Replace<
129142
* The specification of an updated Schedule, as expected by {@link ScheduleHandle.update}.
130143
*/
131144
export type ScheduleUpdateOptions<A extends ScheduleOptionsAction = ScheduleOptionsAction> = Replace<
132-
Omit<ScheduleOptions, 'scheduleId' | 'memo' | 'searchAttributes'>,
145+
Omit<ScheduleOptions, 'scheduleId' | 'memo' | 'searchAttributes' | 'typedSearchAttributes'>,
133146
{
134147
action: A;
135148
state: Omit<ScheduleOptions['state'], 'triggerImmediately' | 'backfill'>;
@@ -172,12 +185,22 @@ export interface ScheduleSummary {
172185
memo?: Record<string, unknown>;
173186

174187
/**
175-
* Additional indexed information attached to the Schedule.
176-
* More info: https://docs.temporal.io/docs/typescript/search-attributes
188+
* Additional indexed information attached to the Schedule. More info:
189+
* https://docs.temporal.io/docs/typescript/search-attributes
177190
*
178191
* Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided.
192+
*
193+
* @deprecated Use {@link typedSearchAttributes} instead.
179194
*/
180-
searchAttributes?: SearchAttributes;
195+
searchAttributes?: SearchAttributes; // eslint-disable-line deprecation/deprecation
196+
197+
/**
198+
* Additional indexed information attached to the Schedule. More info:
199+
* https://docs.temporal.io/docs/typescript/search-attributes
200+
*
201+
* Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided.
202+
*/
203+
typedSearchAttributes?: TypedSearchAttributes;
181204

182205
state: {
183206
/**
@@ -284,12 +307,22 @@ export type ScheduleDescription = {
284307
memo?: Record<string, unknown>;
285308

286309
/**
287-
* Additional indexed information attached to the Schedule.
288-
* More info: https://docs.temporal.io/docs/typescript/search-attributes
310+
* Additional indexed information attached to the Schedule. More info:
311+
* https://docs.temporal.io/docs/typescript/search-attributes
312+
*
313+
* Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided.
314+
*
315+
* @deprecated Use {@link typedSearchAttributes} instead.
316+
*/
317+
searchAttributes: SearchAttributes; // eslint-disable-line deprecation/deprecation
318+
319+
/**
320+
* Additional indexed information attached to the Schedule. More info:
321+
* https://docs.temporal.io/docs/typescript/search-attributes
289322
*
290323
* Values are always converted using {@link JsonPayloadConverter}, even when a custom Data Converter is provided.
291324
*/
292-
searchAttributes: SearchAttributes;
325+
typedSearchAttributes: TypedSearchAttributes;
293326

294327
state: {
295328
/**
@@ -745,6 +778,7 @@ export type ScheduleOptionsStartWorkflowAction<W extends Workflow> = {
745778
| 'args'
746779
| 'memo'
747780
| 'searchAttributes'
781+
| 'typedSearchAttributes'
748782
| 'retry'
749783
| 'workflowExecutionTimeout'
750784
| 'workflowRunTimeout'
@@ -776,6 +810,7 @@ export type ScheduleDescriptionStartWorkflowAction = ScheduleSummaryStartWorkflo
776810
| 'args'
777811
| 'memo'
778812
| 'searchAttributes'
813+
| 'typedSearchAttributes'
779814
| 'retry'
780815
| 'workflowExecutionTimeout'
781816
| 'workflowRunTimeout'

packages/client/src/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type * as grpc from '@grpc/grpc-js';
2-
import type { SearchAttributes, SearchAttributeValue } from '@temporalio/common';
2+
import type { TypedSearchAttributes, SearchAttributes, SearchAttributeValue } from '@temporalio/common';
33
import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow';
44
import * as proto from '@temporalio/proto';
55
import { Replace } from '@temporalio/common/lib/type-helpers';
@@ -47,7 +47,9 @@ export interface WorkflowExecutionInfo {
4747
executionTime?: Date;
4848
closeTime?: Date;
4949
memo?: Record<string, unknown>;
50-
searchAttributes: SearchAttributes;
50+
/** @deprecated Use {@link typedSearchAttributes} instead. */
51+
searchAttributes: SearchAttributes; // eslint-disable-line deprecation/deprecation
52+
typedSearchAttributes: TypedSearchAttributes;
5153
parentExecution?: Required<proto.temporal.api.common.v1.IWorkflowExecution>;
5254
raw: RawWorkflowExecutionInfo;
5355
}
@@ -56,7 +58,7 @@ export interface CountWorkflowExecution {
5658
count: number;
5759
groups: {
5860
count: number;
59-
groupValues: SearchAttributeValue[];
61+
groupValues: SearchAttributeValue[]; // eslint-disable-line deprecation/deprecation
6062
}[];
6163
}
6264

packages/client/src/workflow-client.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import {
44
BaseWorkflowHandle,
55
CancelledFailure,
66
compileRetryPolicy,
7-
mapToPayloads,
87
HistoryAndWorkflowId,
98
QueryDefinition,
109
RetryState,
11-
searchAttributePayloadConverter,
1210
SignalDefinition,
1311
UpdateDefinition,
1412
TerminatedFailure,
@@ -25,6 +23,7 @@ import {
2523
encodeWorkflowIdConflictPolicy,
2624
WorkflowIdConflictPolicy,
2725
} from '@temporalio/common';
26+
import { encodeUnifiedSearchAttributes } from '@temporalio/common/lib/converter/payload-search-attributes';
2827
import { composeInterceptors } from '@temporalio/common/lib/interceptors';
2928
import { History } from '@temporalio/common/lib/proto-utils';
3029
import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers';
@@ -1218,11 +1217,12 @@ export class WorkflowClient extends BaseClient {
12181217
workflowStartDelay: options.startDelay,
12191218
retryPolicy: options.retry ? compileRetryPolicy(options.retry) : undefined,
12201219
memo: options.memo ? { fields: await encodeMapToPayloads(this.dataConverter, options.memo) } : undefined,
1221-
searchAttributes: options.searchAttributes
1222-
? {
1223-
indexedFields: mapToPayloads(searchAttributePayloadConverter, options.searchAttributes),
1224-
}
1225-
: undefined,
1220+
searchAttributes:
1221+
options.searchAttributes || options.typedSearchAttributes // eslint-disable-line deprecation/deprecation
1222+
? {
1223+
indexedFields: encodeUnifiedSearchAttributes(options.searchAttributes, options.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
1224+
}
1225+
: undefined,
12261226
cronSchedule: options.cronSchedule,
12271227
header: { fields: headers },
12281228
};
@@ -1265,6 +1265,7 @@ export class WorkflowClient extends BaseClient {
12651265
protected async createStartWorkflowRequest(input: WorkflowStartInput): Promise<StartWorkflowExecutionRequest> {
12661266
const { options: opts, workflowType, headers } = input;
12671267
const { identity, namespace } = this.options;
1268+
12681269
return {
12691270
namespace,
12701271
identity,
@@ -1284,11 +1285,12 @@ export class WorkflowClient extends BaseClient {
12841285
workflowStartDelay: opts.startDelay,
12851286
retryPolicy: opts.retry ? compileRetryPolicy(opts.retry) : undefined,
12861287
memo: opts.memo ? { fields: await encodeMapToPayloads(this.dataConverter, opts.memo) } : undefined,
1287-
searchAttributes: opts.searchAttributes
1288-
? {
1289-
indexedFields: mapToPayloads(searchAttributePayloadConverter, opts.searchAttributes),
1290-
}
1291-
: undefined,
1288+
searchAttributes:
1289+
opts.searchAttributes || opts.typedSearchAttributes // eslint-disable-line deprecation/deprecation
1290+
? {
1291+
indexedFields: encodeUnifiedSearchAttributes(opts.searchAttributes, opts.typedSearchAttributes), // eslint-disable-line deprecation/deprecation
1292+
}
1293+
: undefined,
12921294
cronSchedule: opts.cronSchedule,
12931295
header: { fields: headers },
12941296
};

0 commit comments

Comments
 (0)