@proompteng/temporal-bun-sdk 0.4.0 → 0.5.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 (112) hide show
  1. package/README.md +8 -1
  2. package/dist/src/bin/temporal-bun.js +1 -1
  3. package/dist/src/common/payloads/converter.js +1 -1
  4. package/dist/src/common/payloads/converter.js.map +1 -1
  5. package/dist/src/common/payloads/json-codec.d.ts.map +1 -1
  6. package/dist/src/common/payloads/json-codec.js +4 -6
  7. package/dist/src/common/payloads/json-codec.js.map +1 -1
  8. package/dist/src/config.d.ts +16 -0
  9. package/dist/src/config.d.ts.map +1 -1
  10. package/dist/src/config.js +49 -0
  11. package/dist/src/config.js.map +1 -1
  12. package/dist/src/interceptors/client.d.ts.map +1 -1
  13. package/dist/src/interceptors/client.js +24 -3
  14. package/dist/src/interceptors/client.js.map +1 -1
  15. package/dist/src/interceptors/worker.d.ts.map +1 -1
  16. package/dist/src/interceptors/worker.js +24 -3
  17. package/dist/src/interceptors/worker.js.map +1 -1
  18. package/dist/src/observability/index.d.ts +4 -0
  19. package/dist/src/observability/index.d.ts.map +1 -1
  20. package/dist/src/observability/index.js +4 -0
  21. package/dist/src/observability/index.js.map +1 -1
  22. package/dist/src/observability/metrics.d.ts.map +1 -1
  23. package/dist/src/observability/metrics.js +70 -10
  24. package/dist/src/observability/metrics.js.map +1 -1
  25. package/dist/src/observability/opentelemetry.d.ts +20 -0
  26. package/dist/src/observability/opentelemetry.d.ts.map +1 -0
  27. package/dist/src/observability/opentelemetry.js +334 -0
  28. package/dist/src/observability/opentelemetry.js.map +1 -0
  29. package/dist/src/proto/temporal/api/activity/v1/message_pb.js +1 -1
  30. package/dist/src/proto/temporal/api/batch/v1/message_pb.js +1 -1
  31. package/dist/src/proto/temporal/api/command/v1/message_pb.js +1 -1
  32. package/dist/src/proto/temporal/api/common/v1/message_pb.js +1 -1
  33. package/dist/src/proto/temporal/api/deployment/v1/message_pb.js +1 -1
  34. package/dist/src/proto/temporal/api/enums/v1/batch_operation_pb.js +1 -1
  35. package/dist/src/proto/temporal/api/enums/v1/command_type_pb.js +1 -1
  36. package/dist/src/proto/temporal/api/enums/v1/common_pb.js +1 -1
  37. package/dist/src/proto/temporal/api/enums/v1/deployment_pb.js +1 -1
  38. package/dist/src/proto/temporal/api/enums/v1/event_type_pb.js +1 -1
  39. package/dist/src/proto/temporal/api/enums/v1/failed_cause_pb.js +1 -1
  40. package/dist/src/proto/temporal/api/enums/v1/namespace_pb.js +1 -1
  41. package/dist/src/proto/temporal/api/enums/v1/nexus_pb.js +1 -1
  42. package/dist/src/proto/temporal/api/enums/v1/query_pb.js +1 -1
  43. package/dist/src/proto/temporal/api/enums/v1/reset_pb.js +1 -1
  44. package/dist/src/proto/temporal/api/enums/v1/schedule_pb.js +1 -1
  45. package/dist/src/proto/temporal/api/enums/v1/task_queue_pb.js +1 -1
  46. package/dist/src/proto/temporal/api/enums/v1/update_pb.js +1 -1
  47. package/dist/src/proto/temporal/api/enums/v1/workflow_pb.js +1 -1
  48. package/dist/src/proto/temporal/api/errordetails/v1/message_pb.js +1 -1
  49. package/dist/src/proto/temporal/api/export/v1/message_pb.js +1 -1
  50. package/dist/src/proto/temporal/api/failure/v1/message_pb.js +1 -1
  51. package/dist/src/proto/temporal/api/filter/v1/message_pb.js +1 -1
  52. package/dist/src/proto/temporal/api/history/v1/message_pb.js +1 -1
  53. package/dist/src/proto/temporal/api/namespace/v1/message_pb.js +1 -1
  54. package/dist/src/proto/temporal/api/nexus/v1/message_pb.js +1 -1
  55. package/dist/src/proto/temporal/api/operatorservice/v1/request_response_pb.js +1 -1
  56. package/dist/src/proto/temporal/api/operatorservice/v1/service_pb.js +1 -1
  57. package/dist/src/proto/temporal/api/protocol/v1/message_pb.js +1 -1
  58. package/dist/src/proto/temporal/api/query/v1/message_pb.js +1 -1
  59. package/dist/src/proto/temporal/api/replication/v1/message_pb.js +1 -1
  60. package/dist/src/proto/temporal/api/rules/v1/message_pb.js +1 -1
  61. package/dist/src/proto/temporal/api/sdk/v1/enhanced_stack_trace_pb.js +1 -1
  62. package/dist/src/proto/temporal/api/sdk/v1/task_complete_metadata_pb.js +1 -1
  63. package/dist/src/proto/temporal/api/sdk/v1/user_metadata_pb.js +1 -1
  64. package/dist/src/proto/temporal/api/sdk/v1/worker_config_pb.js +1 -1
  65. package/dist/src/proto/temporal/api/sdk/v1/workflow_metadata_pb.js +1 -1
  66. package/dist/src/proto/temporal/api/taskqueue/v1/message_pb.js +1 -1
  67. package/dist/src/proto/temporal/api/update/v1/message_pb.js +1 -1
  68. package/dist/src/proto/temporal/api/version/v1/message_pb.js +1 -1
  69. package/dist/src/proto/temporal/api/worker/v1/message_pb.js +1 -1
  70. package/dist/src/proto/temporal/api/workflow/v1/message_pb.js +1 -1
  71. package/dist/src/proto/temporal/api/workflowservice/v1/request_response_pb.js +1 -1
  72. package/dist/src/proto/temporal/api/workflowservice/v1/service_pb.js +1 -1
  73. package/dist/src/worker/runtime.d.ts +1 -0
  74. package/dist/src/worker/runtime.d.ts.map +1 -1
  75. package/dist/src/worker/runtime.js +649 -61
  76. package/dist/src/worker/runtime.js.map +1 -1
  77. package/dist/src/worker/sticky-cache.d.ts +10 -0
  78. package/dist/src/worker/sticky-cache.d.ts.map +1 -1
  79. package/dist/src/worker/sticky-cache.js.map +1 -1
  80. package/dist/src/workflow/commands.d.ts +2 -1
  81. package/dist/src/workflow/commands.d.ts.map +1 -1
  82. package/dist/src/workflow/commands.js +9 -7
  83. package/dist/src/workflow/commands.js.map +1 -1
  84. package/dist/src/workflow/context.d.ts.map +1 -1
  85. package/dist/src/workflow/context.js +28 -9
  86. package/dist/src/workflow/context.js.map +1 -1
  87. package/dist/src/workflow/definition.js.map +1 -1
  88. package/dist/src/workflow/determinism.d.ts +4 -0
  89. package/dist/src/workflow/determinism.d.ts.map +1 -1
  90. package/dist/src/workflow/determinism.js +104 -17
  91. package/dist/src/workflow/determinism.js.map +1 -1
  92. package/dist/src/workflow/executor.d.ts +3 -0
  93. package/dist/src/workflow/executor.d.ts.map +1 -1
  94. package/dist/src/workflow/executor.js +80 -11
  95. package/dist/src/workflow/executor.js.map +1 -1
  96. package/dist/src/workflow/inbound.d.ts +1 -0
  97. package/dist/src/workflow/inbound.d.ts.map +1 -1
  98. package/dist/src/workflow/inbound.js +1 -0
  99. package/dist/src/workflow/inbound.js.map +1 -1
  100. package/dist/src/workflow/index.d.ts +5 -0
  101. package/dist/src/workflow/index.d.ts.map +1 -1
  102. package/dist/src/workflow/index.js +3 -0
  103. package/dist/src/workflow/index.js.map +1 -1
  104. package/dist/src/workflow/log.d.ts +18 -0
  105. package/dist/src/workflow/log.d.ts.map +1 -0
  106. package/dist/src/workflow/log.js +36 -0
  107. package/dist/src/workflow/log.js.map +1 -0
  108. package/dist/src/workflow/replay.d.ts +31 -3
  109. package/dist/src/workflow/replay.d.ts.map +1 -1
  110. package/dist/src/workflow/replay.js +543 -66
  111. package/dist/src/workflow/replay.js.map +1 -1
  112. package/package.json +4 -3
