@proompteng/temporal-bun-sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +95 -0
  2. package/dist/src/client/serialization.d.ts +30 -1
  3. package/dist/src/client/serialization.d.ts.map +1 -1
  4. package/dist/src/client/serialization.js +76 -3
  5. package/dist/src/client/serialization.js.map +1 -1
  6. package/dist/src/client/types.d.ts +26 -0
  7. package/dist/src/client/types.d.ts.map +1 -1
  8. package/dist/src/client.d.ts +10 -2
  9. package/dist/src/client.d.ts.map +1 -1
  10. package/dist/src/client.js +243 -1
  11. package/dist/src/client.js.map +1 -1
  12. package/dist/src/index.d.ts +1 -1
  13. package/dist/src/index.d.ts.map +1 -1
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/worker/concurrency.d.ts +8 -0
  16. package/dist/src/worker/concurrency.d.ts.map +1 -1
  17. package/dist/src/worker/concurrency.js +5 -9
  18. package/dist/src/worker/concurrency.js.map +1 -1
  19. package/dist/src/worker/runtime.d.ts.map +1 -1
  20. package/dist/src/worker/runtime.js +303 -29
  21. package/dist/src/worker/runtime.js.map +1 -1
  22. package/dist/src/worker/update-protocol.d.ts +33 -0
  23. package/dist/src/worker/update-protocol.d.ts.map +1 -0
  24. package/dist/src/worker/update-protocol.js +228 -0
  25. package/dist/src/worker/update-protocol.js.map +1 -0
  26. package/dist/src/workflow/context.d.ts +52 -1
  27. package/dist/src/workflow/context.d.ts.map +1 -1
  28. package/dist/src/workflow/context.js +212 -2
  29. package/dist/src/workflow/context.js.map +1 -1
  30. package/dist/src/workflow/definition.d.ts +29 -3
  31. package/dist/src/workflow/definition.d.ts.map +1 -1
  32. package/dist/src/workflow/definition.js +30 -4
  33. package/dist/src/workflow/definition.js.map +1 -1
  34. package/dist/src/workflow/determinism.d.ts +59 -0
  35. package/dist/src/workflow/determinism.d.ts.map +1 -1
  36. package/dist/src/workflow/determinism.js +76 -1
  37. package/dist/src/workflow/determinism.js.map +1 -1
  38. package/dist/src/workflow/errors.d.ts +3 -0
  39. package/dist/src/workflow/errors.d.ts.map +1 -1
  40. package/dist/src/workflow/errors.js +6 -0
  41. package/dist/src/workflow/errors.js.map +1 -1
  42. package/dist/src/workflow/executor.d.ts +53 -0
  43. package/dist/src/workflow/executor.d.ts.map +1 -1
  44. package/dist/src/workflow/executor.js +237 -6
  45. package/dist/src/workflow/executor.js.map +1 -1
  46. package/dist/src/workflow/inbound.d.ts +84 -0
  47. package/dist/src/workflow/inbound.d.ts.map +1 -0
  48. package/dist/src/workflow/inbound.js +65 -0
  49. package/dist/src/workflow/inbound.js.map +1 -0
  50. package/dist/src/workflow/index.d.ts +1 -0
  51. package/dist/src/workflow/index.d.ts.map +1 -1
  52. package/dist/src/workflow/index.js +1 -0
  53. package/dist/src/workflow/index.js.map +1 -1
  54. package/dist/src/workflow/replay.d.ts +24 -2
  55. package/dist/src/workflow/replay.d.ts.map +1 -1
  56. package/dist/src/workflow/replay.js +459 -14
  57. package/dist/src/workflow/replay.js.map +1 -1
  58. package/dist/src/workflows/index.d.ts +1 -1
  59. package/dist/src/workflows/index.d.ts.map +1 -1
  60. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { decodePayloadsToValues, encodeValuesToPayloads } from '../common/payloa
5
5
  import { PayloadsSchema } from '../proto/temporal/api/common/v1/message_pb';
6
6
  import { EventType } from '../proto/temporal/api/enums/v1/event_type_pb';
7
7
  import { ParentClosePolicy, TimeoutType, WorkflowIdReusePolicy } from '../proto/temporal/api/enums/v1/workflow_pb';
8
- import { intentsEqual } from './determinism';
8
+ import { intentsEqual, stableStringify } from './determinism';
9
9
  export const DETERMINISM_MARKER_NAME = 'temporal-bun-sdk/determinism';
