From 5ac2c4b749032429ae3e6a4542198f2897b266c9 Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Tue, 1 Apr 2025 13:45:05 -0700 Subject: [PATCH 1/5] Add RawValue support for non-converted Payloads --- .../common/src/converter/payload-converter.ts | 65 ++++++++++++++++++- packages/common/src/converter/types.ts | 6 ++ .../test/src/test-integration-workflows.ts | 45 ++++++++++++- packages/workflow/src/internals.ts | 23 ++++--- packages/workflow/src/workflow.ts | 4 +- 5 files changed, 129 insertions(+), 14 deletions(-) diff --git a/packages/common/src/converter/payload-converter.ts b/packages/common/src/converter/payload-converter.ts index 662eb1a99..d0db7662c 100644 --- a/packages/common/src/converter/payload-converter.ts +++ b/packages/common/src/converter/payload-converter.ts @@ -1,7 +1,7 @@ import { decode, encode } from '../encoding'; import { PayloadConverterError, ValueError } from '../errors'; import { Payload } from '../interfaces'; -import { encodingKeys, encodingTypes, METADATA_ENCODING_KEY } from './types'; +import { encodingKeys, encodingTypes, METADATA_ENCODING_KEY, METADATA_RAW_VALUE_KEY } from './types'; /** * Used by the framework to serialize/deserialize data like parameters and return values. @@ -100,6 +100,60 @@ export function mapFromPayloads( ) as Record; } +/** + * RawValue is a wrapper over a payload. + * A payload that belongs to a RawValue is special in that it bypasses normal payload conversion, + * but still undergoes codec conversion. + */ +export class RawValue { + private readonly _payload: Payload; + + /** + * Receives an incoming Payload and returns a RawValue wrapping it. + * + * Notably, this method strips any raw value metadata if it exists. + * This allows users to convert the payload back to its native representation. + * + * @param payload the incoming Payload + * @returns an instance of RawValue + */ + static receive(payload: Payload): RawValue { + if (payload.metadata == null) { + throw new ValueError('Missing payload metadata'); + } + // Remove the raw value identifier key (if it exists). + delete payload.metadata[METADATA_RAW_VALUE_KEY]; + return new RawValue(payload); + } + + constructor(payload: Payload) { + this._payload = payload; + } + + get payload(): Payload { + return this._payload; + } + + /** + * Sends the Payload from the RawValue. + * + * Notably, this method add raw value metadata to identify that the Payload + * belongs to a RawValue when we {@link receive}. + * + * @param payload the incoming Payload + * @returns an instance of RawValue + */ + send(): Payload { + return { + metadata: { + ...this.payload.metadata, + [METADATA_RAW_VALUE_KEY]: encode('true'), + }, + data: this.payload.data, + }; + } +} + export interface PayloadConverterWithEncoding { /** * Converts a value to a {@link Payload}. @@ -143,6 +197,9 @@ export class CompositePayloadConverter implements PayloadConverter { * Returns the first successful result, throws {@link ValueError} if there is no converter that can handle the value. */ public toPayload(value: T): Payload { + if (value instanceof RawValue) { + return value.send(); + } for (const converter of this.converters) { const result = converter.toPayload(value); if (result !== undefined) { @@ -160,6 +217,12 @@ export class CompositePayloadConverter implements PayloadConverter { if (payload.metadata === undefined || payload.metadata === null) { throw new ValueError('Missing payload metadata'); } + // Payload is intended to be a RawValue. + // Avoid payload conversion, return payload wrapped as RawValue. + if (payload.metadata[METADATA_RAW_VALUE_KEY]) { + return RawValue.receive(payload) as T; + } + const encoding = decode(payload.metadata[METADATA_ENCODING_KEY]); const converter = this.converterByEncoding.get(encoding); if (converter === undefined) { diff --git a/packages/common/src/converter/types.ts b/packages/common/src/converter/types.ts index 413c359a3..ba9bede27 100644 --- a/packages/common/src/converter/types.ts +++ b/packages/common/src/converter/types.ts @@ -19,3 +19,9 @@ export const encodingKeys = { } as const; export const METADATA_MESSAGE_TYPE_KEY = 'messageType'; + +/** + * Metadata key used to identify a RawValue payload. + * A RawValue payload is a payload intended to bypass normal payload conversion. + */ +export const METADATA_RAW_VALUE_KEY = 'rawValue'; diff --git a/packages/test/src/test-integration-workflows.ts b/packages/test/src/test-integration-workflows.ts index 9d55c87ef..03fb9de82 100644 --- a/packages/test/src/test-integration-workflows.ts +++ b/packages/test/src/test-integration-workflows.ts @@ -14,11 +14,14 @@ import { ActivityCancellationType, ApplicationFailure, defineSearchAttributeKey, + METADATA_ENCODING_KEY, + RawValue, SearchAttributePair, SearchAttributeType, TypedSearchAttributes, WorkflowExecutionAlreadyStartedError, } from '@temporalio/common'; +import { temporal } from '@temporalio/proto'; import { signalSchedulingWorkflow } from './activities/helpers'; import { activityStartedSignal } from './workflows/definitions'; import * as workflows from './workflows'; @@ -384,7 +387,10 @@ test('Query workflow metadata returns handler descriptions', async (t) => { await worker.runUntil(async () => { const handle = await startWorkflow(queryWorkflowMetadata); - const meta = await handle.query(workflow.workflowMetadataQuery); + const rawValue = await handle.query(workflow.workflowMetadataQuery); + const meta = workflow.defaultPayloadConverter.fromPayload( + rawValue.payload + ) as temporal.api.sdk.v1.IWorkflowMetadata; t.is(meta.definition?.type, 'queryWorkflowMetadata'); const queryDefinitions = meta.definition?.queryDefinitions; // Three built-in ones plus dummyQuery1 and dummyQuery2 @@ -1337,3 +1343,40 @@ test('can register search attributes to dev server', async (t) => { t.deepEqual(desc.searchAttributes, { 'new-search-attr': [12] }); // eslint-disable-line deprecation/deprecation await env.teardown(); }); + +export async function rawValueWorkflow(rawValue: RawValue): Promise { + const { rawValueActivity } = workflow.proxyActivities({ startToCloseTimeout: '10s' }); + return await rawValueActivity(rawValue); +} + +test('workflow and activity can receive/return RawValue', async (t) => { + const { executeWorkflow, createWorker } = helpers(t); + const worker = await createWorker({ + activities: { + async rawValueActivity(rawValue: RawValue): Promise { + return rawValue; + }, + }, + }); + + await worker.runUntil(async () => { + const testValue = 'test'; + const rawValueWithKey: RawValue = new RawValue(workflow.defaultPayloadConverter.toPayload(testValue)); + const res = await executeWorkflow(rawValueWorkflow, { + args: [rawValueWithKey], + }); + // Compare payloads. Explicitly convert to Uint8Array because the actual + // returned payload has Buffer types. + console.log('RETURNED', res); + const actualMetadata = res.payload.metadata![METADATA_ENCODING_KEY]; + const expectedMetadata = rawValueWithKey.payload.metadata![METADATA_ENCODING_KEY]; + t.deepEqual(new Uint8Array(actualMetadata), new Uint8Array(expectedMetadata)); + const actualData = res.payload.data!; + const expectedData = rawValueWithKey.payload.data!; + t.deepEqual(new Uint8Array(actualData), new Uint8Array(expectedData)); + + // Compare value from wrapped payload. + const resValue = workflow.defaultPayloadConverter.fromPayload(res.payload); + t.deepEqual(resValue, testValue); + }); +}); diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index acc49985b..182a2d073 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -20,6 +20,7 @@ import { WorkflowUpdateValidatorType, mapFromPayloads, fromPayloadsAtIndex, + RawValue, } from '@temporalio/common'; import { decodeSearchAttributes, @@ -27,7 +28,7 @@ import { } from '@temporalio/common/lib/converter/payload-search-attributes'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow'; -import type { coresdk, temporal } from '@temporalio/proto'; +import type { coresdk } from '@temporalio/proto'; import { alea, RNG } from './alea'; import { RootCancellationScope } from './cancellation-scope'; import { UpdateScope } from './update-scope'; @@ -292,7 +293,7 @@ export class Activator implements ActivationHandler { [ '__temporal_workflow_metadata', { - handler: (): temporal.api.sdk.v1.IWorkflowMetadata => { + handler: (): RawValue => { const workflowType = this.info.workflowType; const queryDefinitions = Array.from(this.queryHandlers.entries()).map(([name, value]) => ({ name, @@ -306,14 +307,16 @@ export class Activator implements ActivationHandler { name, description: value.description, })); - return { - definition: { - type: workflowType, - queryDefinitions, - signalDefinitions, - updateDefinitions, - }, - }; + return new RawValue( + this.payloadConverter.toPayload({ + definition: { + type: workflowType, + queryDefinitions, + signalDefinitions, + updateDefinitions, + }, + }) + ); }, description: 'Returns metadata associated with this workflow.', }, diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index 711b71b04..0b44f1895 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -22,6 +22,7 @@ import { WorkflowReturnType, WorkflowUpdateValidatorType, SearchAttributeUpdatePair, + RawValue, } from '@temporalio/common'; import { encodeUnifiedSearchAttributes, @@ -30,7 +31,6 @@ import { import { versioningIntentToProto } from '@temporalio/common/lib/versioning-intent-enum'; import { Duration, msOptionalToTs, msToNumber, msToTs, requiredTsToMs } from '@temporalio/common/lib/time'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; -import { temporal } from '@temporalio/proto'; import { CancellationScope, registerSleepImplementation } from './cancellation-scope'; import { UpdateScope } from './update-scope'; import { @@ -1589,4 +1589,4 @@ export function allHandlersFinished(): boolean { export const stackTraceQuery = defineQuery('__stack_trace'); export const enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); -export const workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); +export const workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); From eff5009bb2a1d3a72bdd6c654120915b82388527 Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Tue, 22 Jul 2025 10:24:21 -0400 Subject: [PATCH 2/5] simplify RawValue API, use default converter, return RawValue from all internal queries --- .../common/src/converter/payload-converter.ts | 50 ++----------------- packages/common/src/converter/types.ts | 6 --- packages/test/src/workflows/stack-tracer.ts | 13 +++-- packages/workflow/src/internals.ts | 17 ++++--- packages/workflow/src/workflow.ts | 4 +- 5 files changed, 26 insertions(+), 64 deletions(-) diff --git a/packages/common/src/converter/payload-converter.ts b/packages/common/src/converter/payload-converter.ts index d0db7662c..e43081a16 100644 --- a/packages/common/src/converter/payload-converter.ts +++ b/packages/common/src/converter/payload-converter.ts @@ -1,7 +1,7 @@ import { decode, encode } from '../encoding'; import { PayloadConverterError, ValueError } from '../errors'; import { Payload } from '../interfaces'; -import { encodingKeys, encodingTypes, METADATA_ENCODING_KEY, METADATA_RAW_VALUE_KEY } from './types'; +import { encodingKeys, encodingTypes, METADATA_ENCODING_KEY } from './types'; /** * Used by the framework to serialize/deserialize data like parameters and return values. @@ -102,30 +102,12 @@ export function mapFromPayloads( /** * RawValue is a wrapper over a payload. - * A payload that belongs to a RawValue is special in that it bypasses normal payload conversion, - * but still undergoes codec conversion. + * A payload that belongs to a RawValue is special in that it bypasses user-defined payload converters, + * instead using the default payload converter. The payload still undergoes codec conversion. */ export class RawValue { private readonly _payload: Payload; - /** - * Receives an incoming Payload and returns a RawValue wrapping it. - * - * Notably, this method strips any raw value metadata if it exists. - * This allows users to convert the payload back to its native representation. - * - * @param payload the incoming Payload - * @returns an instance of RawValue - */ - static receive(payload: Payload): RawValue { - if (payload.metadata == null) { - throw new ValueError('Missing payload metadata'); - } - // Remove the raw value identifier key (if it exists). - delete payload.metadata[METADATA_RAW_VALUE_KEY]; - return new RawValue(payload); - } - constructor(payload: Payload) { this._payload = payload; } @@ -133,25 +115,6 @@ export class RawValue { get payload(): Payload { return this._payload; } - - /** - * Sends the Payload from the RawValue. - * - * Notably, this method add raw value metadata to identify that the Payload - * belongs to a RawValue when we {@link receive}. - * - * @param payload the incoming Payload - * @returns an instance of RawValue - */ - send(): Payload { - return { - metadata: { - ...this.payload.metadata, - [METADATA_RAW_VALUE_KEY]: encode('true'), - }, - data: this.payload.data, - }; - } } export interface PayloadConverterWithEncoding { @@ -198,7 +161,7 @@ export class CompositePayloadConverter implements PayloadConverter { */ public toPayload(value: T): Payload { if (value instanceof RawValue) { - return value.send(); + return value.payload; } for (const converter of this.converters) { const result = converter.toPayload(value); @@ -217,11 +180,6 @@ export class CompositePayloadConverter implements PayloadConverter { if (payload.metadata === undefined || payload.metadata === null) { throw new ValueError('Missing payload metadata'); } - // Payload is intended to be a RawValue. - // Avoid payload conversion, return payload wrapped as RawValue. - if (payload.metadata[METADATA_RAW_VALUE_KEY]) { - return RawValue.receive(payload) as T; - } const encoding = decode(payload.metadata[METADATA_ENCODING_KEY]); const converter = this.converterByEncoding.get(encoding); diff --git a/packages/common/src/converter/types.ts b/packages/common/src/converter/types.ts index ba9bede27..413c359a3 100644 --- a/packages/common/src/converter/types.ts +++ b/packages/common/src/converter/types.ts @@ -19,9 +19,3 @@ export const encodingKeys = { } as const; export const METADATA_MESSAGE_TYPE_KEY = 'messageType'; - -/** - * Metadata key used to identify a RawValue payload. - * A RawValue payload is a payload intended to bypass normal payload conversion. - */ -export const METADATA_RAW_VALUE_KEY = 'rawValue'; diff --git a/packages/test/src/workflows/stack-tracer.ts b/packages/test/src/workflows/stack-tracer.ts index e68c9dfad..736a5e0fe 100644 --- a/packages/test/src/workflows/stack-tracer.ts +++ b/packages/test/src/workflows/stack-tracer.ts @@ -4,6 +4,7 @@ */ import * as wf from '@temporalio/workflow'; import type { EnhancedStackTrace } from '@temporalio/workflow/lib/interfaces'; +import { defaultPayloadConverter } from '@temporalio/common/lib/converter/payload-converter'; import type * as activities from '../activities'; import { unblockOrCancel } from './unblock-or-cancel'; @@ -17,12 +18,16 @@ export async function stackTracer(): Promise<[string, string]> { const [first] = await Promise.all([ trigger, Promise.race([ - queryOwnWf(wf.stackTraceQuery).then((stack) => trigger.resolve(stack)), + queryOwnWf(wf.stackTraceQuery).then((stack) => + trigger.resolve(defaultPayloadConverter.fromPayload(stack.payload)) + ), executeChild(unblockOrCancel), sleep(100_000), ]), ]); - const second = await queryOwnWf(wf.stackTraceQuery); + const second = await queryOwnWf(wf.stackTraceQuery).then((stack) => + defaultPayloadConverter.fromPayload(stack.payload) + ); return [first, second]; } @@ -32,7 +37,9 @@ export async function enhancedStackTracer(): Promise { const [enhStack] = await Promise.all([ trigger, Promise.race([ - queryOwnWf(wf.enhancedStackTraceQuery).then((stack) => trigger.resolve(stack)), + queryOwnWf(wf.enhancedStackTraceQuery).then((stack) => + trigger.resolve(defaultPayloadConverter.fromPayload(stack.payload)) + ), executeChild(unblockOrCancel), sleep(100_000), ]), diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index 182a2d073..7a7637adc 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -39,7 +39,6 @@ import { DefaultSignalHandler, StackTraceSDKInfo, StackTraceFileSlice, - EnhancedStackTrace, StackTraceFileLocation, WorkflowInfo, WorkflowCreateOptionsInternal, @@ -255,9 +254,13 @@ export class Activator implements ActivationHandler { '__stack_trace', { handler: () => { - return this.getStackTraces() - .map((s) => s.formatted) - .join('\n\n'); + return new RawValue( + defaultPayloadConverter.toPayload( + this.getStackTraces() + .map((s) => s.formatted) + .join('\n\n') + ) + ); }, description: 'Returns a sensible stack trace.', }, @@ -265,7 +268,7 @@ export class Activator implements ActivationHandler { [ '__enhanced_stack_trace', { - handler: (): EnhancedStackTrace => { + handler: (): RawValue => { const { sourceMap } = this; const sdk: StackTraceSDKInfo = { name: 'typescript', version: pkg.version }; const stacks = this.getStackTraces().map(({ structured: locations }) => ({ locations })); @@ -285,7 +288,7 @@ export class Activator implements ActivationHandler { } } } - return { sdk, stacks, sources }; + return new RawValue(defaultPayloadConverter.toPayload({ sdk, stacks, sources })); }, description: 'Returns a stack trace annotated with source information.', }, @@ -308,7 +311,7 @@ export class Activator implements ActivationHandler { description: value.description, })); return new RawValue( - this.payloadConverter.toPayload({ + defaultPayloadConverter.toPayload({ definition: { type: workflowType, queryDefinitions, diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index 0b44f1895..dc5edd7be 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -1587,6 +1587,6 @@ export function allHandlersFinished(): boolean { return activator.inProgressSignals.size === 0 && activator.inProgressUpdates.size === 0; } -export const stackTraceQuery = defineQuery('__stack_trace'); -export const enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); +export const stackTraceQuery = defineQuery('__stack_trace'); +export const enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); export const workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); From 573c2ca3dc21396a3cab8790122d25c9b741a644 Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Tue, 22 Jul 2025 13:26:45 -0400 Subject: [PATCH 3/5] update test --- .../common/src/converter/payload-converter.ts | 4 +-- .../test/src/test-integration-split-three.ts | 4 +-- .../test/src/test-integration-workflows.ts | 34 +++++-------------- packages/test/src/workflows/stack-tracer.ts | 12 ++----- packages/workflow/src/internals.ts | 28 +++++++-------- packages/workflow/src/workflow.ts | 7 ++-- 6 files changed, 32 insertions(+), 57 deletions(-) diff --git a/packages/common/src/converter/payload-converter.ts b/packages/common/src/converter/payload-converter.ts index e43081a16..20def1955 100644 --- a/packages/common/src/converter/payload-converter.ts +++ b/packages/common/src/converter/payload-converter.ts @@ -108,8 +108,8 @@ export function mapFromPayloads( export class RawValue { private readonly _payload: Payload; - constructor(payload: Payload) { - this._payload = payload; + constructor(value: unknown) { + this._payload = defaultPayloadConverter.toPayload(value); } get payload(): Payload { diff --git a/packages/test/src/test-integration-split-three.ts b/packages/test/src/test-integration-split-three.ts index 5539a5765..cdefc94a3 100644 --- a/packages/test/src/test-integration-split-three.ts +++ b/packages/test/src/test-integration-split-three.ts @@ -101,7 +101,7 @@ if ('promiseHooks' in v8) { { file_path: '/packages/test/src/workflows/stack-tracer.ts', function_name: 'enhancedStackTracer', - line: 32, + line: 33, column: 35, internal_code: false, }, @@ -112,7 +112,7 @@ if ('promiseHooks' in v8) { { file_path: '/packages/test/src/workflows/stack-tracer.ts', function_name: 'enhancedStackTracer', - line: 32, + line: 33, column: 35, internal_code: false, }, diff --git a/packages/test/src/test-integration-workflows.ts b/packages/test/src/test-integration-workflows.ts index af3129840..0d63ba0a3 100644 --- a/packages/test/src/test-integration-workflows.ts +++ b/packages/test/src/test-integration-workflows.ts @@ -14,7 +14,6 @@ import { ActivityCancellationType, ApplicationFailure, defineSearchAttributeKey, - METADATA_ENCODING_KEY, RawValue, SearchAttributePair, SearchAttributeType, @@ -389,10 +388,7 @@ test('Query workflow metadata returns handler descriptions', async (t) => { await worker.runUntil(async () => { const handle = await startWorkflow(queryWorkflowMetadata); - const rawValue = await handle.query(workflow.workflowMetadataQuery); - const meta = workflow.defaultPayloadConverter.fromPayload( - rawValue.payload - ) as temporal.api.sdk.v1.IWorkflowMetadata; + const meta = (await handle.query(workflow.workflowMetadataQuery)) as temporal.api.sdk.v1.IWorkflowMetadata; t.is(meta.definition?.type, 'queryWorkflowMetadata'); const queryDefinitions = meta.definition?.queryDefinitions; // Three built-in ones plus dummyQuery1 and dummyQuery2 @@ -1346,41 +1342,29 @@ test('can register search attributes to dev server', async (t) => { await env.teardown(); }); -export async function rawValueWorkflow(rawValue: RawValue): Promise { +export async function rawValueWorkflow(value: unknown): Promise { const { rawValueActivity } = workflow.proxyActivities({ startToCloseTimeout: '10s' }); - return await rawValueActivity(rawValue); + return await rawValueActivity(new RawValue(value)); } test('workflow and activity can receive/return RawValue', async (t) => { const { executeWorkflow, createWorker } = helpers(t); const worker = await createWorker({ activities: { - async rawValueActivity(rawValue: RawValue): Promise { - return rawValue; + async rawValueActivity(value: unknown): Promise { + return new RawValue(value); }, }, }); await worker.runUntil(async () => { const testValue = 'test'; - const rawValueWithKey: RawValue = new RawValue(workflow.defaultPayloadConverter.toPayload(testValue)); + const rawValue = new RawValue(testValue); const res = await executeWorkflow(rawValueWorkflow, { - args: [rawValueWithKey], - }); - // Compare payloads. Explicitly convert to Uint8Array because the actual - // returned payload has Buffer types. - console.log('RETURNED', res); - const actualMetadata = res.payload.metadata![METADATA_ENCODING_KEY]; - const expectedMetadata = rawValueWithKey.payload.metadata![METADATA_ENCODING_KEY]; - t.deepEqual(new Uint8Array(actualMetadata), new Uint8Array(expectedMetadata)); - const actualData = res.payload.data!; - const expectedData = rawValueWithKey.payload.data!; - t.deepEqual(new Uint8Array(actualData), new Uint8Array(expectedData)); - - // Compare value from wrapped payload. - const resValue = workflow.defaultPayloadConverter.fromPayload(res.payload); - t.deepEqual(resValue, testValue); + args: [rawValue], }); + t.deepEqual(res, testValue); + }); }); export async function ChildWorkflowInfo(): Promise { diff --git a/packages/test/src/workflows/stack-tracer.ts b/packages/test/src/workflows/stack-tracer.ts index 736a5e0fe..d754e2d5d 100644 --- a/packages/test/src/workflows/stack-tracer.ts +++ b/packages/test/src/workflows/stack-tracer.ts @@ -18,16 +18,12 @@ export async function stackTracer(): Promise<[string, string]> { const [first] = await Promise.all([ trigger, Promise.race([ - queryOwnWf(wf.stackTraceQuery).then((stack) => - trigger.resolve(defaultPayloadConverter.fromPayload(stack.payload)) - ), + queryOwnWf(wf.stackTraceQuery).then((stack) => trigger.resolve(stack)), executeChild(unblockOrCancel), sleep(100_000), ]), ]); - const second = await queryOwnWf(wf.stackTraceQuery).then((stack) => - defaultPayloadConverter.fromPayload(stack.payload) - ); + const second = await queryOwnWf(wf.stackTraceQuery); return [first, second]; } @@ -37,9 +33,7 @@ export async function enhancedStackTracer(): Promise { const [enhStack] = await Promise.all([ trigger, Promise.race([ - queryOwnWf(wf.enhancedStackTraceQuery).then((stack) => - trigger.resolve(defaultPayloadConverter.fromPayload(stack.payload)) - ), + queryOwnWf(wf.enhancedStackTraceQuery).then((stack) => trigger.resolve(stack)), executeChild(unblockOrCancel), sleep(100_000), ]), diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index 531e4c744..222f90dda 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -264,11 +264,9 @@ export class Activator implements ActivationHandler { { handler: () => { return new RawValue( - defaultPayloadConverter.toPayload( - this.getStackTraces() - .map((s) => s.formatted) - .join('\n\n') - ) + this.getStackTraces() + .map((s) => s.formatted) + .join('\n\n') ); }, description: 'Returns a sensible stack trace.', @@ -297,7 +295,7 @@ export class Activator implements ActivationHandler { } } } - return new RawValue(defaultPayloadConverter.toPayload({ sdk, stacks, sources })); + return new RawValue({ sdk, stacks, sources }); }, description: 'Returns a stack trace annotated with source information.', }, @@ -319,16 +317,14 @@ export class Activator implements ActivationHandler { name, description: value.description, })); - return new RawValue( - defaultPayloadConverter.toPayload({ - definition: { - type: workflowType, - queryDefinitions, - signalDefinitions, - updateDefinitions, - }, - }) - ); + return new RawValue({ + definition: { + type: workflowType, + queryDefinitions, + signalDefinitions, + updateDefinitions, + }, + }); }, description: 'Returns metadata associated with this workflow.', }, diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index 06680e600..db3435807 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -33,6 +33,7 @@ import { import { versioningIntentToProto } from '@temporalio/common/lib/versioning-intent-enum'; import { Duration, msOptionalToTs, msToNumber, msToTs, requiredTsToMs } from '@temporalio/common/lib/time'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; +import { temporal } from '@temporalio/proto'; import { CancellationScope, registerSleepImplementation } from './cancellation-scope'; import { UpdateScope } from './update-scope'; import { @@ -1652,6 +1653,6 @@ export function setWorkflowOptions( }); } -export const stackTraceQuery = defineQuery('__stack_trace'); -export const enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); -export const workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); +export const stackTraceQuery = defineQuery('__stack_trace'); +export const enhancedStackTraceQuery = defineQuery('__enhanced_stack_trace'); +export const workflowMetadataQuery = defineQuery('__temporal_workflow_metadata'); From de9d0b94f41fcbfef97a2d358e730604a50d9bc0 Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Thu, 31 Jul 2025 13:10:34 -0400 Subject: [PATCH 4/5] remove unused imports, import typing of RawValue --- packages/common/src/converter/payload-converter.ts | 8 +++++--- packages/test/src/test-integration-workflows.ts | 3 +-- packages/test/src/workflows/stack-tracer.ts | 1 - packages/workflow/src/internals.ts | 9 +++++---- packages/workflow/src/workflow.ts | 1 - 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/common/src/converter/payload-converter.ts b/packages/common/src/converter/payload-converter.ts index 20def1955..ee329f73b 100644 --- a/packages/common/src/converter/payload-converter.ts +++ b/packages/common/src/converter/payload-converter.ts @@ -105,11 +105,13 @@ export function mapFromPayloads( * A payload that belongs to a RawValue is special in that it bypasses user-defined payload converters, * instead using the default payload converter. The payload still undergoes codec conversion. */ -export class RawValue { +export declare const rawPayloadTypeBrand: unique symbol; +export class RawValue { private readonly _payload: Payload; + private readonly [rawPayloadTypeBrand]: T = undefined as T; - constructor(value: unknown) { - this._payload = defaultPayloadConverter.toPayload(value); + constructor(value: T, payloadConverter: PayloadConverter = defaultPayloadConverter) { + this._payload = payloadConverter.toPayload(value); } get payload(): Payload { diff --git a/packages/test/src/test-integration-workflows.ts b/packages/test/src/test-integration-workflows.ts index 0d63ba0a3..cef4061e4 100644 --- a/packages/test/src/test-integration-workflows.ts +++ b/packages/test/src/test-integration-workflows.ts @@ -20,7 +20,6 @@ import { TypedSearchAttributes, WorkflowExecutionAlreadyStartedError, } from '@temporalio/common'; -import { temporal } from '@temporalio/proto'; import { signalSchedulingWorkflow } from './activities/helpers'; import { activityStartedSignal } from './workflows/definitions'; import * as workflows from './workflows'; @@ -388,7 +387,7 @@ test('Query workflow metadata returns handler descriptions', async (t) => { await worker.runUntil(async () => { const handle = await startWorkflow(queryWorkflowMetadata); - const meta = (await handle.query(workflow.workflowMetadataQuery)) as temporal.api.sdk.v1.IWorkflowMetadata; + const meta = await handle.query(workflow.workflowMetadataQuery); t.is(meta.definition?.type, 'queryWorkflowMetadata'); const queryDefinitions = meta.definition?.queryDefinitions; // Three built-in ones plus dummyQuery1 and dummyQuery2 diff --git a/packages/test/src/workflows/stack-tracer.ts b/packages/test/src/workflows/stack-tracer.ts index d754e2d5d..e68c9dfad 100644 --- a/packages/test/src/workflows/stack-tracer.ts +++ b/packages/test/src/workflows/stack-tracer.ts @@ -4,7 +4,6 @@ */ import * as wf from '@temporalio/workflow'; import type { EnhancedStackTrace } from '@temporalio/workflow/lib/interfaces'; -import { defaultPayloadConverter } from '@temporalio/common/lib/converter/payload-converter'; import type * as activities from '../activities'; import { unblockOrCancel } from './unblock-or-cancel'; diff --git a/packages/workflow/src/internals.ts b/packages/workflow/src/internals.ts index 222f90dda..231560375 100644 --- a/packages/workflow/src/internals.ts +++ b/packages/workflow/src/internals.ts @@ -31,7 +31,7 @@ import { } from '@temporalio/common/lib/converter/payload-search-attributes'; import { composeInterceptors } from '@temporalio/common/lib/interceptors'; import { makeProtoEnumConverters } from '@temporalio/common/lib/internal-workflow'; -import type { coresdk } from '@temporalio/proto'; +import type { coresdk, temporal } from '@temporalio/proto'; import { alea, RNG } from './alea'; import { RootCancellationScope } from './cancellation-scope'; import { UpdateScope } from './update-scope'; @@ -48,6 +48,7 @@ import { ActivationCompletion, DefaultUpdateHandler, DefaultQueryHandler, + EnhancedStackTrace, } from './interfaces'; import { type SinkCall } from './sinks'; import { untrackPromise } from './stack-helpers'; @@ -263,7 +264,7 @@ export class Activator implements ActivationHandler { '__stack_trace', { handler: () => { - return new RawValue( + return new RawValue( this.getStackTraces() .map((s) => s.formatted) .join('\n\n') @@ -295,7 +296,7 @@ export class Activator implements ActivationHandler { } } } - return new RawValue({ sdk, stacks, sources }); + return new RawValue({ sdk, stacks, sources }); }, description: 'Returns a stack trace annotated with source information.', }, @@ -317,7 +318,7 @@ export class Activator implements ActivationHandler { name, description: value.description, })); - return new RawValue({ + return new RawValue({ definition: { type: workflowType, queryDefinitions, diff --git a/packages/workflow/src/workflow.ts b/packages/workflow/src/workflow.ts index db3435807..2d5898865 100644 --- a/packages/workflow/src/workflow.ts +++ b/packages/workflow/src/workflow.ts @@ -22,7 +22,6 @@ import { WorkflowReturnType, WorkflowUpdateValidatorType, SearchAttributeUpdatePair, - RawValue, compilePriority, WorkflowDefinitionOptionsOrGetter, } from '@temporalio/common'; From 88385a174a89b891787d42bebd9e465e72bc3f7b Mon Sep 17 00:00:00 2001 From: Thomas Hardy Date: Thu, 31 Jul 2025 15:19:42 -0400 Subject: [PATCH 5/5] move brand symbol --- packages/common/src/converter/payload-converter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/converter/payload-converter.ts b/packages/common/src/converter/payload-converter.ts index ee329f73b..5baa8278d 100644 --- a/packages/common/src/converter/payload-converter.ts +++ b/packages/common/src/converter/payload-converter.ts @@ -100,12 +100,12 @@ export function mapFromPayloads( ) as Record; } +export declare const rawPayloadTypeBrand: unique symbol; /** * RawValue is a wrapper over a payload. * A payload that belongs to a RawValue is special in that it bypasses user-defined payload converters, * instead using the default payload converter. The payload still undergoes codec conversion. */ -export declare const rawPayloadTypeBrand: unique symbol; export class RawValue { private readonly _payload: Payload; private readonly [rawPayloadTypeBrand]: T = undefined as T;