{"version":3,"file":"derive.mjs","names":[],"sources":["../../../../@mongez/atom/src/derive.ts"],"sourcesContent":["/**\n * @fileoverview Derived atoms.\n *\n * A derived atom holds a value computed from one or more other atoms.\n * Dependencies are auto-tracked: whichever atoms the compute function\n * reads via the `get` argument become dependencies. When any of those\n * change, the derived value recomputes and notifies its subscribers.\n *\n * Conceptually similar to Jotai's `atom(get => ...)` and MobX's\n * `computed`. Returns a normal `Atom<T>`, so every consumer pattern in\n * `@mongez/react-atom` (useValue, useState, watch, onChange, …) works.\n *\n * @example\n * ```ts\n * const first = createAtom({ key: \"first\", default: \"Ada\" });\n * const last  = createAtom({ key: \"last\",  default: \"Lovelace\" });\n *\n * const fullName = derive(\"fullName\", get => `${get(first)} ${get(last)}`);\n *\n * fullName.value;                 // \"Ada Lovelace\"\n * first.update(\"Grace\");\n * fullName.value;                 // \"Grace Lovelace\"\n * ```\n */\nimport { type EventSubscription } from \"@mongez/events\";\nimport { createAtom } from \"./atom\";\nimport type { Atom } from \"./types\";\n\n/**\n * The reader passed to a derive compute function. Calling `get(atom)`\n * registers that atom as a dependency and returns its current value.\n */\nexport type DeriveGetter = <V>(atom: Atom<V, any>) => V;\n\nexport type DeriveOptions = {\n  /**\n   * Skip the global `atoms` registry. Used by `AtomStore` clones — most\n   * consumers should leave this alone.\n   * @default true\n   */\n  register?: boolean;\n};\n\n/**\n * Create a derived atom.\n *\n * The compute function runs once eagerly on creation to seed the initial\n * value and to discover dependencies. After that, it re-runs every time\n * any tracked dependency changes.\n *\n * Conditional reads work: an `if` branch inside the compute function\n * that reads a different atom on a later run picks up the new dep and\n * drops the old one. This handles the \"dynamic dependency graph\" case\n * (e.g. `if (get(currentRoute) === \"users\") return get(usersAtom)`).\n *\n * Calling `update`, `silentUpdate`, `change`, `merge` directly on the\n * returned atom works but is discouraged — the next dependency change\n * will overwrite anything you wrote. Use a regular atom if you need\n * writable state.\n */\nexport function derive<T>(\n  key: string,\n  compute: (get: DeriveGetter) => T,\n  options: DeriveOptions = {},\n): Atom<T> {\n  /**\n   * Active subscriptions to dependencies, keyed by the source atom.\n   * Replaced wholesale on each recompute so dynamic dependency graphs\n   * don't accumulate stale subscriptions.\n   */\n  let depSubs = new Map<Atom<any>, EventSubscription>();\n\n  /**\n   * A scratch set used during a recompute to mark which atoms were\n   * touched on this run. After compute finishes we diff against\n   * `depSubs`, drop stale ones, and add new ones.\n   */\n  let trackedThisRun: Set<Atom<any>> | undefined;\n\n  const trackingGet: DeriveGetter = atom => {\n    if (trackedThisRun) trackedThisRun.add(atom);\n    return atom.value;\n  };\n\n  /**\n   * Recompute the derived value and reconcile the dependency set.\n   * Updates the derived atom via the normal `update` flow so all\n   * downstream subscribers see the change.\n   *\n   * Errors thrown inside `compute` are caught and re-thrown\n   * asynchronously: we don't want a single broken derivation to take\n   * down the atom-bus subscriber. The atom's previous value is kept.\n   */\n  const recompute = () => {\n    trackedThisRun = new Set();\n    let next: T;\n    try {\n      next = compute(trackingGet);\n    } catch (err) {\n      trackedThisRun = undefined;\n      // Surface the error without breaking the source-atom's update cycle.\n      queueMicrotask(() => {\n        throw err;\n      });\n      return;\n    }\n\n    // Reconcile: subscribe to newly-seen deps, drop deps no longer read.\n    const seen = trackedThisRun;\n    trackedThisRun = undefined;\n\n    for (const dep of seen) {\n      if (!depSubs.has(dep)) {\n        depSubs.set(dep, dep.onChange(recompute));\n      }\n    }\n    for (const [dep, sub] of depSubs) {\n      if (!seen.has(dep)) {\n        sub.unsubscribe();\n        depSubs.delete(dep);\n      }\n    }\n\n    // Push the new value through the standard update path. If the value\n    // is structurally unchanged the atom's update() will short-circuit\n    // for primitives; for objects we always send a new reference\n    // because `compute` builds one each call.\n    derivedAtom.update(next);\n  };\n\n  // Bootstrap: compute the initial value and the initial dep set.\n  trackedThisRun = new Set();\n  const initialValue = compute(trackingGet);\n  const seen = trackedThisRun;\n  trackedThisRun = undefined;\n\n  const derivedAtom = createAtom<T>(\n    {\n      key,\n      default: initialValue,\n    },\n    { register: options.register !== false },\n  );\n\n  // Wire up dependency subscriptions now that the derived atom exists.\n  for (const dep of seen) {\n    depSubs.set(dep, dep.onChange(recompute));\n  }\n\n  // Tear down dependency subs when the atom is destroyed so they don't\n  // outlive the consumer and leak memory.\n  derivedAtom.onDestroy(() => {\n    for (const sub of depSubs.values()) sub.unsubscribe();\n    depSubs.clear();\n  });\n\n  return derivedAtom;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,OACd,KACA,SACA,UAAyB,CAAC,GACjB;;;;;;CAMT,IAAI,0BAAU,IAAI,IAAkC;;;;;;CAOpD,IAAI;CAEJ,MAAM,eAA4B,SAAQ;EACxC,IAAI,gBAAgB,eAAe,IAAI,IAAI;EAC3C,OAAO,KAAK;CACd;;;;;;;;;;CAWA,MAAM,kBAAkB;EACtB,iCAAiB,IAAI,IAAI;EACzB,IAAI;EACJ,IAAI;GACF,OAAO,QAAQ,WAAW;EAC5B,SAAS,KAAK;GACZ,iBAAiB;GAEjB,qBAAqB;IACnB,MAAM;GACR,CAAC;GACD;EACF;EAGA,MAAM,OAAO;EACb,iBAAiB;EAEjB,KAAK,MAAM,OAAO,MAChB,IAAI,CAAC,QAAQ,IAAI,GAAG,GAClB,QAAQ,IAAI,KAAK,IAAI,SAAS,SAAS,CAAC;EAG5C,KAAK,MAAM,CAAC,KAAK,QAAQ,SACvB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG;GAClB,IAAI,YAAY;GAChB,QAAQ,OAAO,GAAG;EACpB;EAOF,YAAY,OAAO,IAAI;CACzB;CAGA,iCAAiB,IAAI,IAAI;CACzB,MAAM,eAAe,QAAQ,WAAW;CACxC,MAAM,OAAO;CACb,iBAAiB;CAEjB,MAAM,cAAc,WAClB;EACE;EACA,SAAS;CACX,GACA,EAAE,UAAU,QAAQ,aAAa,MAAM,CACzC;CAGA,KAAK,MAAM,OAAO,MAChB,QAAQ,IAAI,KAAK,IAAI,SAAS,SAAS,CAAC;CAK1C,YAAY,gBAAgB;EAC1B,KAAK,MAAM,OAAO,QAAQ,OAAO,GAAG,IAAI,YAAY;EACpD,QAAQ,MAAM;CAChB,CAAC;CAED,OAAO;AACT"}