@xstate-devtools/adapter 0.1.0 → 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 +29 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate-devtools/adapter",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
package/src/core.ts CHANGED
@@ -569,8 +569,13 @@ 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
- actorRefs.set(sessionId, actorRef)
573
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.
574
579
  const actorLogic = (actorRef as { logic?: unknown }).logic as any
575
580
  const machine = actorLogic?.root
576
581
  ? serializeMachine(
@@ -578,8 +583,31 @@ export function createInspector(
578
583
  actorLogic.config?.__xstateDevtoolsSource ?? getSourceLocation(source, options),
579
584
  )
580
585
  : null
586
+ actorRefs.set(sessionId, actorRef)
581
587
  actorMachines.set(sessionId, machine)
582
588
 
589
+ // Eagerly remove the actor from both maps when it stops so we don't
590
+ // accumulate strong references to every short-lived actor indefinitely.
591
+ // Without this, actors that stop silently (no further snapshot/event) are
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
+ }
605
+ try {
606
+ actorRef.subscribe({ complete: notifyStop, error: notifyStop })
607
+ } catch {
608
+ // subscribe is best-effort; older actor implementations may not support it
609
+ }
610
+
583
611
  const message: PageToExtensionMessage = {
584
612
  type: 'XSTATE_ACTOR_REGISTERED',
585
613
  sessionId: tag(sessionId),