@xstate-devtools/adapter 0.1.1 → 0.1.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/core.ts +28 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate-devtools/adapter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
package/src/core.ts CHANGED
@@ -569,44 +569,45 @@ export function createInspector(
569
569
  if (inspectionEvent.type === '@xstate.actor') {
570
570
  const actorRef: AnyActorRef = inspectionEvent.actorRef
571
571
  const sessionId: string = actorRef.sessionId
572
+
573
+ // Serialize machine and populate both Maps BEFORE subscribing so that
574
+ // the Maps are fully consistent if complete()/error() ever fire
575
+ // synchronously during subscribe() (e.g. an actor that starts in a
576
+ // final state). Populating after subscribe introduced a race where
577
+ // actorMachines.delete() in the callback was a no-op and the subsequent
578
+ // actorMachines.set() would leak the entry permanently.
579
+ const actorLogic = (actorRef as { logic?: unknown }).logic as any
580
+ const machine = actorLogic?.root
581
+ ? serializeMachine(
582
+ actorLogic,
583
+ actorLogic.config?.__xstateDevtoolsSource ?? getSourceLocation(source, options),
584
+ )
585
+ : null
572
586
  actorRefs.set(sessionId, actorRef)
587
+ actorMachines.set(sessionId, machine)
573
588
 
574
589
  // Eagerly remove the actor from both maps when it stops so we don't
575
590
  // accumulate strong references to every short-lived actor indefinitely.
576
591
  // Without this, actors that stop silently (no further snapshot/event) are
577
592
  // only removed by checkAndNotifyStop — which never fires for them.
593
+ const notifyStop = () => {
594
+ if (actorRefs.has(sessionId)) {
595
+ actorRefs.delete(sessionId)
596
+ actorMachines.delete(sessionId)
597
+ const stopMsg: PageToExtensionMessage = {
598
+ type: 'XSTATE_ACTOR_STOPPED',
599
+ sessionId: tag(sessionId),
600
+ }
601
+ debugLog(source, 'actor stopped; notifying transport', summarizeMessage(stopMsg))
602
+ transport.send(stopMsg)
603
+ }
604
+ }
578
605
  try {
579
- actorRef.subscribe({
580
- complete: () => {
581
- if (actorRefs.has(sessionId)) {
582
- actorRefs.delete(sessionId)
583
- actorMachines.delete(sessionId)
584
- const stopMsg: PageToExtensionMessage = {
585
- type: 'XSTATE_ACTOR_STOPPED',
586
- sessionId: tag(sessionId),
587
- }
588
- debugLog(source, 'actor completed; notifying transport', summarizeMessage(stopMsg))
589
- transport.send(stopMsg)
590
- }
591
- },
592
- error: () => {
593
- actorRefs.delete(sessionId)
594
- actorMachines.delete(sessionId)
595
- },
596
- })
606
+ actorRef.subscribe({ complete: notifyStop, error: notifyStop })
597
607
  } catch {
598
608
  // subscribe is best-effort; older actor implementations may not support it
599
609
  }
600
610
 
601
- const actorLogic = (actorRef as { logic?: unknown }).logic as any
602
- const machine = actorLogic?.root
603
- ? serializeMachine(
604
- actorLogic,
605
- actorLogic.config?.__xstateDevtoolsSource ?? getSourceLocation(source, options),
606
- )
607
- : null
608
- actorMachines.set(sessionId, machine)
609
-
610
611
  const message: PageToExtensionMessage = {
611
612
  type: 'XSTATE_ACTOR_REGISTERED',
612
613
  sessionId: tag(sessionId),