@@ -12,6 +12,7 @@ import { SemanticInternalAttributes } from "../semanticInternalAttributes.js";
1212import { TriggerTracer } from "../tracer.js" ;
1313import { zodfetch } from "../zodfetch.js" ;
1414import { flattenAttributes } from "./flattenAttributes.js" ;
15+ import get from "lodash.get" ;
1516
1617export type IOPacket = {
1718 data ?: string | undefined ;
@@ -505,13 +506,46 @@ function safeJsonParse(value: string): any {
505506 }
506507}
507508
509+ /**
510+ * Replaces the data in a SuperJSON-serialized string with new payload data while preserving
511+ * the original type metadata (Dates, BigInts, Sets, Maps, etc.).
512+ *
513+ * It is primarily useful for our run replay functionality where we want to preserve the original
514+ * type metadata for the new payload.
515+ *
516+ * Note that `undefined` type metadata is ignored when the corresponding field is overriden in the
517+ * new payload, i.e., fields which were previously undefined in the original payload are restored into
518+ * the primitive type they have in the new payload, instead of `undefined`.
519+ * This is a workaround for https://github.com/triggerdotdev/trigger.dev/issues/1968.
520+ *
521+ * @param original - A SuperJSON-serialized string containing the original data with type metadata
522+ * @param newPayload - A JSON string containing the new data to replace the original payload
523+ * @returns The deserialized object with new data but original type metadata preserved
524+ *
525+ * @throws {Error } If the newPayload is not valid JSON
526+ */
508527export async function replaceSuperJsonPayload ( original : string , newPayload : string ) {
509528 const superjson = await loadSuperJSON ( ) ;
510529 const originalObject = superjson . parse ( original ) ;
530+ const newPayloadObject = JSON . parse ( newPayload ) ;
511531 const { meta } = superjson . serialize ( originalObject ) ;
512532
533+ if ( meta ?. values ) {
534+ const originalUndefinedKeys = Object . entries ( meta . values )
535+ . filter ( ( [ , value ] ) => Array . isArray ( value ) && value . at ( 0 ) === "undefined" )
536+ . map ( ( [ key ] ) => key ) ;
537+
538+ const overridenUndefinedKeys = originalUndefinedKeys . filter (
539+ ( key ) => get ( newPayloadObject , key ) !== undefined
540+ ) ;
541+
542+ overridenUndefinedKeys . forEach ( ( key ) => {
543+ delete ( meta . values as Record < string , any > ) [ key ] ;
544+ } ) ;
545+ }
546+
513547 const newSuperJson = {
514- json : JSON . parse ( newPayload ) as any ,
548+ json : newPayloadObject ,
515549 meta,
516550 } ;
517551
0 commit comments