@@ -5,85 +5,99 @@ import { patchProtobufRoot } from '@temporalio/proto/lib/patch-protobuf-root';
5
5
export type History = proto . temporal . api . history . v1 . IHistory ;
6
6
export type Payload = proto . temporal . api . common . v1 . IPayload ;
7
7
8
+ /**
9
+ * JSON representation of Temporal's {@link Payload} protobuf object
10
+ */
11
+ export interface JSONPayload {
12
+ /**
13
+ * Mapping of key to base64 encoded value
14
+ */
15
+ metadata ?: Record < string , string > | null ;
16
+ /**
17
+ * base64 encoded value
18
+ */
19
+ data ?: string | null ;
20
+ }
21
+
8
22
// Cast to any because the generated proto module types are missing the lookupType method
9
23
const patched = patchProtobufRoot ( proto ) as any ;
10
24
const historyType = patched . lookupType ( 'temporal.api.history.v1.History' ) ;
11
25
const payloadType = patched . lookupType ( 'temporal.api.common.v1.Payload' ) ;
12
26
13
- function pascalCaseToConstantCase ( s : string ) {
14
- return s . replace ( / [ ^ \b ] [ A - Z ] / g, ( m ) => `${ m [ 0 ] } _${ m [ 1 ] } ` ) . toUpperCase ( ) ;
15
- }
16
-
17
- function fixEnumValue < O extends Record < string , any > > ( obj : O , attr : keyof O , prefix : string ) {
18
- return (
19
- obj [ attr ] && {
20
- [ attr ] : obj [ attr ] . startsWith ( prefix ) ? obj [ attr ] : `${ prefix } _${ pascalCaseToConstantCase ( obj [ attr ] ) } ` ,
21
- }
22
- ) ;
23
- }
27
+ /**
28
+ * Convert a proto JSON representation of History to a valid History object
29
+ */
30
+ export function historyFromJSON ( history : unknown ) : History {
31
+ function pascalCaseToConstantCase ( s : string ) {
32
+ return s . replace ( / [ ^ \b ] [ A - Z ] / g, ( m ) => `${ m [ 0 ] } _${ m [ 1 ] } ` ) . toUpperCase ( ) ;
33
+ }
24
34
25
- // fromProto3JSON doesn't allow null values on 'bytes' fields. This turns out to be a problem for payloads.
26
- // Recursively descend on objects and array, and fix in-place any payload that has a null data field
27
- function fixPayloads < T > ( e : T ) : T {
28
- function isPayload ( p : any ) : p is JSONPayload {
29
- return p && typeof p === 'object' && 'metadata' in p && 'data' in p ;
35
+ function fixEnumValue < O extends Record < string , any > > ( obj : O , attr : keyof O , prefix : string ) {
36
+ return (
37
+ obj [ attr ] && {
38
+ [ attr ] : obj [ attr ] . startsWith ( prefix ) ? obj [ attr ] : `${ prefix } _${ pascalCaseToConstantCase ( obj [ attr ] ) } ` ,
39
+ }
40
+ ) ;
30
41
}
31
42
32
- if ( e && typeof e === 'object' ) {
33
- if ( isPayload ( e ) ) {
34
- if ( e . data === null ) {
35
- const { data : _data , ...rest } = e ;
36
- return rest as T ;
43
+ // fromProto3JSON doesn't allow null values on 'bytes' fields. This turns out to be a problem for payloads.
44
+ // Recursively descend on objects and array, and fix in-place any payload that has a null data field
45
+ function fixPayloads < T > ( e : T ) : T {
46
+ function isPayload ( p : any ) : p is JSONPayload {
47
+ return p && typeof p === 'object' && 'metadata' in p && 'data' in p ;
48
+ }
49
+
50
+ if ( e && typeof e === 'object' ) {
51
+ if ( isPayload ( e ) ) {
52
+ if ( e . data === null ) {
53
+ const { data : _data , ...rest } = e ;
54
+ return rest as T ;
55
+ }
56
+ return e ;
37
57
}
38
- return e ;
58
+ if ( Array . isArray ( e ) ) return e . map ( fixPayloads ) as T ;
59
+ return Object . fromEntries ( Object . entries ( e as object ) . map ( ( [ k , v ] ) => [ k , fixPayloads ( v ) ] ) ) as T ;
39
60
}
40
- if ( Array . isArray ( e ) ) return e . map ( fixPayloads ) as T ;
41
- return Object . fromEntries ( Object . entries ( e as object ) . map ( ( [ k , v ] ) => [ k , fixPayloads ( v ) ] ) ) as T ;
61
+ return e ;
42
62
}
43
- return e ;
44
- }
45
63
46
- function fixHistoryEvent ( e : Record < string , any > ) {
47
- const type = Object . keys ( e ) . find ( ( k ) => k . endsWith ( 'EventAttributes' ) ) ;
48
- if ( ! type ) {
49
- throw new TypeError ( `Missing attributes in history event: ${ JSON . stringify ( e ) } ` ) ;
50
- }
64
+ function fixHistoryEvent ( e : Record < string , any > ) {
65
+ const type = Object . keys ( e ) . find ( ( k ) => k . endsWith ( 'EventAttributes' ) ) ;
66
+ if ( ! type ) {
67
+ throw new TypeError ( `Missing attributes in history event: ${ JSON . stringify ( e ) } ` ) ;
68
+ }
51
69
52
- // Fix payloads with null data
53
- e = fixPayloads ( e ) ;
54
-
55
- return {
56
- ...e ,
57
- ...fixEnumValue ( e , 'eventType' , 'EVENT_TYPE' ) ,
58
- [ type ] : {
59
- ...e [ type ] ,
60
- ...( e [ type ] . taskQueue && {
61
- taskQueue : { ...e [ type ] . taskQueue , ...fixEnumValue ( e [ type ] . taskQueue , 'kind' , 'TASK_QUEUE_KIND' ) } ,
62
- } ) ,
63
- ...fixEnumValue ( e [ type ] , 'parentClosePolicy' , 'PARENT_CLOSE_POLICY' ) ,
64
- ...fixEnumValue ( e [ type ] , 'workflowIdReusePolicy' , 'WORKFLOW_ID_REUSE_POLICY' ) ,
65
- ...fixEnumValue ( e [ type ] , 'initiator' , 'CONTINUE_AS_NEW_INITIATOR' ) ,
66
- ...fixEnumValue ( e [ type ] , 'retryState' , 'RETRY_STATE' ) ,
67
- ...( e [ type ] . childWorkflowExecutionFailureInfo && {
68
- childWorkflowExecutionFailureInfo : {
69
- ...e [ type ] . childWorkflowExecutionFailureInfo ,
70
- ...fixEnumValue ( e [ type ] . childWorkflowExecutionFailureInfo , 'retryState' , 'RETRY_STATE' ) ,
71
- } ,
72
- } ) ,
73
- } ,
74
- } ;
75
- }
70
+ // Fix payloads with null data
71
+ e = fixPayloads ( e ) ;
76
72
77
- function fixHistory ( h : Record < string , any > ) {
78
- return {
79
- events : h . events . map ( fixHistoryEvent ) ,
80
- } ;
81
- }
73
+ return {
74
+ ...e ,
75
+ ...fixEnumValue ( e , 'eventType' , 'EVENT_TYPE' ) ,
76
+ [ type ] : {
77
+ ...e [ type ] ,
78
+ ...( e [ type ] . taskQueue && {
79
+ taskQueue : { ...e [ type ] . taskQueue , ...fixEnumValue ( e [ type ] . taskQueue , 'kind' , 'TASK_QUEUE_KIND' ) } ,
80
+ } ) ,
81
+ ...fixEnumValue ( e [ type ] , 'parentClosePolicy' , 'PARENT_CLOSE_POLICY' ) ,
82
+ ...fixEnumValue ( e [ type ] , 'workflowIdReusePolicy' , 'WORKFLOW_ID_REUSE_POLICY' ) ,
83
+ ...fixEnumValue ( e [ type ] , 'initiator' , 'CONTINUE_AS_NEW_INITIATOR' ) ,
84
+ ...fixEnumValue ( e [ type ] , 'retryState' , 'RETRY_STATE' ) ,
85
+ ...( e [ type ] . childWorkflowExecutionFailureInfo && {
86
+ childWorkflowExecutionFailureInfo : {
87
+ ...e [ type ] . childWorkflowExecutionFailureInfo ,
88
+ ...fixEnumValue ( e [ type ] . childWorkflowExecutionFailureInfo , 'retryState' , 'RETRY_STATE' ) ,
89
+ } ,
90
+ } ) ,
91
+ } ,
92
+ } ;
93
+ }
94
+
95
+ function fixHistory ( h : Record < string , any > ) {
96
+ return {
97
+ events : h . events . map ( fixHistoryEvent ) ,
98
+ } ;
99
+ }
82
100
83
- /**
84
- * Convert a proto JSON representation of History to a valid History object
85
- */
86
- export function historyFromJSON ( history : unknown ) : History {
87
101
if ( typeof history !== 'object' || history == null || ! Array . isArray ( ( history as any ) . events ) ) {
88
102
throw new TypeError ( 'Invalid history, expected an object with an array of events' ) ;
89
103
}
@@ -95,17 +109,26 @@ export function historyFromJSON(history: unknown): History {
95
109
}
96
110
97
111
/**
98
- * JSON representation of Temporal's {@link Payload} protobuf object
112
+ * Convert an History object, e.g. as returned by `WorkflowClient.list().withHistory()`, to a JSON
113
+ * string that adheres to the same norm as JSON history files produced by other Temporal tools.
99
114
*/
100
- export interface JSONPayload {
101
- /**
102
- * Mapping of key to base64 encoded value
103
- */
104
- metadata ?: Record < string , string > | null ;
105
- /**
106
- * base64 encoded value
107
- */
108
- data ?: string | null ;
115
+ export function historyToJSON ( history : History ) : string {
116
+ // toProto3JSON doesn't correctly handle some of our "bytes" fields, passing them untouched to the
117
+ // output, after which JSON.stringify() would convert them to an array of numbers. As a workaround,
118
+ // recursively walk the object and convert all Buffer instances to base64 strings. Note this only
119
+ // works on proto3-json-serializer v2.0.0. v2.0.2 throws an error before we even get the chance
120
+ // to fix the buffers. See https://github.com/googleapis/proto3-json-serializer-nodejs/issues/103.
121
+ function fixBuffers < T > ( e : T ) : T {
122
+ if ( e && typeof e === 'object' ) {
123
+ if ( e instanceof Buffer ) return e . toString ( 'base64' ) as any ;
124
+ if ( Array . isArray ( e ) ) return e . map ( fixBuffers ) as T ;
125
+ return Object . fromEntries ( Object . entries ( e as object ) . map ( ( [ k , v ] ) => [ k , fixBuffers ( v ) ] ) ) as T ;
126
+ }
127
+ return e ;
128
+ }
129
+
130
+ const protoJson = toProto3JSON ( proto . temporal . api . history . v1 . History . fromObject ( history ) as any ) ;
131
+ return JSON . stringify ( fixBuffers ( protoJson ) , null , 2 ) ;
109
132
}
110
133
111
134
/**
0 commit comments