{"version":3,"sources":["../src/index.ts","../src/types/index.ts"],"sourcesContent":["import type { WalkerOS } from '@walkeros/core';\nimport {\n  getMappingValue,\n  isArray,\n  isBoolean,\n  isObject,\n  isString,\n} from '@walkeros/core';\nimport mixpanel from 'mixpanel-browser';\nimport type {\n  Destination,\n  Env,\n  MixpanelSDK,\n  RuntimeState,\n  Settings,\n} from './types';\n\n// Types export - consumers can import as DestinationMixpanel.Settings etc.\nexport * as DestinationMixpanel from './types';\n\n/**\n * Resolve the Mixpanel SDK: use the caller-provided mock when available\n * (tests), otherwise fall back to the real mixpanel-browser singleton.\n * Matches @walkeros/web-destination-clarity and\n * @walkeros/server-destination-gcp (BigQuery).\n */\nfunction getMixpanel(env: Env | undefined): MixpanelSDK {\n  return env?.mixpanel ?? (mixpanel as unknown as MixpanelSDK);\n}\n\n/**\n * Resolve the distinctId from an identify mapping value and call\n * mixpanel.identify() only if the value changed since the last call.\n * Returns the updated lastIdentity snapshot.\n */\nfunction applyIdentify(\n  mp: MixpanelSDK,\n  resolved: Record<string, unknown>,\n  lastIdentity: RuntimeState['lastIdentity'] = {},\n): NonNullable<RuntimeState['lastIdentity']> {\n  const updated = { ...lastIdentity };\n  const distinctId = resolved.distinctId;\n  if (!isString(distinctId) || distinctId === '') return updated;\n  if (distinctId === lastIdentity.distinctId) return updated;\n  mp.identify(distinctId);\n  updated.distinctId = distinctId;\n  return updated;\n}\n\ntype PeopleObjectOp =\n  | 'set'\n  | 'set_once'\n  | 'increment'\n  | 'append'\n  | 'union'\n  | 'remove';\n\nconst PEOPLE_OBJECT_OPS: PeopleObjectOp[] = [\n  'set',\n  'set_once',\n  'increment',\n  'append',\n  'union',\n  'remove',\n];\n\n/**\n * Dispatch resolved people operations to the SDK. The resolved object\n * may contain any subset of the 8 ops.\n */\nfunction applyPeople(mp: MixpanelSDK, resolved: Record<string, unknown>): void {\n  for (const op of PEOPLE_OBJECT_OPS) {\n    const bag = resolved[op];\n    if (!isObject(bag)) continue;\n    if (Object.keys(bag).length === 0) continue;\n    // Typed dispatch - each op's first arg is a Record<string, unknown>.\n    (mp.people[op] as (props: Record<string, unknown>) => void)(\n      bag as Record<string, unknown>,\n    );\n  }\n\n  if (isArray(resolved.unset)) {\n    const names = (resolved.unset as unknown[]).filter((v): v is string =>\n      isString(v),\n    );\n    if (names.length > 0) mp.people.unset(names);\n  }\n\n  if (resolved.delete_user === true) {\n    mp.people.delete_user();\n  }\n}\n\ntype GroupObjectOp = 'set' | 'set_once' | 'union' | 'remove';\n\nconst GROUP_OBJECT_OPS: GroupObjectOp[] = [\n  'set',\n  'set_once',\n  'union',\n  'remove',\n];\n\nfunction applyGroupProfile(\n  mp: MixpanelSDK,\n  resolved: Record<string, unknown>,\n): void {\n  const { key, id } = resolved as { key?: unknown; id?: unknown };\n  if (!isString(key) || key === '' || !isString(id) || id === '') return;\n\n  const group = mp.get_group(key, id);\n\n  for (const op of GROUP_OBJECT_OPS) {\n    const bag = resolved[op];\n    if (!isObject(bag)) continue;\n    if (Object.keys(bag).length === 0) continue;\n    (group[op] as (props: Record<string, unknown>) => void)(\n      bag as Record<string, unknown>,\n    );\n  }\n\n  if (isArray(resolved.unset)) {\n    const names = (resolved.unset as unknown[]).filter((v): v is string =>\n      isString(v),\n    );\n    if (names.length > 0) group.unset(names);\n  }\n\n  if (resolved.delete === true) {\n    group.delete();\n  }\n}\n\nexport const destinationMixpanel: Destination = {\n  type: 'mixpanel',\n\n  config: {},\n\n  init({ config, env }) {\n    const settings = config.settings;\n    if (!settings?.apiKey) return false;\n\n    const mp = getMixpanel(env as Env | undefined);\n    // Destructure the walkerOS-specific keys out; the rest flow through to\n    // Mixpanel's Config.\n    const {\n      apiKey,\n      identify: _identify,\n      group: _group,\n      _state: _existingState,\n      ...mixpanelConfig\n    } = settings;\n\n    // Apply walkerOS-specific defaults: walkerOS sources handle page views\n    // and element captures, so we turn off Mixpanel's built-ins unless the\n    // user explicitly enables them.\n    const initConfig = {\n      track_pageview: false,\n      autocapture: false,\n      ...mixpanelConfig,\n    };\n\n    // Mixpanel's init() is synchronous (returns void).\n    mp.init(apiKey, initConfig);\n\n    // Initialize runtime state. push() mutates this in place on subsequent\n    // events to skip redundant identify() calls.\n    const _state: RuntimeState = { lastIdentity: {} };\n    return { ...config, settings: { ...settings, _state } };\n  },\n\n  async push(event, { config, rule, env, data, collector }) {\n    const mp = getMixpanel(env as Env | undefined);\n    const settings = (config.settings || {}) as Settings;\n    const mappingSettings = rule?.settings || {};\n    const state: RuntimeState = settings._state || {};\n\n    // 1. Reset - fires first so subsequent identity calls start clean.\n    if (mappingSettings.reset !== undefined) {\n      const resolved = isBoolean(mappingSettings.reset)\n        ? mappingSettings.reset\n        : await getMappingValue(event, mappingSettings.reset, { collector });\n      if (resolved) {\n        mp.reset();\n        state.lastIdentity = {};\n      }\n    }\n\n    // 2. Identity - rule-level override wins over destination-level.\n    const identifyMapping = mappingSettings.identify ?? settings.identify;\n    if (identifyMapping !== undefined) {\n      const resolved = await getMappingValue(event, identifyMapping, {\n        collector,\n      });\n      if (isObject(resolved)) {\n        state.lastIdentity = applyIdentify(\n          mp,\n          resolved as Record<string, unknown>,\n          state.lastIdentity,\n        );\n      }\n    }\n\n    // 3. People operations - fires zero or more mp.people.* calls.\n    if (mappingSettings.people !== undefined) {\n      const resolved = await getMappingValue(event, mappingSettings.people, {\n        collector,\n      });\n      if (isObject(resolved)) {\n        applyPeople(mp, resolved as Record<string, unknown>);\n      }\n    }\n\n    // 4. Group assignment - resolves to { key, id } and calls set_group.\n    const groupMapping = mappingSettings.group ?? settings.group;\n    if (groupMapping !== undefined) {\n      const resolved = await getMappingValue(event, groupMapping, {\n        collector,\n      });\n      if (isObject(resolved)) {\n        const { key, id } = resolved as { key?: unknown; id?: unknown };\n        if (\n          isString(key) &&\n          key !== '' &&\n          (isString(id) || isArray(id)) &&\n          id !== ''\n        ) {\n          mp.set_group(key, id as string | string[]);\n        }\n      }\n    }\n\n    // 5. Group profile - resolves to { key, id, set?, set_once?, ... }\n    if (mappingSettings.groupProfile !== undefined) {\n      const resolved = await getMappingValue(\n        event,\n        mappingSettings.groupProfile,\n        { collector },\n      );\n      if (isObject(resolved)) {\n        applyGroupProfile(mp, resolved as Record<string, unknown>);\n      }\n    }\n\n    // 6. Default track - unless the rule opts out via silent.\n    if (rule?.silent !== true) {\n      const eventName = isString(rule?.name) ? rule.name : event.name;\n      const properties = isObject(data)\n        ? (data as Record<string, unknown>)\n        : {};\n      mp.track(eventName, properties);\n    }\n\n    // Persist state mutations back onto config.settings.\n    settings._state = state;\n  },\n\n  on(type, context) {\n    if (type !== 'consent' || !context.data) return;\n    const mp = getMixpanel(context.env as Env | undefined);\n\n    const consent = context.data as WalkerOS.Consent;\n    // Derive the consent keys from config.consent - iterate every key the\n    // destination declared as required. If ALL required keys are granted,\n    // opt IN; otherwise opt OUT.\n    const required = context.config?.consent;\n    if (!required || Object.keys(required).length === 0) return;\n\n    const allGranted = Object.keys(required).every(\n      (key) => consent[key] === true,\n    );\n    if (allGranted) {\n      mp.opt_in_tracking();\n    } else {\n      mp.opt_out_tracking();\n    }\n  },\n\n  destroy({ config, env }) {\n    const mp = getMixpanel(env as Env | undefined);\n    // Mixpanel has no public flush API. stop_batch_senders() halts the\n    // in-memory batcher; any already-queued events rely on the SDK's\n    // internal unload handler (sendBeacon on page hide).\n    mp.stop_batch_senders?.();\n    const settings = config?.settings;\n    if (settings?._state) settings._state = { lastIdentity: {} };\n  },\n};\n\nexport default destinationMixpanel;\n","import type {\n  Mapping as WalkerOSMapping,\n  Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationWeb } from '@walkeros/web-core';\n// mixpanel-browser's Config interface covers every init option (api_host,\n// persistence, batch_requests, record_sessions_percent, etc.). Extending it\n// directly keeps IntelliSense complete and prevents drift from the SDK.\nimport type { Config as MixpanelConfig } from 'mixpanel-browser';\n\n/**\n * Destination-level settings.\n *\n * Extends Mixpanel's `Config` so every `mixpanel.init()` option flows through\n * without per-field plumbing. The destination adds walkerOS-specific keys:\n *  - `apiKey` (required) — maps to the first arg of `mixpanel.init()`\n *  - `identify` — destination-level identity mapping\n *  - `include` — event sections flattened into `track()` properties\n *  - `group` — destination-level group association\n *  - `_state` — runtime state (not user-facing, mutated by init/push)\n */\nexport interface Settings extends Partial<MixpanelConfig> {\n  apiKey: string;\n  identify?: WalkerOSMapping.Value;\n  group?: WalkerOSMapping.Value;\n  /** Runtime state — populated by init() and mutated by push(). Not user-facing. */\n  _state?: RuntimeState;\n}\n\nexport interface RuntimeState {\n  /** Last-set distinct_id, used to skip redundant identify() calls. */\n  lastIdentity?: {\n    distinctId?: string;\n  };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Per-rule mapping settings. Every feature here is a walkerOS mapping value\n * resolved via getMappingValue(). Keys follow Mixpanel's native method names:\n *  - `identify` → `mixpanel.identify(distinctId)`\n *  - `people` → `mixpanel.people.{set,set_once,increment,append,union,remove,unset,delete_user}`\n *  - `group` → `mixpanel.set_group(key, id)`\n *  - `groupProfile` → `mixpanel.get_group(key, id).{set,set_once,unset,union,remove,delete}`\n *  - `reset` → `mixpanel.reset()`\n */\nexport interface Mapping {\n  identify?: WalkerOSMapping.Value;\n  people?: WalkerOSMapping.Value;\n  group?: WalkerOSMapping.Value;\n  groupProfile?: WalkerOSMapping.Value;\n  reset?: WalkerOSMapping.Value | boolean;\n}\n\n/**\n * The `people` namespace on the mixpanel singleton. Mirrors the real SDK\n * shape so tests can spy each method individually.\n */\nexport interface MixpanelPeople {\n  set: (\n    prop: string | Record<string, unknown>,\n    to?: unknown,\n    callback?: () => void,\n  ) => void;\n  set_once: (\n    prop: string | Record<string, unknown>,\n    to?: unknown,\n    callback?: () => void,\n  ) => void;\n  increment: (\n    prop: string | Record<string, number>,\n    by?: number,\n    callback?: () => void,\n  ) => void;\n  append: (\n    prop: string | Record<string, unknown>,\n    value?: unknown,\n    callback?: () => void,\n  ) => void;\n  union: (\n    prop: string | Record<string, unknown[]>,\n    value?: unknown[],\n    callback?: () => void,\n  ) => void;\n  remove: (\n    prop: string | Record<string, unknown>,\n    value?: unknown,\n    callback?: () => void,\n  ) => void;\n  unset: (prop: string | string[], callback?: () => void) => void;\n  delete_user: () => void;\n}\n\n/**\n * The handle returned by `mixpanel.get_group(key, id)`. Supports property\n * operations on the group profile.\n */\nexport interface MixpanelGroup {\n  set: (\n    prop: string | Record<string, unknown>,\n    to?: unknown,\n    callback?: () => void,\n  ) => void;\n  set_once: (\n    prop: string | Record<string, unknown>,\n    to?: unknown,\n    callback?: () => void,\n  ) => void;\n  unset: (prop: string | string[], callback?: () => void) => void;\n  union: (\n    prop: string | Record<string, unknown[]>,\n    value?: unknown[],\n    callback?: () => void,\n  ) => void;\n  remove: (\n    prop: string | Record<string, unknown>,\n    value?: unknown,\n    callback?: () => void,\n  ) => void;\n  delete: () => void;\n}\n\n/**\n * Mixpanel SDK surface — the subset of `mixpanel-browser` the destination\n * actually uses. Mirrors the singleton's shape so tests can mock the whole\n * object via env.mixpanel.\n */\nexport interface MixpanelSDK {\n  init: (\n    token: string,\n    config?: Partial<MixpanelConfig>,\n    name?: string,\n  ) => void;\n  track: (\n    event: string,\n    properties?: Record<string, unknown>,\n    callback?: () => void,\n  ) => void;\n  identify: (distinctId?: string) => void;\n  reset: () => void;\n  set_group: (\n    groupKey: string,\n    groupIds: string | string[],\n    callback?: () => void,\n  ) => void;\n  get_group: (groupKey: string, groupId: string) => MixpanelGroup;\n  opt_in_tracking: (options?: Record<string, unknown>) => void;\n  opt_out_tracking: (options?: Record<string, unknown>) => void;\n  stop_batch_senders?: () => void;\n  people: MixpanelPeople;\n}\n\n/**\n * Env — optional SDK override. Production leaves this undefined and the\n * destination falls back to the real `mixpanel-browser` default export.\n * Tests provide a mock via env.mixpanel = { ... }.\n */\nexport interface Env extends DestinationWeb.Env {\n  mixpanel?: MixpanelSDK;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport type Destination = DestinationWeb.Destination<Types>;\nexport type Config = DestinationWeb.Config<Types>;\n\nexport interface MixpanelDestination extends Destination {\n  env?: Env;\n}\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n"],"mappings":"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","names":[]}