{"version":3,"file":"devtools.mjs","names":[],"sources":["../../../../@mongez/atom/src/devtools.ts"],"sourcesContent":["/**\n * @fileoverview Redux DevTools bridge for `@mongez/atom`.\n *\n * Opt-in, browser-only, zero-cost when not enabled (tree-shaken if you\n * never import `enableAtomDevtools`).\n *\n * Pipes every atom update into the Redux DevTools extension so you get:\n *\n *   - A live list of every registered atom and its current value.\n *   - A timeline of updates with diffs.\n *   - Time-travel: jumping back in the timeline restores the matching\n *     state via `silentUpdate` on every atom.\n *\n * @example\n * ```ts\n * // app entry, dev only\n * if (process.env.NODE_ENV !== \"production\") {\n *   enableAtomDevtools({ name: \"MyApp\" });\n * }\n * ```\n */\nimport events from \"@mongez/events\";\nimport { atoms } from \"./atom\";\nimport type { Atom } from \"./types\";\n\n/**\n * Subset of the Redux DevTools instance API we use. Typed locally so we\n * don't need to take a dep on `@redux-devtools/extension`.\n */\ntype DevtoolsInstance = {\n  init(state: unknown): void;\n  send(action: { type: string; payload?: unknown }, state: unknown): void;\n  subscribe(\n    listener: (message: {\n      type: string;\n      payload?: { type?: string };\n      state?: string;\n    }) => void,\n  ): () => void;\n  disconnect?(): void;\n};\n\ntype DevtoolsExtension = {\n  connect(options?: {\n    name?: string;\n    features?: Record<string, unknown>;\n  }): DevtoolsInstance;\n};\n\nexport type EnableDevtoolsOptions = {\n  /** Label shown in the extension UI. */\n  name?: string;\n  /**\n   * Skip atoms whose key matches any of these patterns. Useful for\n   * silencing high-frequency atoms (mouse position, scroll, etc.) that\n   * would otherwise spam the timeline.\n   */\n  ignore?: Array<RegExp | string>;\n  /**\n   * How often (ms) to look for newly-registered atoms. Apps that\n   * register every atom at startup never need this; the default of\n   * 1000ms is fine for hot-reload and code-splitting cases.\n   * @default 1000\n   */\n  scanInterval?: number;\n};\n\n/**\n * Connect every registered atom to the Redux DevTools extension.\n *\n * Returns a teardown function that disconnects and stops the registry\n * scan. Safe to call when the extension isn't installed — it returns\n * a no-op teardown without doing any work.\n */\nexport function enableAtomDevtools(\n  options: EnableDevtoolsOptions = {},\n): () => void {\n  const win =\n    typeof window !== \"undefined\" ? (window as unknown as Window & {\n      __REDUX_DEVTOOLS_EXTENSION__?: DevtoolsExtension;\n    }) : undefined;\n\n  const ext = win?.__REDUX_DEVTOOLS_EXTENSION__;\n  if (!ext) return () => {};\n\n  const devtools = ext.connect({\n    name: options.name ?? \"@mongez/atom\",\n  });\n\n  const ignored = (key: string): boolean =>\n    !!options.ignore?.some(pat =>\n      typeof pat === \"string\" ? pat === key : pat.test(key),\n    );\n\n  const snapshot = (): Record<string, unknown> => {\n    const out: Record<string, unknown> = {};\n    for (const [k, a] of Object.entries(atoms)) {\n      if (!ignored(k)) out[k] = a.value;\n    }\n    return out;\n  };\n\n  devtools.init(snapshot());\n\n  // Per-atom subscriptions we own (so we can tear them down).\n  const subscribed = new Map<string, () => void>();\n\n  const subscribeAtom = (atom: Atom<any>) => {\n    if (ignored(atom.key)) return;\n    if (subscribed.has(atom.key)) return;\n    const sub = atom.onChange(newValue => {\n      devtools.send(\n        { type: `${atom.key}/update`, payload: newValue },\n        snapshot(),\n      );\n    });\n    const onResetSub = atom.onReset(() => {\n      devtools.send({ type: `${atom.key}/reset` }, snapshot());\n    });\n    const onDestroySub = atom.onDestroy(() => {\n      devtools.send({ type: `${atom.key}/destroy` }, snapshot());\n      // Drop our own subscription bookkeeping.\n      subscribed.delete(atom.key);\n    });\n    subscribed.set(atom.key, () => {\n      sub.unsubscribe();\n      onResetSub.unsubscribe();\n      onDestroySub.unsubscribe();\n    });\n  };\n\n  for (const atom of Object.values(atoms)) subscribeAtom(atom);\n\n  // Pick up atoms registered AFTER enableAtomDevtools fires. Apps that\n  // import all their atoms at boot will never trigger this; lazy-loaded\n  // routes that register atoms on demand will. Poll-based for simplicity.\n  const interval = options.scanInterval ?? 1000;\n  const scanTimer: ReturnType<typeof setInterval> = setInterval(() => {\n    for (const atom of Object.values(atoms)) subscribeAtom(atom);\n  }, interval);\n\n  // Time-travel: when the user jumps to a snapshot in the extension,\n  // restore every atom's value via silentUpdate so consumers see the\n  // restored state on the next read.\n  const unsubDevtools = devtools.subscribe(message => {\n    if (message.type !== \"DISPATCH\") return;\n    const payloadType = message.payload?.type;\n    if (\n      payloadType !== \"JUMP_TO_STATE\" &&\n      payloadType !== \"JUMP_TO_ACTION\"\n    ) {\n      return;\n    }\n    if (!message.state) return;\n    let restored: Record<string, unknown>;\n    try {\n      restored = JSON.parse(message.state);\n    } catch {\n      return;\n    }\n    for (const [key, value] of Object.entries(restored)) {\n      const atom = atoms[key];\n      if (!atom) continue;\n      atom.silentUpdate(value);\n      // Synthesise an update event so React subscribers re-render.\n      events.trigger(`atoms.${key}.update`, value, atom.currentValue, atom);\n    }\n  });\n\n  return () => {\n    clearInterval(scanTimer);\n    for (const unsub of subscribed.values()) unsub();\n    subscribed.clear();\n    unsubDevtools();\n    devtools.disconnect?.();\n  };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,mBACd,UAAiC,CAAC,GACtB;CAMZ,MAAM,OAJJ,OAAO,WAAW,cAAe,SAE5B,SAEU;CACjB,IAAI,CAAC,KAAK,aAAa,CAAC;CAExB,MAAM,WAAW,IAAI,QAAQ,EAC3B,MAAM,QAAQ,QAAQ,eACxB,CAAC;CAED,MAAM,WAAW,QACf,CAAC,CAAC,QAAQ,QAAQ,MAAK,QACrB,OAAO,QAAQ,WAAW,QAAQ,MAAM,IAAI,KAAK,GAAG,CACtD;CAEF,MAAM,iBAA0C;EAC9C,MAAM,MAA+B,CAAC;EACtC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,GACvC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,KAAK,EAAE;EAE9B,OAAO;CACT;CAEA,SAAS,KAAK,SAAS,CAAC;CAGxB,MAAM,6BAAa,IAAI,IAAwB;CAE/C,MAAM,iBAAiB,SAAoB;EACzC,IAAI,QAAQ,KAAK,GAAG,GAAG;EACvB,IAAI,WAAW,IAAI,KAAK,GAAG,GAAG;EAC9B,MAAM,MAAM,KAAK,UAAS,aAAY;GACpC,SAAS,KACP;IAAE,MAAM,GAAG,KAAK,IAAI;IAAU,SAAS;GAAS,GAChD,SAAS,CACX;EACF,CAAC;EACD,MAAM,aAAa,KAAK,cAAc;GACpC,SAAS,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,QAAQ,GAAG,SAAS,CAAC;EACzD,CAAC;EACD,MAAM,eAAe,KAAK,gBAAgB;GACxC,SAAS,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,GAAG,SAAS,CAAC;GAEzD,WAAW,OAAO,KAAK,GAAG;EAC5B,CAAC;EACD,WAAW,IAAI,KAAK,WAAW;GAC7B,IAAI,YAAY;GAChB,WAAW,YAAY;GACvB,aAAa,YAAY;EAC3B,CAAC;CACH;CAEA,KAAK,MAAM,QAAQ,OAAO,OAAO,KAAK,GAAG,cAAc,IAAI;CAK3D,MAAM,WAAW,QAAQ,gBAAgB;CACzC,MAAM,YAA4C,kBAAkB;EAClE,KAAK,MAAM,QAAQ,OAAO,OAAO,KAAK,GAAG,cAAc,IAAI;CAC7D,GAAG,QAAQ;CAKX,MAAM,gBAAgB,SAAS,WAAU,YAAW;EAClD,IAAI,QAAQ,SAAS,YAAY;EACjC,MAAM,cAAc,QAAQ,SAAS;EACrC,IACE,gBAAgB,mBAChB,gBAAgB,kBAEhB;EAEF,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI;EACJ,IAAI;GACF,WAAW,KAAK,MAAM,QAAQ,KAAK;EACrC,QAAQ;GACN;EACF;EACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAAG;GACnD,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,MAAM;GACX,KAAK,aAAa,KAAK;GAEvB,OAAO,QAAQ,SAAS,IAAI,UAAU,OAAO,KAAK,cAAc,IAAI;EACtE;CACF,CAAC;CAED,aAAa;EACX,cAAc,SAAS;EACvB,KAAK,MAAM,SAAS,WAAW,OAAO,GAAG,MAAM;EAC/C,WAAW,MAAM;EACjB,cAAc;EACd,SAAS,aAAa;CACxB;AACF"}