@@ -10,10 +10,17 @@ import {
1010 ScheduleHandle ,
1111 ScheduleSummary ,
1212 ScheduleUpdateOptions ,
13+ ScheduleDescription ,
1314} from '@temporalio/client' ;
1415import { msToNumber } from '@temporalio/common/lib/time' ;
15- import { SearchAttributes , SearchAttributeType , TypedSearchAttributes } from '@temporalio/common' ;
16- import { registerDefaultCustomSearchAttributes , RUN_INTEGRATION_TESTS } from './helpers' ;
16+ import {
17+ SearchAttributeType ,
18+ SearchAttributes ,
19+ TypedSearchAttributes ,
20+ defineSearchAttributeKey ,
21+ } from '@temporalio/common' ;
22+ import { registerDefaultCustomSearchAttributes , RUN_INTEGRATION_TESTS , waitUntil } from './helpers' ;
23+ import { defaultSAKeys } from './helpers-integration' ;
1724
1825export interface Context {
1926 client : Client ;
@@ -168,9 +175,7 @@ if (RUN_INTEGRATION_TESTS) {
168175 searchAttributes : {
169176 CustomKeywordField : [ 'test-value2' ] ,
170177 } ,
171- typedSearchAttributes : new TypedSearchAttributes ( [
172- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
173- ] ) ,
178+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
174179 } ,
175180 } ) ;
176181
@@ -188,8 +193,8 @@ if (RUN_INTEGRATION_TESTS) {
188193 t . deepEqual (
189194 describedSchedule . action . typedSearchAttributes ,
190195 new TypedSearchAttributes ( [
191- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
192- { key : { name : 'CustomKeywordField' , type : SearchAttributeType . KEYWORD } , value : 'test-value2' } ,
196+ { key : defaultSAKeys . CustomIntField , value : 42 } ,
197+ { key : defaultSAKeys . CustomKeywordField , value : 'test-value2' } ,
193198 ] )
194199 ) ;
195200 } finally {
@@ -216,9 +221,7 @@ if (RUN_INTEGRATION_TESTS) {
216221 searchAttributes : {
217222 CustomKeywordField : [ 'test-value2' ] ,
218223 } ,
219- typedSearchAttributes : new TypedSearchAttributes ( [
220- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
221- ] ) ,
224+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
222225 } ,
223226 } ) ;
224227
@@ -237,8 +240,8 @@ if (RUN_INTEGRATION_TESTS) {
237240 t . deepEqual (
238241 describedSchedule . action . typedSearchAttributes ,
239242 new TypedSearchAttributes ( [
240- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
241- { key : { name : 'CustomKeywordField' , type : SearchAttributeType . KEYWORD } , value : 'test-value2' } ,
243+ { key : defaultSAKeys . CustomIntField , value : 42 } ,
244+ { key : defaultSAKeys . CustomKeywordField , value : 'test-value2' } ,
242245 ] )
243246 ) ;
244247 } finally {
@@ -351,9 +354,7 @@ if (RUN_INTEGRATION_TESTS) {
351354 searchAttributes : {
352355 CustomKeywordField : [ 'test-value2' ] ,
353356 } ,
354- typedSearchAttributes : new TypedSearchAttributes ( [
355- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
356- ] ) ,
357+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
357358 } ,
358359 } ) ;
359360
@@ -598,9 +599,7 @@ if (RUN_INTEGRATION_TESTS) {
598599 taskQueue,
599600 } ,
600601 searchAttributes,
601- typedSearchAttributes : new TypedSearchAttributes ( [
602- { key : { name : 'CustomIntField' , type : SearchAttributeType . INT } , value : 42 } ,
603- ] ) ,
602+ typedSearchAttributes : new TypedSearchAttributes ( [ { key : defaultSAKeys . CustomIntField , value : 42 } ] ) ,
604603 } )
605604 ) ;
606605 }
@@ -759,6 +758,130 @@ if (RUN_INTEGRATION_TESTS) {
759758 }
760759 } ) ;
761760
761+ test . serial ( 'Can update search attributes of a schedule' , async ( t ) => {
762+ const { client } = t . context ;
763+ const scheduleId = `can-update-search-attributes-of-schedule-${ randomUUID ( ) } ` ;
764+
765+ // Helper to wait for search attribute changes to propagate.
766+ const waitForAttributeChange = async (
767+ handle : ScheduleHandle ,
768+ attributeName : string ,
769+ shouldExist : boolean
770+ ) : Promise < ScheduleDescription > => {
771+ await waitUntil ( async ( ) => {
772+ const desc = await handle . describe ( ) ;
773+ const exists =
774+ desc . typedSearchAttributes . getAll ( ) . find ( ( pair ) => pair . key . name === attributeName ) !== undefined ;
775+ return exists === shouldExist ;
776+ } , 300 ) ;
777+ return await handle . describe ( ) ;
778+ } ;
779+
780+ // Create a schedule with search attributes.
781+ const handle = await client . schedule . create ( {
782+ scheduleId,
783+ spec : {
784+ calendars : [ { hour : { start : 2 , end : 7 , step : 1 } } ] ,
785+ } ,
786+ action : {
787+ type : 'startWorkflow' ,
788+ workflowType : dummyWorkflow ,
789+ taskQueue,
790+ } ,
791+ searchAttributes : {
792+ CustomKeywordField : [ 'keyword-one' ] ,
793+ } ,
794+ typedSearchAttributes : [ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 1 } ] ,
795+ } ) ;
796+
797+ // Check the search attributes are part of the schedule description.
798+ const desc = await handle . describe ( ) ;
799+ // eslint-disable-next-line deprecation/deprecation
800+ t . deepEqual ( desc . searchAttributes , {
801+ CustomKeywordField : [ 'keyword-one' ] ,
802+ CustomIntField : [ 1 ] ,
803+ } ) ;
804+ t . deepEqual (
805+ desc . typedSearchAttributes ,
806+ new TypedSearchAttributes ( [
807+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 1 } ,
808+ { key : defineSearchAttributeKey ( 'CustomKeywordField' , SearchAttributeType . KEYWORD ) , value : 'keyword-one' } ,
809+ ] )
810+ ) ;
811+
812+ // Perform a series of updates to schedule's search attributes.
813+ try {
814+ // Update existing search attributes, add new ones.
815+ await handle . update ( ( desc ) => ( {
816+ ...desc ,
817+ searchAttributes : {
818+ CustomKeywordField : [ 'keyword-two' ] ,
819+ // Add a new search attribute.
820+ CustomDoubleField : [ 1.5 ] ,
821+ } ,
822+ typedSearchAttributes : [
823+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 2 } ,
824+ // Add a new typed search attribute.
825+ { key : defineSearchAttributeKey ( 'CustomTextField' , SearchAttributeType . TEXT ) , value : 'new-text' } ,
826+ ] ,
827+ } ) ) ;
828+
829+ let desc = await waitForAttributeChange ( handle , 'CustomTextField' , true ) ;
830+ // eslint-disable-next-line deprecation/deprecation
831+ t . deepEqual ( desc . searchAttributes , {
832+ CustomKeywordField : [ 'keyword-two' ] ,
833+ CustomIntField : [ 2 ] ,
834+ CustomDoubleField : [ 1.5 ] ,
835+ CustomTextField : [ 'new-text' ] ,
836+ } ) ;
837+ t . deepEqual (
838+ desc . typedSearchAttributes ,
839+ new TypedSearchAttributes ( [
840+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 2 } ,
841+ { key : defineSearchAttributeKey ( 'CustomKeywordField' , SearchAttributeType . KEYWORD ) , value : 'keyword-two' } ,
842+ { key : defineSearchAttributeKey ( 'CustomTextField' , SearchAttributeType . TEXT ) , value : 'new-text' } ,
843+ { key : defineSearchAttributeKey ( 'CustomDoubleField' , SearchAttributeType . DOUBLE ) , value : 1.5 } ,
844+ ] )
845+ ) ;
846+
847+ // Update and remove some search attributes. We remove a search attribute by omitting an existing key from the update.
848+ await handle . update ( ( desc ) => ( {
849+ ...desc ,
850+ searchAttributes : {
851+ CustomKeywordField : [ 'keyword-three' ] ,
852+ } ,
853+ typedSearchAttributes : [ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 3 } ] ,
854+ } ) ) ;
855+
856+ desc = await waitForAttributeChange ( handle , 'CustomTextField' , false ) ;
857+ // eslint-disable-next-line deprecation/deprecation
858+ t . deepEqual ( desc . searchAttributes , {
859+ CustomKeywordField : [ 'keyword-three' ] ,
860+ CustomIntField : [ 3 ] ,
861+ } ) ;
862+ t . deepEqual (
863+ desc . typedSearchAttributes ,
864+ new TypedSearchAttributes ( [
865+ { key : defineSearchAttributeKey ( 'CustomIntField' , SearchAttributeType . INT ) , value : 3 } ,
866+ { key : defineSearchAttributeKey ( 'CustomKeywordField' , SearchAttributeType . KEYWORD ) , value : 'keyword-three' } ,
867+ ] )
868+ ) ;
869+
870+ // Remove all search attributes.
871+ await handle . update ( ( desc ) => ( {
872+ ...desc ,
873+ searchAttributes : { } ,
874+ typedSearchAttributes : [ ] ,
875+ } ) ) ;
876+
877+ desc = await waitForAttributeChange ( handle , 'CustomIntField' , false ) ;
878+ t . deepEqual ( desc . searchAttributes , { } ) ; // eslint-disable-line deprecation/deprecation
879+ t . deepEqual ( desc . typedSearchAttributes , new TypedSearchAttributes ( [ ] ) ) ;
880+ } finally {
881+ await handle . delete ( ) ;
882+ }
883+ } ) ;
884+
762885 test . serial ( 'User metadata on schedule' , async ( t ) => {
763886 const { client } = t . context ;
764887 const scheduleId = `schedule-with-user-metadata-${ randomUUID ( ) } ` ;
0 commit comments