10
10
  const DETERMINISM_MARKER_SCHEMA_VERSION = 1;
11
11
  const DETERMINISM_MARKER_DETAIL_KEY = 'snapshot';
@@ -58,6 +58,7 @@ export const ingestWorkflowHistory = (intake) => Effect.gen(function* () {
58
58
  const events = sortHistoryEvents(intake.history ?? []);
59
59
  const shouldUseDeterminismMarker = intake.ignoreDeterminismMarker !== true;
60
60
  let markerSnapshot;
61
+ const updateEntries = collectWorkflowUpdateEntries(events);
61
62
  if (shouldUseDeterminismMarker) {
62
63
  for (const event of events) {
63
64
  if (event.eventType !== EventType.MARKER_RECORDED) {
@@ -76,21 +77,31 @@ export const ingestWorkflowHistory = (intake) => Effect.gen(function* () {
76
77
  }
77
78
  }
78
79
  const extractedFailureMetadata = extractFailureMetadata(events);
80
+ const replayUpdateInvocations = yield* Effect.tryPromise(async () => collectWorkflowUpdateInvocations(events, intake.dataConverter));
79
81
  if (shouldUseDeterminismMarker && markerSnapshot) {
80
- const determinismState = markerSnapshot.determinismState.failureMetadata
81
- ? markerSnapshot.determinismState
82
- : {
83
- ...markerSnapshot.determinismState,
84
- ...(extractedFailureMetadata ? { failureMetadata: extractedFailureMetadata } : {}),
82
+ let determinismState = cloneDeterminismState(markerSnapshot.determinismState);
83
+ if (!determinismState.failureMetadata && extractedFailureMetadata) {
84
+ determinismState = {
85
+ ...determinismState,
86
+ failureMetadata: extractedFailureMetadata,
85
87
  };
88
+ }
89
+ if ((!determinismState.updates || determinismState.updates.length === 0) && updateEntries.length > 0) {
90
+ determinismState = {
91
+ ...determinismState,
92
+ updates: updateEntries,
93
+ };
94
+ }
95
+ determinismState = mergePendingQueryRequests(determinismState, intake.queries);
86
96
  const latestEventId = resolveHistoryLastEventId(events) ?? markerSnapshot.lastEventId ?? null;
87
97
  return {
88
98
  determinismState,
89
99
  lastEventId: latestEventId,
90
100
  hasDeterminismMarker: true,
101
+ ...(replayUpdateInvocations.length > 0 ? { updates: replayUpdateInvocations } : {}),
91
102
  };
92
103
  }
93
- return yield* reconstructDeterminismState(events, intake, extractedFailureMetadata);
104
+ return yield* reconstructDeterminismState(events, intake, extractedFailureMetadata, updateEntries, replayUpdateInvocations);
94
105
  });
95
106
  /**
96
107
  * Diff determinism state against freshly emitted intents to produce rich
@@ -153,6 +164,36 @@ export const diffDeterminismState = (expected, actual) => Effect.sync(() => {
153
164
  });
154
165
  }
155
166
  }
167
+ const expectedSignals = expected.signals ?? [];
168
+ const actualSignals = actual.signals ?? [];
169
+ const maxSignals = Math.max(expectedSignals.length, actualSignals.length);
170
+ for (let index = 0; index < maxSignals; index += 1) {
171
+ const expectedSignal = expectedSignals[index];
172
+ const actualSignal = actualSignals[index];
173
+ if (!recordsMatch(expectedSignal, actualSignal)) {
174
+ mismatches.push({ kind: 'signal', index, expected: expectedSignal, actual: actualSignal });
175
+ }
176
+ }
177
+ const expectedQueries = expected.queries ?? [];
178
+ const actualQueries = actual.queries ?? [];
179
+ const maxQueries = Math.max(expectedQueries.length, actualQueries.length);
180
+ for (let index = 0; index < maxQueries; index += 1) {
181
+ const expectedQuery = expectedQueries[index];
182
+ const actualQuery = actualQueries[index];
183
+ if (!recordsMatch(expectedQuery, actualQuery)) {
184
+ mismatches.push({ kind: 'query', index, expected: expectedQuery, actual: actualQuery });
185
+ }
186
+ }
187
+ const expectedUpdates = expected.updates ?? [];
188
+ const actualUpdates = actual.updates ?? [];
189
+ const maxUpdates = Math.max(expectedUpdates.length, actualUpdates.length);
190
+ for (let index = 0; index < maxUpdates; index += 1) {
191
+ const expectedEntry = expectedUpdates[index];
192
+ const actualEntry = actualUpdates[index];
193
+ if (!updatesEqual(expectedEntry, actualEntry)) {
194
+ mismatches.push({ kind: 'update', index, expected: expectedEntry, actual: actualEntry });
195
+ }
196
+ }
156
197
  return { mismatches };
157
198
  });
158
199
  export const resolveHistoryLastEventId = (events) => {
@@ -180,6 +221,9 @@ export const cloneDeterminismState = (state) => ({
180
221
  randomValues: [...state.randomValues],
181
222
  timeValues: [...state.timeValues],
182
223
  failureMetadata: state.failureMetadata ? { ...state.failureMetadata } : undefined,
224
+ signals: state.signals ? state.signals.map((record) => ({ ...record })) : [],
225
+ queries: state.queries ? state.queries.map((record) => ({ ...record })) : [],
226
+ updates: state.updates ? state.updates.map((entry) => ({ ...entry })) : undefined,
183
227
  });
184
228
  const sortHistoryEvents = (events) => events.slice().sort((left, right) => {
185
229
  const leftId = resolveEventIdForSort(left);
@@ -340,9 +384,12 @@ const resolveCommandMismatchMetadata = (expectedEntry, actualEntry) => {
340
384
  eventType,
341
385
  };
342
386
  };
343
- const reconstructDeterminismState = (events, intake, failureMetadata) => Effect.gen(function* () {
387
+ const reconstructDeterminismState = (events, intake, failureMetadata, precomputedUpdates, replayUpdateInvocations = []) => Effect.gen(function* () {
344
388
  let sequence = 0;
345
389
  const commandHistory = [];
390
+ const signalRecords = [];
391
+ const queryRecords = [];
392
+ const updates = precomputedUpdates ?? collectWorkflowUpdateEntries(events);
346
393
  for (const event of events) {
347
394
  switch (event.eventType) {
348
395
  case EventType.ACTIVITY_TASK_SCHEDULED: {
@@ -401,6 +448,25 @@ const reconstructDeterminismState = (events, intake, failureMetadata) => Effect.
401
448
  }
402
449
  break;
403
450
  }
451
+ case EventType.WORKFLOW_EXECUTION_SIGNALED: {
452
+ if (event.attributes?.case !== 'workflowExecutionSignaledEventAttributes') {
453
+ break;
454
+ }
455
+ const attributes = event.attributes.value;
456
+ const payloads = yield* decodePayloadArray(intake.dataConverter, attributes.input);
457
+ const workflowTaskCompletedEventId = 'workflowTaskCompletedEventId' in attributes
458
+ ? normalizeEventId(attributes
459
+ .workflowTaskCompletedEventId)
460
+ : null;
461
+ signalRecords.push({
462
+ signalName: attributes.signalName ?? 'unknown',
463
+ payloadHash: stableStringify(payloads),
464
+ eventId: normalizeEventId(event.eventId),
465
+ workflowTaskCompletedEventId,
466
+ identity: attributes.identity ?? null,
467
+ });
468
+ break;
469
+ }
404
470
  case EventType.WORKFLOW_EXECUTION_CONTINUED_AS_NEW: {
405
471
  if (event.attributes?.case !== 'workflowExecutionContinuedAsNewEventAttributes') {
406
472
  break;
@@ -419,15 +485,21 @@ const reconstructDeterminismState = (events, intake, failureMetadata) => Effect.
419
485
  break;
420
486
  }
421
487
  }
488
+ let determinismState = {
489
+ commandHistory,
490
+ randomValues: [],
491
+ timeValues: [],
492
+ ...(failureMetadata ? { failureMetadata } : {}),
493
+ signals: signalRecords,
494
+ queries: queryRecords,
495
+ ...(updates.length > 0 ? { updates } : {}),
496
+ };
497
+ determinismState = mergePendingQueryRequests(determinismState, intake.queries);
422
498
  return {
423
- determinismState: {
424
- commandHistory,
425
- randomValues: [],
426
- timeValues: [],
427
- ...(failureMetadata ? { failureMetadata } : {}),
428
- },
499
+ determinismState,
429
500
  lastEventId: resolveHistoryLastEventId(events),
430
501
  hasDeterminismMarker: false,
502
+ ...(replayUpdateInvocations.length > 0 ? { updates: replayUpdateInvocations } : {}),
431
503
  };
432
504
  });
433
505
  const fromActivityTaskScheduled = (attributes, sequence, intake) => Effect.gen(function* () {
@@ -550,6 +622,190 @@ const fromContinueAsNew = (attributes, sequence, intake) => Effect.gen(function*
550
622
  };
551
623
  return intent;
552
624
  });
625
+ const collectWorkflowUpdateEntries = (events) => {
626
+ const updates = [];
627
+ for (const event of events) {
628
+ if (!event.attributes) {
629
+ continue;
630
+ }
631
+ const historyEventId = normalizeEventId(event.eventId) ?? undefined;
632
+ switch (event.eventType) {
633
+ case EventType.WORKFLOW_EXECUTION_UPDATE_ADMITTED: {
634
+ if (event.attributes.case !== 'workflowExecutionUpdateAdmittedEventAttributes') {
635
+ break;
636
+ }
637
+ const attrs = event.attributes.value;
638
+ const updateId = normalizeUpdateId(attrs.request?.meta?.updateId);
639
+ if (!updateId) {
640
+ break;
641
+ }
642
+ updates.push({
643
+ updateId,
644
+ stage: 'admitted',
645
+ handlerName: normalizeOptionalNonEmptyString(attrs.request?.input?.name),
646
+ identity: normalizeOptionalNonEmptyString(attrs.request?.meta?.identity),
647
+ historyEventId,
648
+ });
649
+ break;
650
+ }
651
+ case EventType.WORKFLOW_EXECUTION_UPDATE_ACCEPTED: {
652
+ if (event.attributes.case !== 'workflowExecutionUpdateAcceptedEventAttributes') {
653
+ break;
654
+ }
655
+ const attrs = event.attributes.value;
656
+ const updateId = normalizeUpdateId(attrs.acceptedRequest?.meta?.updateId);
657
+ if (!updateId) {
658
+ break;
659
+ }
660
+ updates.push({
661
+ updateId,
662
+ stage: 'accepted',
663
+ handlerName: normalizeOptionalNonEmptyString(attrs.acceptedRequest?.input?.name),
664
+ identity: normalizeOptionalNonEmptyString(attrs.acceptedRequest?.meta?.identity),
665
+ messageId: normalizeOptionalNonEmptyString(attrs.acceptedRequestMessageId),
666
+ sequencingEventId: normalizeBigintIdentifier(attrs.acceptedRequestSequencingEventId),
667
+ historyEventId,
668
+ });
669
+ break;
670
+ }
671
+ case EventType.WORKFLOW_EXECUTION_UPDATE_REJECTED: {
672
+ if (event.attributes.case !== 'workflowExecutionUpdateRejectedEventAttributes') {
673
+ break;
674
+ }
675
+ const attrs = event.attributes.value;
676
+ const updateId = normalizeUpdateId(attrs.rejectedRequest?.meta?.updateId);
677
+ if (!updateId) {
678
+ break;
679
+ }
680
+ updates.push({
681
+ updateId,
682
+ stage: 'rejected',
683
+ handlerName: normalizeOptionalNonEmptyString(attrs.rejectedRequest?.input?.name),
684
+ identity: normalizeOptionalNonEmptyString(attrs.rejectedRequest?.meta?.identity),
685
+ messageId: normalizeOptionalNonEmptyString(attrs.rejectedRequestMessageId),
686
+ sequencingEventId: normalizeBigintIdentifier(attrs.rejectedRequestSequencingEventId),
687
+ failureMessage: normalizeOptionalNonEmptyString(attrs.failure?.message),
688
+ historyEventId,
689
+ });
690
+ break;
691
+ }
692
+ case EventType.WORKFLOW_EXECUTION_UPDATE_COMPLETED: {
693
+ if (event.attributes.case !== 'workflowExecutionUpdateCompletedEventAttributes') {
694
+ break;
695
+ }
696
+ const attrs = event.attributes.value;
697
+ const updateId = normalizeUpdateId(attrs.meta?.updateId);
698
+ if (!updateId) {
699
+ break;
700
+ }
701
+ updates.push({
702
+ updateId,
703
+ stage: 'completed',
704
+ identity: normalizeOptionalNonEmptyString(attrs.meta?.identity),
705
+ acceptedEventId: normalizeBigintIdentifier(attrs.acceptedEventId),
706
+ outcome: resolveOutcomeStatus(attrs.outcome),
707
+ historyEventId,
708
+ });
709
+ break;
710
+ }
711
+ default:
712
+ break;
713
+ }
714
+ }
715
+ return updates;
716
+ };
717
+ const collectWorkflowUpdateInvocations = async (events, dataConverter) => {
718
+ const invocations = [];
719
+ for (const event of events) {
720
+ if (event.eventType === EventType.WORKFLOW_EXECUTION_UPDATE_ACCEPTED) {
721
+ if (event.attributes.case !== 'workflowExecutionUpdateAcceptedEventAttributes') {
722
+ continue;
723
+ }
724
+ const attrs = event.attributes.value;
725
+ const invocation = await buildUpdateInvocationFromRequest({
726
+ request: attrs.acceptedRequest,
727
+ protocolInstanceId: attrs.protocolInstanceId,
728
+ requestMessageId: attrs.acceptedRequestMessageId,
729
+ sequencingEventId: attrs.acceptedRequestSequencingEventId,
730
+ fallbackEventId: event.eventId,
731
+ dataConverter,
732
+ });
733
+ if (invocation) {
734
+ invocations.push(invocation);
735
+ }
736
+ continue;
737
+ }
738
+ if (event.eventType === EventType.WORKFLOW_EXECUTION_UPDATE_REJECTED) {
739
+ if (event.attributes.case !== 'workflowExecutionUpdateRejectedEventAttributes') {
740
+ continue;
741
+ }
742
+ const attrs = event.attributes.value;
743
+ const invocation = await buildUpdateInvocationFromRequest({
744
+ request: attrs.rejectedRequest,
745
+ protocolInstanceId: attrs.protocolInstanceId,
746
+ requestMessageId: attrs.rejectedRequestMessageId,
747
+ sequencingEventId: attrs.rejectedRequestSequencingEventId,
748
+ fallbackEventId: event.eventId,
749
+ dataConverter,
750
+ });
751
+ if (invocation) {
752
+ invocations.push(invocation);
753
+ }
754
+ continue;
755
+ }
756
+ if (event.eventType === EventType.WORKFLOW_EXECUTION_UPDATE_ADMITTED) {
757
+ if (event.attributes.case !== 'workflowExecutionUpdateAdmittedEventAttributes') {
758
+ continue;
759
+ }
760
+ const attrs = event.attributes.value;
761
+ const invocation = await buildUpdateInvocationFromRequest({
762
+ request: attrs.request,
763
+ protocolInstanceId: 'history-admitted',
764
+ requestMessageId: `history-${event.eventId ?? 'update-admitted'}`,
765
+ sequencingEventId: event.eventId ? BigInt(event.eventId) : undefined,
766
+ fallbackEventId: event.eventId,
767
+ dataConverter,
768
+ });
769
+ if (invocation) {
770
+ invocations.push(invocation);
771
+ }
772
+ }
773
+ }
774
+ return invocations;
775
+ };
776
+ const buildUpdateInvocationFromRequest = async (input) => {
777
+ const req = input.request;
778
+ const updateId = normalizeUpdateId(req?.meta?.updateId);
779
+ const updateName = req?.input?.name?.trim();
780
+ if (!updateId || !updateName) {
781
+ return undefined;
782
+ }
783
+ const values = (await decodePayloadsToValues(input.dataConverter, req?.input?.args?.payloads ?? []))?.map((value) => value) ?? [];
784
+ const payload = normalizeUpdateInvocationPayload(values);
785
+ const sequencing = input.sequencingEventId !== undefined
786
+ ? input.sequencingEventId.toString()
787
+ : input.fallbackEventId !== undefined && input.fallbackEventId !== null
788
+ ? String(input.fallbackEventId)
789
+ : undefined;
790
+ return {
791
+ protocolInstanceId: input.protocolInstanceId ?? 'history-replay',
792
+ requestMessageId: input.requestMessageId ?? updateId,
793
+ updateId,
794
+ name: updateName,
795
+ payload,
796
+ identity: req?.meta?.identity,
797
+ sequencingEventId: sequencing,
798
+ };
799
+ };
800
+ const normalizeUpdateInvocationPayload = (values) => {
801
+ if (!values || values.length === 0) {
802
+ return undefined;
803
+ }
804
+ if (values.length === 1) {
805
+ return values[0];
806
+ }
807
+ return values;
808
+ };
553
809
  const decodePayloadArray = (converter, payloads) => Effect.tryPromise(async () => await decodePayloadsToValues(converter, payloads?.payloads ?? []));
554
810
  const convertRetryPolicy = (policy) => {
555
811
  if (!policy) {
@@ -575,6 +831,45 @@ const convertRetryPolicy = (policy) => {
575
831
  ...(nonRetryable !== undefined ? { nonRetryableErrorTypes: nonRetryable } : {}),
576
832
  };
577
833
  };
834
+ const normalizeUpdateId = (value) => {
835
+ if (typeof value !== 'string') {
836
+ return undefined;
837
+ }
838
+ const trimmed = value.trim();
839
+ return trimmed.length > 0 ? trimmed : undefined;
840
+ };
841
+ const normalizeOptionalNonEmptyString = (value) => {
842
+ if (typeof value !== 'string') {
843
+ return undefined;
844
+ }
845
+ const trimmed = value.trim();
846
+ return trimmed.length > 0 ? trimmed : undefined;
847
+ };
848
+ const normalizeBigintIdentifier = (value) => {
849
+ if (value === undefined || value === null) {
850
+ return undefined;
851
+ }
852
+ if (typeof value === 'string') {
853
+ return value.length > 0 ? value : undefined;
854
+ }
855
+ if (typeof value === 'number' && Number.isFinite(value)) {
856
+ return value.toString();
857
+ }
858
+ if (typeof value === 'bigint') {
859
+ return value.toString();
860
+ }
861
+ return undefined;
862
+ };
863
+ const resolveOutcomeStatus = (outcome) => {
864
+ const caseName = outcome?.value?.case;
865
+ if (caseName === 'success') {
866
+ return 'success';
867
+ }
868
+ if (caseName === 'failure') {
869
+ return 'failure';
870
+ }
871
+ return undefined;
872
+ };
578
873
  const sanitizeDeterminismMarkerEnvelope = (input) => {
579
874
  const schemaVersion = input.schemaVersion;
580
875
  if (schemaVersion !== DETERMINISM_MARKER_SCHEMA_VERSION) {
@@ -611,6 +906,9 @@ const sanitizeDeterminismState = (value) => {
611
906
  const commandHistoryRaw = value.commandHistory;
612
907
  const randomValuesRaw = value.randomValues;
613
908
  const timeValuesRaw = value.timeValues;
909
+ const signalsRaw = Array.isArray(value.signals) ? value.signals : [];
910
+ const queriesRaw = Array.isArray(value.queries) ? value.queries : [];
911
+ const updatesRaw = value.updates;
614
912
  if (!Array.isArray(commandHistoryRaw) || !Array.isArray(randomValuesRaw) || !Array.isArray(timeValuesRaw)) {
615
913
  throw new Error('Determinism marker contained invalid determinism state shape');
616
914
  }
@@ -627,11 +925,17 @@ const sanitizeDeterminismState = (value) => {
627
925
  const randomValues = randomValuesRaw.map((val, index) => coerceNumber(val, `determinism.randomValues[${index}]`));
628
926
  const timeValues = timeValuesRaw.map((val, index) => coerceNumber(val, `determinism.timeValues[${index}]`));
629
927
  const failureMetadata = sanitizeFailureMetadata(value.failureMetadata);
928
+ const signals = signalsRaw.map((record, index) => sanitizeSignalRecord(record, index));
929
+ const queries = queriesRaw.map((record, index) => sanitizeQueryRecord(record, index));
930
+ const updates = sanitizeDeterminismUpdates(updatesRaw);
630
931
  return {
631
932
  commandHistory,
632
933
  randomValues,
633
934
  timeValues,
634
935
  ...(failureMetadata ? { failureMetadata } : {}),
936
+ signals,
937
+ queries,
938
+ ...(updates ? { updates } : {}),
635
939
  };
636
940
  };
637
941
  const sanitizeCommandMetadata = (value, index) => {
@@ -678,6 +982,81 @@ const sanitizeFailureMetadata = (value) => {
678
982
  ...(retryState !== undefined ? { retryState } : {}),
679
983
  };
680
984
  };
985
+ const sanitizeDeterminismUpdates = (value) => {
986
+ if (value === undefined || value === null) {
987
+ return undefined;
988
+ }
989
+ if (!Array.isArray(value)) {
990
+ throw new Error('Determinism marker contained invalid updates list');
991
+ }
992
+ return value.map((entry, index) => {
993
+ if (!isRecord(entry)) {
994
+ throw new Error(`Determinism marker update entry ${index} is invalid`);
995
+ }
996
+ const updateId = coerceString(entry.updateId, `determinism.updates[${index}].updateId`);
997
+ const stage = sanitizeUpdateStage(entry.stage, index);
998
+ const handlerName = coerceOptionalTrimmedString(entry.handlerName, `determinism.updates[${index}].handlerName`);
999
+ const identity = coerceOptionalTrimmedString(entry.identity, `determinism.updates[${index}].identity`);
1000
+ const sequencingEventId = coerceOptionalTrimmedString(entry.sequencingEventId, `determinism.updates[${index}].sequencingEventId`);
1001
+ const messageId = coerceOptionalTrimmedString(entry.messageId, `determinism.updates[${index}].messageId`);
1002
+ const acceptedEventId = coerceOptionalTrimmedString(entry.acceptedEventId, `determinism.updates[${index}].acceptedEventId`);
1003
+ const outcome = sanitizeUpdateOutcome(entry.outcome, index);
1004
+ const failureMessage = coerceOptionalTrimmedString(entry.failureMessage, `determinism.updates[${index}].failureMessage`);
1005
+ const historyEventId = coerceOptionalTrimmedString(entry.historyEventId, `determinism.updates[${index}].historyEventId`);
1006
+ return {
1007
+ updateId,
1008
+ stage,
1009
+ ...(handlerName ? { handlerName } : {}),
1010
+ ...(identity ? { identity } : {}),
1011
+ ...(sequencingEventId ? { sequencingEventId } : {}),
1012
+ ...(messageId ? { messageId } : {}),
1013
+ ...(acceptedEventId ? { acceptedEventId } : {}),
1014
+ ...(outcome ? { outcome } : {}),
1015
+ ...(failureMessage ? { failureMessage } : {}),
1016
+ ...(historyEventId ? { historyEventId } : {}),
1017
+ };
1018
+ });
1019
+ };
1020
+ const sanitizeSignalRecord = (value, index) => {
1021
+ if (!isRecord(value)) {
1022
+ throw new Error(`Determinism marker contained invalid signal entry at index ${index}`);
1023
+ }
1024
+ const signalName = coerceString(value.signalName, `determinism.signals[${index}].signalName`);
1025
+ const payloadHash = coerceString(value.payloadHash, `determinism.signals[${index}].payloadHash`);
1026
+ const handlerName = coerceOptionalString(value.handlerName, `determinism.signals[${index}].handlerName`);
1027
+ const eventId = normalizeOptionalEventId(value.eventId, `determinism.signals[${index}].eventId`);
1028
+ const workflowTaskCompletedEventId = normalizeOptionalEventId(value.workflowTaskCompletedEventId, `determinism.signals[${index}].workflowTaskCompletedEventId`);
1029
+ const identity = coerceOptionalString(value.identity, `determinism.signals[${index}].identity`);
1030
+ return {
1031
+ signalName,
1032
+ payloadHash,
1033
+ ...(handlerName ? { handlerName } : {}),
1034
+ ...(eventId ? { eventId } : {}),
1035
+ ...(workflowTaskCompletedEventId ? { workflowTaskCompletedEventId } : {}),
1036
+ ...(identity ? { identity } : {}),
1037
+ };
1038
+ };
1039
+ const sanitizeQueryRecord = (value, index) => {
1040
+ if (!isRecord(value)) {
1041
+ throw new Error(`Determinism marker contained invalid query entry at index ${index}`);
1042
+ }
1043
+ const queryName = coerceString(value.queryName, `determinism.queries[${index}].queryName`);
1044
+ const requestHash = coerceString(value.requestHash, `determinism.queries[${index}].requestHash`);
1045
+ const handlerName = coerceOptionalString(value.handlerName, `determinism.queries[${index}].handlerName`);
1046
+ const identity = coerceOptionalString(value.identity, `determinism.queries[${index}].identity`);
1047
+ const queryId = coerceOptionalString(value.queryId, `determinism.queries[${index}].queryId`);
1048
+ const resultHash = coerceOptionalString(value.resultHash, `determinism.queries[${index}].resultHash`);
1049
+ const failureHash = coerceOptionalString(value.failureHash, `determinism.queries[${index}].failureHash`);
1050
+ return {
1051
+ queryName,
1052
+ requestHash,
1053
+ ...(handlerName ? { handlerName } : {}),
1054
+ ...(identity ? { identity } : {}),
1055
+ ...(queryId ? { queryId } : {}),
1056
+ ...(resultHash ? { resultHash } : {}),
1057
+ ...(failureHash ? { failureHash } : {}),
1058
+ };
1059
+ };
681
1060
  const sanitizeRecordedAt = (value) => {
682
1061
  if (typeof value === 'string' && value.length > 0) {
683
1062
  return value;
@@ -717,6 +1096,14 @@ const coerceOptionalString = (value, label) => {
717
1096
  }
718
1097
  return value.length > 0 ? value : undefined;
719
1098
  };
1099
+ const coerceOptionalTrimmedString = (value, label) => {
1100
+ const raw = coerceOptionalString(value, label);
1101
+ if (raw === undefined) {
1102
+ return undefined;
1103
+ }
1104
+ const trimmed = raw.trim();
1105
+ return trimmed.length > 0 ? trimmed : undefined;
1106
+ };
720
1107
  const coerceNumber = (value, label) => {
721
1108
  if (typeof value === 'number' && Number.isFinite(value)) {
722
1109
  return value;
@@ -746,6 +1133,21 @@ const normalizeOptionalEventId = (value, label) => {
746
1133
  throw new Error(`Determinism marker contained invalid ${label}`);
747
1134
  }
748
1135
  };
1136
+ const sanitizeUpdateStage = (value, index) => {
1137
+ if (value === 'admitted' || value === 'accepted' || value === 'rejected' || value === 'completed') {
1138
+ return value;
1139
+ }
1140
+ throw new Error(`Determinism marker update entry ${index} contained invalid stage`);
1141
+ };
1142
+ const sanitizeUpdateOutcome = (value, index) => {
1143
+ if (value === undefined || value === null) {
1144
+ return undefined;
1145
+ }
1146
+ if (value === 'success' || value === 'failure') {
1147
+ return value;
1148
+ }
1149
+ throw new Error(`Determinism marker update entry ${index} contained invalid outcome`);
1150
+ };
749
1151
  const valuesEqual = (expected, actual) => {
750
1152
  if (expected === undefined && actual === undefined) {
751
1153
  return true;
@@ -755,5 +1157,48 @@ const valuesEqual = (expected, actual) => {
755
1157
  }
756
1158
  return Object.is(expected, actual);
757
1159
  };
1160
+ const updatesEqual = (expected, actual) => {
1161
+ if (!expected && !actual) {
1162
+ return true;
1163
+ }
1164
+ if (!expected || !actual) {
1165
+ return false;
1166
+ }
1167
+ return (expected.updateId === actual.updateId &&
1168
+ expected.stage === actual.stage &&
1169
+ expected.handlerName === actual.handlerName &&
1170
+ expected.identity === actual.identity &&
1171
+ expected.sequencingEventId === actual.sequencingEventId &&
1172
+ expected.messageId === actual.messageId &&
1173
+ expected.outcome === actual.outcome &&
1174
+ expected.failureMessage === actual.failureMessage);
1175
+ };
1176
+ const recordsMatch = (expected, actual) => {
1177
+ if (expected === undefined && actual === undefined) {
1178
+ return true;
1179
+ }
1180
+ if (expected === undefined || actual === undefined) {
1181
+ return false;
1182
+ }
1183
+ return stableStringify(expected) === stableStringify(actual);
1184
+ };
1185
+ const mergePendingQueryRequests = (state, requests) => {
1186
+ if (!requests || requests.length === 0) {
1187
+ return state;
1188
+ }
1189
+ const nextQueries = state.queries ? [...state.queries] : [];
1190
+ for (const request of requests) {
1191
+ nextQueries.push({
1192
+ queryName: request.name,
1193
+ requestHash: stableStringify(request.args ?? []),
1194
+ ...(request.metadata?.identity ? { identity: request.metadata.identity } : {}),
1195
+ ...(request.id ? { queryId: request.id } : {}),
1196
+ });
1197
+ }
1198
+ return {
1199
+ ...state,
1200
+ queries: nextQueries,
1201
+ };
1202
+ };
758
1203
  const isRecord = (value) => typeof value === 'object' && value !== null;
759
1204
  //# sourceMappingURL=replay.js.map