@@ -7,7 +7,8 @@ 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
8
  import { intentsEqual, stableStringify } from './determinism';
9
9
  export const DETERMINISM_MARKER_NAME = 'temporal-bun-sdk/determinism';
10
- const DETERMINISM_MARKER_SCHEMA_VERSION = 1;
10
+ const DETERMINISM_MARKER_SCHEMA_VERSION_V1 = 1;
11
+ const DETERMINISM_MARKER_SCHEMA_VERSION_V2 = 2;
11
12
  const DETERMINISM_MARKER_DETAIL_KEY = 'snapshot';
12
13
  /**
13
14
  * Encode a workflow determinism snapshot into a marker payload map suitable for
@@ -15,13 +16,25 @@ const DETERMINISM_MARKER_DETAIL_KEY = 'snapshot';
15
16
  */
16
17
  export const encodeDeterminismMarkerDetails = (converter, input) => Effect.tryPromise(async () => {
17
18
  const recordedAt = (input.recordedAt ?? new Date()).toISOString();
18
- const envelope = {
19
- schemaVersion: DETERMINISM_MARKER_SCHEMA_VERSION,
20
- workflow: input.info,
21
- determinismState: input.determinismState,
22
- lastEventId: normalizeEventId(input.lastEventId),
23
- recordedAtIso: recordedAt,
24
- };
19
+ const markerType = input.markerType ?? 'full';
20
+ if (markerType === 'delta' && !input.determinismDelta) {
21
+ throw new Error('Determinism marker delta payload missing');
22
+ }
23
+ const envelope = markerType === 'delta'
24
+ ? {
25
+ schemaVersion: DETERMINISM_MARKER_SCHEMA_VERSION_V2,
26
+ workflow: input.info,
27
+ determinismDelta: input.determinismDelta,
28
+ lastEventId: normalizeEventId(input.lastEventId),
29
+ recordedAtIso: recordedAt,
30
+ }
31
+ : {
32
+ schemaVersion: DETERMINISM_MARKER_SCHEMA_VERSION_V1,
33
+ workflow: input.info,
34
+ determinismState: input.determinismState,
35
+ lastEventId: normalizeEventId(input.lastEventId),
36
+ recordedAtIso: recordedAt,
37
+ };
25
38
  const payloads = await encodeValuesToPayloads(converter, [envelope]);
26
39
  if (!payloads || payloads.length === 0) {
27
40
  throw new Error('Failed to encode determinism marker payloads');
@@ -30,6 +43,20 @@ export const encodeDeterminismMarkerDetails = (converter, input) => Effect.tryPr
30
43
  [DETERMINISM_MARKER_DETAIL_KEY]: create(PayloadsSchema, { payloads }),
31
44
  };
32
45
  });
46
+ export const measurePayloadsByteSize = (details) => {
47
+ let total = 0;
48
+ for (const payloads of Object.values(details)) {
49
+ for (const payload of payloads.payloads ?? []) {
50
+ total += payload.data?.byteLength ?? 0;
51
+ const metadata = payload.metadata ?? {};
52
+ for (const value of Object.values(metadata)) {
53
+ total += value?.byteLength ?? 0;
54
+ }
55
+ }
56
+ }
57
+ return total;
58
+ };
59
+ export const encodeDeterminismMarkerDetailsWithSize = (converter, input) => encodeDeterminismMarkerDetails(converter, input).pipe(Effect.map((details) => ({ details, sizeBytes: measurePayloadsByteSize(details) })));
33
60
  /**
34
61
  * Decode determinism snapshot metadata from a marker payload map. Returns
35
62
  * `undefined` when the supplied details do not contain a determinism snapshot.
@@ -57,10 +84,14 @@ export const decodeDeterminismMarkerEnvelope = (input) => Effect.tryPromise(asyn
57
84
  export const ingestWorkflowHistory = (intake) => Effect.gen(function* () {
58
85
  const events = sortHistoryEvents(intake.history ?? []);
59
86
  const shouldUseDeterminismMarker = intake.ignoreDeterminismMarker !== true;
60
- let markerSnapshot;
61
- const updateEntries = collectWorkflowUpdateEntries(events);
87
+ let markerState;
88
+ let markerLastEventId = null;
89
+ let markerEventId = null;
90
+ let markerEventIndex = null;
91
+ let markerInvalid = false;
92
+ let hasMarker = false;
62
93
  if (shouldUseDeterminismMarker) {
63
- for (const event of events) {
94
+ for (const [index, event] of events.entries()) {
64
95
  if (event.eventType !== EventType.MARKER_RECORDED) {
65
96
  continue;
66
97
  }
@@ -72,36 +103,82 @@ export const ingestWorkflowHistory = (intake) => Effect.gen(function* () {
72
103
  details: event.attributes.value.details,
73
104
  }), () => Effect.succeed(undefined));
74
105
  if (decoded) {
75
- markerSnapshot = decoded;
106
+ const applied = applyDeterminismMarker(markerState, decoded);
107
+ if (!applied) {
108
+ markerInvalid = true;
109
+ break;
110
+ }
111
+ markerState = applied;
112
+ markerLastEventId = decoded.lastEventId;
113
+ markerEventId = normalizeEventId(event.eventId);
114
+ markerEventIndex = index;
115
+ hasMarker = true;
76
116
  }
77
117
  }
78
118
  }
79
119
  const extractedFailureMetadata = extractFailureMetadata(events);
80
- const replayUpdateInvocations = yield* Effect.tryPromise(async () => collectWorkflowUpdateInvocations(events, intake.dataConverter));
81
- if (shouldUseDeterminismMarker && markerSnapshot) {
82
- let determinismState = cloneDeterminismState(markerSnapshot.determinismState);
83
- if (!determinismState.failureMetadata && extractedFailureMetadata) {
84
- determinismState = {
85
- ...determinismState,
86
- failureMetadata: extractedFailureMetadata,
87
- };
120
+ const historyLastEventId = resolveHistoryLastEventId(events) ?? null;
121
+ if (shouldUseDeterminismMarker && hasMarker && !markerInvalid && markerState && markerEventIndex !== null) {
122
+ const historyKinds = collectCommandKinds(events.slice(0, markerEventIndex + 1));
123
+ const markerKinds = markerState.commandHistory.map((entry) => entry.intent.kind);
124
+ if (!commandKindsMatch(markerKinds, historyKinds)) {
125
+ markerInvalid = true;
126
+ markerState = undefined;
88
127
  }
89
- if ((!determinismState.updates || determinismState.updates.length === 0) && updateEntries.length > 0) {
90
- determinismState = {
91
- ...determinismState,
92
- updates: updateEntries,
128
+ }
129
+ const eventsAfterMarker = shouldUseDeterminismMarker && hasMarker && markerEventIndex !== null ? events.slice(markerEventIndex + 1) : events;
130
+ const replayUpdateInvocationsAfterMarker = yield* Effect.tryPromise(async () => collectWorkflowUpdateInvocations(eventsAfterMarker, intake.dataConverter));
131
+ const updateEntriesAfterMarker = collectWorkflowUpdateEntries(eventsAfterMarker);
132
+ const replayUpdateInvocationsFull = yield* Effect.tryPromise(async () => collectWorkflowUpdateInvocations(events, intake.dataConverter));
133
+ const updateEntriesFull = collectWorkflowUpdateEntries(events);
134
+ const markerCoversHistory = markerEventId !== null && markerEventId === historyLastEventId;
135
+ const historyCommandCount = countHistoryCommandEvents(events);
136
+ if (shouldUseDeterminismMarker && hasMarker && !markerInvalid && markerState) {
137
+ if (markerCoversHistory || eventsAfterMarker.length === 0) {
138
+ let determinismState = cloneDeterminismState(markerState);
139
+ if (!determinismState.failureMetadata && extractedFailureMetadata) {
140
+ determinismState = {
141
+ ...determinismState,
142
+ failureMetadata: extractedFailureMetadata,
143
+ };
144
+ }
145
+ if ((!determinismState.updates || determinismState.updates.length === 0) &&
146
+ updateEntriesAfterMarker.length > 0) {
147
+ determinismState = {
148
+ ...determinismState,
149
+ updates: updateEntriesAfterMarker,
150
+ };
151
+ }
152
+ determinismState = mergePendingQueryRequests(determinismState, intake.queries);
153
+ const latestEventId = historyLastEventId ?? markerLastEventId ?? null;
154
+ if (determinismState.commandHistory.length !== historyCommandCount) {
155
+ return yield* reconstructDeterminismState(events, intake, extractedFailureMetadata, updateEntriesFull, replayUpdateInvocationsFull);
156
+ }
157
+ return {
158
+ determinismState,
159
+ lastEventId: latestEventId,
160
+ hasDeterminismMarker: true,
161
+ markerState: cloneDeterminismState(markerState),
162
+ ...(replayUpdateInvocationsFull.length > 0 ? { updates: replayUpdateInvocationsFull } : {}),
93
163
  };
94
164
  }
95
- determinismState = mergePendingQueryRequests(determinismState, intake.queries);
96
- const latestEventId = resolveHistoryLastEventId(events) ?? markerSnapshot.lastEventId ?? null;
165
+ const replayed = yield* reconstructDeterminismState(eventsAfterMarker, intake, extractedFailureMetadata, updateEntriesAfterMarker, replayUpdateInvocationsAfterMarker, {
166
+ seedState: markerState,
167
+ historyLastEventId,
168
+ seedHistoryEvents: markerEventIndex !== null ? events.slice(0, markerEventIndex + 1) : undefined,
169
+ });
170
+ if (replayed.determinismState.commandHistory.length !== historyCommandCount) {
171
+ return yield* reconstructDeterminismState(events, intake, extractedFailureMetadata, updateEntriesFull, replayUpdateInvocationsFull);
172
+ }
97
173
  return {
98
- determinismState,
99
- lastEventId: latestEventId,
174
+ ...replayed,
100
175
  hasDeterminismMarker: true,
101
- ...(replayUpdateInvocations.length > 0 ? { updates: replayUpdateInvocations } : {}),
176
+ markerState: cloneDeterminismState(markerState),
177
+ lastEventId: historyLastEventId ?? markerLastEventId ?? null,
178
+ ...(replayUpdateInvocationsFull.length > 0 ? { updates: replayUpdateInvocationsFull } : {}),
102
179
  };
103
180
  }
104
- return yield* reconstructDeterminismState(events, intake, extractedFailureMetadata, updateEntries, replayUpdateInvocations);
181
+ return yield* reconstructDeterminismState(events, intake, extractedFailureMetadata, updateEntriesFull, replayUpdateInvocationsFull);
105
182
  });
106
183
  /**
107
184
  * Diff determinism state against freshly emitted intents to produce rich
@@ -220,6 +297,7 @@ export const cloneDeterminismState = (state) => ({
220
297
  })),
221
298
  randomValues: [...state.randomValues],
222
299
  timeValues: [...state.timeValues],
300
+ ...(state.logCount !== undefined ? { logCount: state.logCount } : {}),
223
301
  failureMetadata: state.failureMetadata ? { ...state.failureMetadata } : undefined,
224
302
  signals: state.signals ? state.signals.map((record) => ({ ...record })) : [],
225
303
  queries: state.queries ? state.queries.map((record) => ({ ...record })) : [],
@@ -384,14 +462,295 @@ const resolveCommandMismatchMetadata = (expectedEntry, actualEntry) => {
384
462
  eventType,
385
463
  };
386
464
  };
387
- const reconstructDeterminismState = (events, intake, failureMetadata, precomputedUpdates, replayUpdateInvocations = []) => Effect.gen(function* () {
388
- let sequence = 0;
389
- const commandHistory = [];
390
- const signalRecords = [];
391
- const queryRecords = [];
392
- const updates = precomputedUpdates ?? collectWorkflowUpdateEntries(events);
465
+ const seedDeterminismEventMaps = (state, seedEvents) => {
393
466
  const scheduledActivities = new Map();
394
467
  const timersByStartEventId = new Map();
468
+ if (!state) {
469
+ if (seedEvents) {
470
+ seedDeterminismEventMapsFromHistory(scheduledActivities, timersByStartEventId, seedEvents);
471
+ }
472
+ return { scheduledActivities, timersByStartEventId };
473
+ }
474
+ for (const entry of state.commandHistory) {
475
+ const eventId = entry.metadata?.eventId;
476
+ if (!eventId) {
477
+ continue;
478
+ }
479
+ if (entry.intent.kind === 'schedule-activity') {
480
+ scheduledActivities.set(eventId, entry.intent.activityId);
481
+ continue;
482
+ }
483
+ if (entry.intent.kind === 'start-timer') {
484
+ timersByStartEventId.set(eventId, entry.intent.timerId);
485
+ }
486
+ }
487
+ if (seedEvents) {
488
+ seedDeterminismEventMapsFromHistory(scheduledActivities, timersByStartEventId, seedEvents);
489
+ }
490
+ return { scheduledActivities, timersByStartEventId };
491
+ };
492
+ const seedDeterminismEventMapsFromHistory = (scheduledActivities, timersByStartEventId, events) => {
493
+ for (const event of events) {
494
+ switch (event.eventType) {
495
+ case EventType.ACTIVITY_TASK_SCHEDULED: {
496
+ if (event.attributes?.case !== 'activityTaskScheduledEventAttributes') {
497
+ break;
498
+ }
499
+ const activityId = event.attributes.value.activityId;
500
+ const eventId = normalizeEventId(event.eventId);
501
+ if (activityId && eventId && !scheduledActivities.has(eventId)) {
502
+ scheduledActivities.set(eventId, activityId);
503
+ }
504
+ break;
505
+ }
506
+ case EventType.TIMER_STARTED: {
507
+ if (event.attributes?.case !== 'timerStartedEventAttributes') {
508
+ break;
509
+ }
510
+ const timerId = event.attributes.value.timerId;
511
+ const eventId = normalizeEventId(event.eventId);
512
+ if (timerId && eventId && !timersByStartEventId.has(eventId)) {
513
+ timersByStartEventId.set(eventId, timerId);
514
+ }
515
+ break;
516
+ }
517
+ default:
518
+ break;
519
+ }
520
+ }
521
+ };
522
+ const countHistoryCommandEvents = (events) => {
523
+ const { scheduledActivities, timersByStartEventId } = seedDeterminismEventMaps(undefined, events);
524
+ let count = 0;
525
+ for (const event of events) {
526
+ switch (event.eventType) {
527
+ case EventType.ACTIVITY_TASK_SCHEDULED: {
528
+ if (event.attributes?.case !== 'activityTaskScheduledEventAttributes') {
529
+ break;
530
+ }
531
+ if (event.attributes.value.activityType?.name) {
532
+ count += 1;
533
+ }
534
+ break;
535
+ }
536
+ case EventType.TIMER_STARTED: {
537
+ if (event.attributes?.case !== 'timerStartedEventAttributes') {
538
+ break;
539
+ }
540
+ if (durationToMillis(event.attributes.value.startToFireTimeout) !== undefined) {
541
+ count += 1;
542
+ }
543
+ break;
544
+ }
545
+ case EventType.START_CHILD_WORKFLOW_EXECUTION_INITIATED: {
546
+ if (event.attributes?.case !== 'startChildWorkflowExecutionInitiatedEventAttributes') {
547
+ break;
548
+ }
549
+ if (event.attributes.value.workflowType?.name) {
550
+ count += 1;
551
+ }
552
+ break;
553
+ }
554
+ case EventType.ACTIVITY_TASK_CANCEL_REQUESTED: {
555
+ if (event.attributes?.case !== 'activityTaskCancelRequestedEventAttributes') {
556
+ break;
557
+ }
558
+ const scheduledEventId = normalizeBigintIdentifier(event.attributes.value.scheduledEventId);
559
+ if (scheduledEventId && scheduledActivities.has(scheduledEventId)) {
560
+ count += 1;
561
+ }
562
+ break;
563
+ }
564
+ case EventType.TIMER_CANCELED: {
565
+ if (event.attributes?.case !== 'timerCanceledEventAttributes') {
566
+ break;
567
+ }
568
+ const startedEventId = normalizeBigintIdentifier(event.attributes.value.startedEventId);
569
+ if (startedEventId && timersByStartEventId.has(startedEventId)) {
570
+ count += 1;
571
+ }
572
+ break;
573
+ }
574
+ case EventType.SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: {
575
+ if (event.attributes?.case !== 'signalExternalWorkflowExecutionInitiatedEventAttributes') {
576
+ break;
577
+ }
578
+ if (event.attributes.value.signalName) {
579
+ count += 1;
580
+ }
581
+ break;
582
+ }
583
+ case EventType.REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: {
584
+ if (event.attributes?.case !== 'requestCancelExternalWorkflowExecutionInitiatedEventAttributes') {
585
+ break;
586
+ }
587
+ count += 1;
588
+ break;
589
+ }
590
+ case EventType.WORKFLOW_EXECUTION_CONTINUED_AS_NEW: {
591
+ if (event.attributes?.case !== 'workflowExecutionContinuedAsNewEventAttributes') {
592
+ break;
593
+ }
594
+ count += 1;
595
+ break;
596
+ }
597
+ case EventType.MARKER_RECORDED: {
598
+ if (event.attributes?.case !== 'markerRecordedEventAttributes') {
599
+ break;
600
+ }
601
+ if (event.attributes.value.markerName !== DETERMINISM_MARKER_NAME) {
602
+ count += 1;
603
+ }
604
+ break;
605
+ }
606
+ case EventType.UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: {
607
+ if (event.attributes?.case !== 'upsertWorkflowSearchAttributesEventAttributes') {
608
+ break;
609
+ }
610
+ count += 1;
611
+ break;
612
+ }
613
+ case EventType.WORKFLOW_PROPERTIES_MODIFIED: {
614
+ if (event.attributes?.case !== 'workflowPropertiesModifiedEventAttributes') {
615
+ break;
616
+ }
617
+ count += 1;
618
+ break;
619
+ }
620
+ default:
621
+ break;
622
+ }
623
+ }
624
+ return count;
625
+ };
626
+ const commandKindsMatch = (expected, actual) => {
627
+ if (expected.length !== actual.length) {
628
+ return false;
629
+ }
630
+ for (let index = 0; index < expected.length; index += 1) {
631
+ if (expected[index] !== actual[index]) {
632
+ return false;
633
+ }
634
+ }
635
+ return true;
636
+ };
637
+ const collectCommandKinds = (events) => {
638
+ const { scheduledActivities, timersByStartEventId } = seedDeterminismEventMaps(undefined, events);
639
+ const kinds = [];
640
+ for (const event of events) {
641
+ switch (event.eventType) {
642
+ case EventType.ACTIVITY_TASK_SCHEDULED: {
643
+ if (event.attributes?.case !== 'activityTaskScheduledEventAttributes') {
644
+ break;
645
+ }
646
+ if (event.attributes.value.activityType?.name) {
647
+ kinds.push('schedule-activity');
648
+ }
649
+ break;
650
+ }
651
+ case EventType.TIMER_STARTED: {
652
+ if (event.attributes?.case !== 'timerStartedEventAttributes') {
653
+ break;
654
+ }
655
+ if (event.attributes.value.timerId) {
656
+ kinds.push('start-timer');
657
+ }
658
+ break;
659
+ }
660
+ case EventType.START_CHILD_WORKFLOW_EXECUTION_INITIATED: {
661
+ if (event.attributes?.case !== 'startChildWorkflowExecutionInitiatedEventAttributes') {
662
+ break;
663
+ }
664
+ if (event.attributes.value.workflowType?.name) {
665
+ kinds.push('start-child-workflow');
666
+ }
667
+ break;
668
+ }
669
+ case EventType.ACTIVITY_TASK_CANCEL_REQUESTED: {
670
+ if (event.attributes?.case !== 'activityTaskCancelRequestedEventAttributes') {
671
+ break;
672
+ }
673
+ const scheduledKey = normalizeEventId(event.attributes.value.scheduledEventId);
674
+ if (scheduledKey && scheduledActivities.has(scheduledKey)) {
675
+ kinds.push('request-cancel-activity');
676
+ }
677
+ break;
678
+ }
679
+ case EventType.TIMER_CANCELED: {
680
+ if (event.attributes?.case !== 'timerCanceledEventAttributes') {
681
+ break;
682
+ }
683
+ const startedKey = normalizeEventId(event.attributes.value.startedEventId);
684
+ if (startedKey && timersByStartEventId.has(startedKey)) {
685
+ kinds.push('cancel-timer');
686
+ }
687
+ break;
688
+ }
689
+ case EventType.SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: {
690
+ if (event.attributes?.case !== 'signalExternalWorkflowExecutionInitiatedEventAttributes') {
691
+ break;
692
+ }
693
+ if (event.attributes.value.signalName) {
694
+ kinds.push('signal-external-workflow');
695
+ }
696
+ break;
697
+ }
698
+ case EventType.REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: {
699
+ if (event.attributes?.case !== 'requestCancelExternalWorkflowExecutionInitiatedEventAttributes') {
700
+ break;
701
+ }
702
+ if (event.attributes.value.workflowExecution?.workflowId) {
703
+ kinds.push('request-cancel-external-workflow');
704
+ }
705
+ break;
706
+ }
707
+ case EventType.WORKFLOW_EXECUTION_CONTINUED_AS_NEW: {
708
+ if (event.attributes?.case !== 'workflowExecutionContinuedAsNewEventAttributes') {
709
+ break;
710
+ }
711
+ kinds.push('continue-as-new');
712
+ break;
713
+ }
714
+ case EventType.MARKER_RECORDED: {
715
+ if (event.attributes?.case !== 'markerRecordedEventAttributes') {
716
+ break;
717
+ }
718
+ if (event.attributes.value.markerName !== DETERMINISM_MARKER_NAME) {
719
+ kinds.push('record-marker');
720
+ }
721
+ break;
722
+ }
723
+ case EventType.UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: {
724
+ if (event.attributes?.case !== 'upsertWorkflowSearchAttributesEventAttributes') {
725
+ break;
726
+ }
727
+ kinds.push('upsert-search-attributes');
728
+ break;
729
+ }
730
+ case EventType.WORKFLOW_PROPERTIES_MODIFIED: {
731
+ if (event.attributes?.case !== 'workflowPropertiesModifiedEventAttributes') {
732
+ break;
733
+ }
734
+ kinds.push('modify-workflow-properties');
735
+ break;
736
+ }
737
+ default:
738
+ break;
739
+ }
740
+ }
741
+ return kinds;
742
+ };
743
+ const reconstructDeterminismState = (events, intake, failureMetadata, precomputedUpdates, replayUpdateInvocations = [], options) => Effect.gen(function* () {
744
+ const seededState = options?.seedState ? cloneDeterminismState(options.seedState) : undefined;
745
+ let sequence = seededState?.commandHistory.length ?? 0;
746
+ const commandHistory = seededState ? [...seededState.commandHistory] : [];
747
+ const signalRecords = seededState ? [...seededState.signals] : [];
748
+ const queryRecords = seededState ? [...seededState.queries] : [];
749
+ const updateEntries = precomputedUpdates ?? collectWorkflowUpdateEntries(events);
750
+ const updates = seededState?.updates && seededState.updates.length > 0
751
+ ? [...seededState.updates, ...updateEntries]
752
+ : updateEntries;
753
+ const { scheduledActivities, timersByStartEventId } = seedDeterminismEventMaps(seededState, options?.seedHistoryEvents);
395
754
  for (const event of events) {
396
755
  switch (event.eventType) {
397
756
  case EventType.ACTIVITY_TASK_SCHEDULED: {
@@ -582,11 +941,12 @@ const reconstructDeterminismState = (events, intake, failureMetadata, precompute
582
941
  break;
583
942
  }
584
943
  }
944
+ const resolvedFailureMetadata = failureMetadata ?? seededState?.failureMetadata;
585
945
  let determinismState = {
586
946
  commandHistory,
587
- randomValues: [],
588
- timeValues: [],
589
- ...(failureMetadata ? { failureMetadata } : {}),
947
+ randomValues: seededState?.randomValues ?? [],
948
+ timeValues: seededState?.timeValues ?? [],
949
+ ...(resolvedFailureMetadata ? { failureMetadata: resolvedFailureMetadata } : {}),
590
950
  signals: signalRecords,
591
951
  queries: queryRecords,
592
952
  ...(updates.length > 0 ? { updates } : {}),
@@ -594,7 +954,7 @@ const reconstructDeterminismState = (events, intake, failureMetadata, precompute
594
954
  determinismState = mergePendingQueryRequests(determinismState, intake.queries);
595
955
  return {
596
956
  determinismState,
597
- lastEventId: resolveHistoryLastEventId(events),
957
+ lastEventId: options?.historyLastEventId ?? resolveHistoryLastEventId(events),
598
958
  hasDeterminismMarker: false,
599
959
  ...(replayUpdateInvocations.length > 0 ? { updates: replayUpdateInvocations } : {}),
600
960
  };
@@ -891,7 +1251,19 @@ const collectWorkflowUpdateEntries = (events) => {
891
1251
  return updates;
892
1252
  };
893
1253
  const collectWorkflowUpdateInvocations = async (events, dataConverter) => {
894
- const invocations = [];
1254
+ const candidates = [];
1255
+ let order = 0;
1256
+ const parseSequencing = (value) => {
1257
+ if (!value) {
1258
+ return undefined;
1259
+ }
1260
+ try {
1261
+ return BigInt(value);
1262
+ }
1263
+ catch {
1264
+ return undefined;
1265
+ }
1266
+ };
895
1267
  for (const event of events) {
896
1268
  if (event.eventType === EventType.WORKFLOW_EXECUTION_UPDATE_ACCEPTED) {
897
1269
  if (event.attributes.case !== 'workflowExecutionUpdateAcceptedEventAttributes') {
@@ -907,25 +1279,8 @@ const collectWorkflowUpdateInvocations = async (events, dataConverter) => {
907
1279
  dataConverter,
908
1280
  });
909
1281
  if (invocation) {
910
- invocations.push(invocation);
911
- }
912
- continue;
913
- }
914
- if (event.eventType === EventType.WORKFLOW_EXECUTION_UPDATE_REJECTED) {
915
- if (event.attributes.case !== 'workflowExecutionUpdateRejectedEventAttributes') {
916
- continue;
917
- }
918
- const attrs = event.attributes.value;
919
- const invocation = await buildUpdateInvocationFromRequest({
920
- request: attrs.rejectedRequest,
921
- protocolInstanceId: attrs.protocolInstanceId,
922
- requestMessageId: attrs.rejectedRequestMessageId,
923
- sequencingEventId: attrs.rejectedRequestSequencingEventId,
924
- fallbackEventId: event.eventId,
925
- dataConverter,
926
- });
927
- if (invocation) {
928
- invocations.push(invocation);
1282
+ candidates.push({ invocation, sequencing: parseSequencing(invocation.sequencingEventId), order });
1283
+ order += 1;
929
1284
  }
930
1285
  continue;
931
1286
  }
@@ -943,11 +1298,59 @@ const collectWorkflowUpdateInvocations = async (events, dataConverter) => {
943
1298
  dataConverter,
944
1299
  });
945
1300
  if (invocation) {
946
- invocations.push(invocation);
1301
+ candidates.push({ invocation, sequencing: parseSequencing(invocation.sequencingEventId), order });
1302
+ order += 1;
947
1303
  }
948
1304
  }
949
1305
  }
950
- return invocations;
1306
+ const byUpdateId = new Map();
1307
+ for (const candidate of candidates) {
1308
+ const updateId = candidate.invocation.updateId;
1309
+ const existing = byUpdateId.get(updateId);
1310
+ if (!existing) {
1311
+ byUpdateId.set(updateId, candidate);
1312
+ continue;
1313
+ }
1314
+ const existingSeq = existing.sequencing;
1315
+ const candidateSeq = candidate.sequencing;
1316
+ if (candidateSeq !== undefined && existingSeq !== undefined) {
1317
+ if (candidateSeq < existingSeq) {
1318
+ byUpdateId.set(updateId, candidate);
1319
+ }
1320
+ continue;
1321
+ }
1322
+ if (candidateSeq !== undefined && existingSeq === undefined) {
1323
+ byUpdateId.set(updateId, candidate);
1324
+ continue;
1325
+ }
1326
+ if (candidateSeq === undefined && existingSeq !== undefined) {
1327
+ continue;
1328
+ }
1329
+ if (candidate.order < existing.order) {
1330
+ byUpdateId.set(updateId, candidate);
1331
+ }
1332
+ }
1333
+ return [...byUpdateId.values()]
1334
+ .sort((left, right) => {
1335
+ const leftSeq = left.sequencing;
1336
+ const rightSeq = right.sequencing;
1337
+ if (leftSeq !== undefined && rightSeq !== undefined) {
1338
+ if (leftSeq < rightSeq) {
1339
+ return -1;
1340
+ }
1341
+ if (leftSeq > rightSeq) {
1342
+ return 1;
1343
+ }
1344
+ }
1345
+ else if (leftSeq !== undefined) {
1346
+ return -1;
1347
+ }
1348
+ else if (rightSeq !== undefined) {
1349
+ return 1;
1350
+ }
1351
+ return left.order - right.order;
1352
+ })
1353
+ .map((entry) => entry.invocation);
951
1354
  };
952
1355
  const buildUpdateInvocationFromRequest = async (input) => {
953
1356
  const req = input.request;
@@ -1089,19 +1492,63 @@ const resolveOutcomeStatus = (outcome) => {
1089
1492
  }
1090
1493
  return undefined;
1091
1494
  };
1495
+ const applyDeterminismDelta = (state, delta) => ({
1496
+ ...state,
1497
+ commandHistory: [...state.commandHistory, ...(delta.commandHistory ?? [])],
1498
+ randomValues: [...state.randomValues, ...(delta.randomValues ?? [])],
1499
+ timeValues: [...state.timeValues, ...(delta.timeValues ?? [])],
1500
+ signals: [...state.signals, ...(delta.signals ?? [])],
1501
+ queries: [...state.queries, ...(delta.queries ?? [])],
1502
+ ...(delta.updates
1503
+ ? { updates: [...(state.updates ?? []), ...(delta.updates ?? [])] }
1504
+ : state.updates
1505
+ ? { updates: [...state.updates] }
1506
+ : {}),
1507
+ ...(delta.logCount !== undefined ? { logCount: delta.logCount } : {}),
1508
+ ...(delta.failureMetadata ? { failureMetadata: delta.failureMetadata } : {}),
1509
+ });
1510
+ const applyDeterminismMarker = (state, marker) => {
1511
+ if (marker.schemaVersion === 1) {
1512
+ return marker.determinismState;
1513
+ }
1514
+ const base = marker.determinismState ?? state;
1515
+ if (!base) {
1516
+ return undefined;
1517
+ }
1518
+ if (!marker.determinismDelta) {
1519
+ return base;
1520
+ }
1521
+ return applyDeterminismDelta(base, marker.determinismDelta);
1522
+ };
1092
1523
  const sanitizeDeterminismMarkerEnvelope = (input) => {
1093
1524
  const schemaVersion = input.schemaVersion;
1094
- if (schemaVersion !== DETERMINISM_MARKER_SCHEMA_VERSION) {
1525
+ if (schemaVersion !== DETERMINISM_MARKER_SCHEMA_VERSION_V1 &&
1526
+ schemaVersion !== DETERMINISM_MARKER_SCHEMA_VERSION_V2) {
1095
1527
  throw new Error(`Unsupported determinism marker schema version: ${String(schemaVersion)}`);
1096
1528
  }
1097
1529
  const workflow = sanitizeWorkflowInfo(input.workflow);
1098
- const determinismState = sanitizeDeterminismState(input.determinismState);
1099
1530
  const lastEventId = normalizeEventId(input.lastEventId);
1100
1531
  const recordedAt = sanitizeRecordedAt(input.recordedAtIso);
1532
+ if (schemaVersion === DETERMINISM_MARKER_SCHEMA_VERSION_V1) {
1533
+ const determinismState = sanitizeDeterminismState(input.determinismState);
1534
+ return {
1535
+ schemaVersion: DETERMINISM_MARKER_SCHEMA_VERSION_V1,
1536
+ workflow,
1537
+ determinismState,
1538
+ lastEventId,
1539
+ recordedAtIso: recordedAt,
1540
+ };
1541
+ }
1542
+ const determinismState = input.determinismState !== undefined ? sanitizeDeterminismState(input.determinismState) : undefined;
1543
+ const determinismDelta = input.determinismDelta !== undefined ? sanitizeDeterminismDelta(input.determinismDelta) : undefined;
1544
+ if (!determinismState && !determinismDelta) {
1545
+ throw new Error('Determinism marker missing determinism state or delta');
1546
+ }
1101
1547
  return {
1102
- schemaVersion: DETERMINISM_MARKER_SCHEMA_VERSION,
1548
+ schemaVersion: DETERMINISM_MARKER_SCHEMA_VERSION_V2,
1103
1549
  workflow,
1104
- determinismState,
1550
+ ...(determinismState ? { determinismState } : {}),
1551
+ ...(determinismDelta ? { determinismDelta } : {}),
1105
1552
  lastEventId,
1106
1553
  recordedAtIso: recordedAt,
1107
1554
  };
@@ -1125,6 +1572,7 @@ const sanitizeDeterminismState = (value) => {
1125
1572
  const commandHistoryRaw = value.commandHistory;
1126
1573
  const randomValuesRaw = value.randomValues;
1127
1574
  const timeValuesRaw = value.timeValues;
1575
+ const logCountRaw = value.logCount;
1128
1576
  const signalsRaw = Array.isArray(value.signals) ? value.signals : [];
1129
1577
  const queriesRaw = Array.isArray(value.queries) ? value.queries : [];
1130
1578
  const updatesRaw = value.updates;
@@ -1143,6 +1591,7 @@ const sanitizeDeterminismState = (value) => {
1143
1591
  });
1144
1592
  const randomValues = randomValuesRaw.map((val, index) => coerceNumber(val, `determinism.randomValues[${index}]`));
1145
1593
  const timeValues = timeValuesRaw.map((val, index) => coerceNumber(val, `determinism.timeValues[${index}]`));
1594
+ const logCount = coerceOptionalNumber(logCountRaw, 'determinism.logCount');
1146
1595
  const failureMetadata = sanitizeFailureMetadata(value.failureMetadata);
1147
1596
  const signals = signalsRaw.map((record, index) => sanitizeSignalRecord(record, index));
1148
1597
  const queries = queriesRaw.map((record, index) => sanitizeQueryRecord(record, index));
@@ -1151,12 +1600,40 @@ const sanitizeDeterminismState = (value) => {
1151
1600
  commandHistory,
1152
1601
  randomValues,
1153
1602
  timeValues,
1603
+ ...(logCount !== undefined ? { logCount } : {}),
1154
1604
  ...(failureMetadata ? { failureMetadata } : {}),
1155
1605
  signals,
1156
1606
  queries,
1157
1607
  ...(updates ? { updates } : {}),
1158
1608
  };
1159
1609
  };
1610
+ const sanitizeDeterminismDelta = (value) => {
1611
+ if (!isRecord(value)) {
1612
+ throw new Error('Determinism marker contained invalid determinism delta');
1613
+ }
1614
+ const commandHistoryRaw = value.commandHistory ?? [];
1615
+ const randomValuesRaw = value.randomValues ?? [];
1616
+ const timeValuesRaw = value.timeValues ?? [];
1617
+ const signalsRaw = value.signals ?? [];
1618
+ const queriesRaw = value.queries ?? [];
1619
+ if (!Array.isArray(commandHistoryRaw) ||
1620
+ !Array.isArray(randomValuesRaw) ||
1621
+ !Array.isArray(timeValuesRaw) ||
1622
+ !Array.isArray(signalsRaw) ||
1623
+ !Array.isArray(queriesRaw)) {
1624
+ throw new Error('Determinism marker contained invalid determinism delta shape');
1625
+ }
1626
+ return sanitizeDeterminismState({
1627
+ commandHistory: commandHistoryRaw,
1628
+ randomValues: randomValuesRaw,
1629
+ timeValues: timeValuesRaw,
1630
+ signals: signalsRaw,
1631
+ queries: queriesRaw,
1632
+ ...(value.updates !== undefined ? { updates: value.updates } : {}),
1633
+ ...(value.logCount !== undefined ? { logCount: value.logCount } : {}),
1634
+ ...(value.failureMetadata !== undefined ? { failureMetadata: value.failureMetadata } : {}),
1635
+ });
1636
+ };
1160
1637
  const sanitizeCommandMetadata = (value, index) => {
1161
1638
  if (value === undefined || value === null) {
1162
1639
  return undefined;