{"version":3,"sources":["/home/runner/work/flags/flags/packages/adapter-posthog/dist/index.cjs","../src/index.ts","../src/provider/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/flags/flags/packages/adapter-posthog/dist/index.cjs","sourcesContent":[null,"import type { Adapter } from 'flags';\nimport { PostHog, type PostHogOptions } from 'posthog-node';\nimport type { JsonType, PostHogAdapter, PostHogEntities } from './types';\n\nexport { getProviderData } from './provider';\nexport type { PostHogEntities, JsonType };\n\ntype FlagEvaluations = Awaited<ReturnType<PostHog['evaluateFlags']>>;\n\n// Builds an adapter (with a single-flag `decide` and a batched `bulkDecide`)\n// around one way of reading a value out of an `evaluateFlags` snapshot. The\n// value adapter reads `getFlag`, the payload adapter reads `getFlagPayload`.\nfunction createFlagAdapter(\n  client: PostHog,\n  adapterId: symbol,\n  read: (snapshot: FlagEvaluations, key: string) => unknown,\n  isPresent: (value: unknown) => boolean,\n  noun: 'value' | 'payload',\n): Adapter<unknown, PostHogEntities> {\n  return {\n    adapterId,\n    async decide({ key, entities, defaultValue }) {\n      const { distinctId } = parseEntities(entities);\n      const snapshot = await client.evaluateFlags(distinctId, {\n        flagKeys: [key],\n      });\n      const value = read(snapshot, key);\n      if (!isPresent(value)) {\n        if (typeof defaultValue !== 'undefined') return defaultValue;\n        throw new Error(\n          `PostHog Adapter found no ${noun} for ${key} and no default value was provided.`,\n        );\n      }\n      return value;\n    },\n    async bulkDecide({ flags, entities }) {\n      const { distinctId } = parseEntities(entities);\n      const snapshot = await client.evaluateFlags(distinctId, {\n        flagKeys: flags.map(({ key }) => key),\n      });\n      const out: Record<string, unknown> = {};\n      for (const { key } of flags) {\n        const value = read(snapshot, key);\n        // Omit absent values so the SDK applies each flag's `defaultValue`.\n        if (isPresent(value)) out[key] = value;\n      }\n      return out;\n    },\n  };\n}\n\nexport function createPostHogAdapter({\n  postHogKey,\n  postHogOptions,\n}: {\n  postHogKey: string;\n  postHogOptions: PostHogOptions;\n}): PostHogAdapter {\n  const client = new PostHog(postHogKey, postHogOptions);\n\n  // Stable identities captured in this closure so every adapter object the\n  // factories below hand out shares them. `evaluate()` batches flags whose\n  // adapters share an `adapterId` (and `identify` source) into a single\n  // `evaluateFlags` request. Value and payload need separate ids because\n  // `bulkDecide` returns one value per key and can't tell from the key alone\n  // whether the caller wanted the flag value or its payload.\n  const valueAdapter = createFlagAdapter(\n    client,\n    Symbol('postHogAdapter.value'),\n    // `getFlag` returns `false` for a disabled flag (a real value) and\n    // `undefined` only when the flag was not returned by the evaluation.\n    (snapshot, key) => snapshot.getFlag(key),\n    (value) => value !== undefined,\n    'value',\n  );\n\n  const payloadAdapter = createFlagAdapter(\n    client,\n    Symbol('postHogAdapter.payload'),\n    (snapshot, key) => snapshot.getFlagPayload(key),\n    (value) => Boolean(value),\n    'payload',\n  );\n\n  const adapter = (<ValueType>() =>\n    valueAdapter as Adapter<ValueType, PostHogEntities>) as PostHogAdapter;\n  adapter.payload = <ValueType>() =>\n    payloadAdapter as Adapter<ValueType, PostHogEntities>;\n\n  return adapter;\n}\n\nfunction parseEntities(entities?: PostHogEntities): PostHogEntities {\n  if (!entities) {\n    throw new Error(\n      'PostHog Adapter: Missing entities, ' +\n        'flag must be defined with an identify() function.',\n    );\n  }\n  return entities;\n}\n\nfunction assertEnv(name: string): string {\n  const value = process.env[name];\n  if (!value) {\n    throw new Error(`PostHog Adapter: Missing ${name} environment variable`);\n  }\n  return value;\n}\n\nlet defaultPostHogAdapter: PostHogAdapter | undefined;\n\n/**\n * Internal function for testing purposes\n */\nexport function resetDefaultPostHogAdapter() {\n  defaultPostHogAdapter = undefined;\n}\n\nfunction getOrCreateDefaultAdapter(): PostHogAdapter {\n  if (!defaultPostHogAdapter) {\n    // Evaluation mode is an explicit choice, not a side effect of any other\n    // credential. Local evaluation is opt-in via POSTHOG_SECRET_KEY (a `phs_`\n    // project secret key). When it is set, posthog-node polls flag definitions\n    // and evaluates flags in-process; otherwise flags are evaluated remotely.\n    //\n    // Note: POSTHOG_PERSONAL_API_KEY is only used by getProviderData (Flags\n    // Explorer discovery) and is intentionally never passed to the runtime\n    // client, so it does not enable local-evaluation polling.\n    const secretKey = process.env.POSTHOG_SECRET_KEY;\n\n    defaultPostHogAdapter = createPostHogAdapter({\n      postHogKey: assertEnv('POSTHOG_PROJECT_API_KEY'),\n      postHogOptions: {\n        host: assertEnv('POSTHOG_HOST'),\n        secretKey,\n        enableLocalEvaluation: Boolean(secretKey),\n        // Presumption: Server IP is likely not a good proxy for user location\n        disableGeoip: true,\n      },\n    });\n  }\n  return defaultPostHogAdapter;\n}\n\n/**\n * The default PostHog adapter, initialized lazily from environment variables on\n * first use. Pass it uninvoked (`adapter: postHogAdapter`) or invoked\n * (`adapter: postHogAdapter()`) to read a flag's value, or use\n * `postHogAdapter.payload` to read the flag's attached payload.\n */\nexport const postHogAdapter: PostHogAdapter = Object.assign(\n  <ValueType>() => getOrCreateDefaultAdapter()<ValueType>(),\n  {\n    payload: <ValueType>() => getOrCreateDefaultAdapter().payload<ValueType>(),\n  },\n);\n","import type { FlagDefinitionsType, ProviderData } from 'flags';\n\n// See: https://posthog.com/docs/api/feature-flags#get-api-projects-project_id-feature_flags\ninterface ApiData {\n  count: number;\n  next?: string | null;\n  previous?: string | null;\n  results: {\n    id: number;\n    key: string;\n    name: string;\n    created_at: string;\n    description: string;\n    deleted: boolean;\n    active: boolean;\n    is_simple_flag: boolean;\n    filters: {\n      payloads?: Record<string, string>;\n      multivariate?: Record<string, unknown>;\n    };\n  }[];\n}\n\nexport async function getProviderData(options: {\n  personalApiKey: string;\n  projectId: string;\n  appHost?: string;\n}): Promise<ProviderData> {\n  const hints: Exclude<ProviderData['hints'], undefined> = [];\n\n  if (!options.personalApiKey) {\n    hints.push({\n      key: 'posthog/missing-personal-api-key',\n      text: 'Missing PostHog Personal API Key',\n    });\n  }\n\n  let host = options.appHost;\n  if (!host) {\n    try {\n      host = getAppHost();\n    } catch {\n      hints.push({\n        key: 'posthog/missing-app-host',\n        text: 'Missing POSTHOG_HOST environment variable',\n      });\n    }\n  }\n\n  if (!options.projectId) {\n    hints.push({\n      key: 'posthog/missing-project-id',\n      text: 'Missing PostHog Project ID',\n    });\n  }\n\n  if (hints.length > 0) {\n    return { definitions: {}, hints };\n  }\n\n  const headers = {\n    Authorization: `Bearer ${options.personalApiKey}`,\n  };\n\n  const res = await fetch(\n    `${host}/api/projects/${options.projectId}/feature_flags`,\n    {\n      method: 'GET',\n      headers,\n      cache: 'no-store',\n    },\n  );\n\n  if (res.status !== 200) {\n    return {\n      definitions: {},\n      hints: [\n        {\n          key: `posthog/response-not-ok/${options.projectId}`,\n          text: `Failed to fetch PostHog (Received ${res.status} response)`,\n        },\n      ],\n    };\n  }\n\n  try {\n    const data = (await res.json()) as ApiData;\n    const items: ApiData['results'] = [...data.results];\n\n    // paginate in a parallel request\n    for (let offset = 100; offset < data.count; offset += 100) {\n      const paginatedRes = await fetch(\n        `${host}/api/projects/${options.projectId}/feature_flags?offset=${offset}&limit=100`,\n        {\n          method: 'GET',\n          headers,\n          cache: 'no-store',\n        },\n      );\n\n      if (paginatedRes.status === 200) {\n        const paginatedData = (await paginatedRes.json()) as ApiData;\n        items.push(...paginatedData.results);\n      } else {\n        hints.push({\n          key: `posthog/response-not-ok/${options.projectId}-${offset}`,\n          text: `Failed to fetch PostHog (Received ${paginatedRes.status} response)`,\n        });\n      }\n    }\n\n    return {\n      definitions: items.reduce<FlagDefinitionsType>((acc, item) => {\n        acc[item.key] = {\n          origin: `${host}/project/${options.projectId}/feature_flags/${item.id}`,\n          description: item.name,\n          createdAt: new Date(item.created_at).getTime(),\n          options: !item.filters.payloads\n            ? [{ value: false }, { value: true }]\n            : Object.entries(item.filters.payloads ?? {}).map(\n                ([key, value]) => ({\n                  value: JSON.parse(value),\n                  label: key,\n                }),\n              ),\n        };\n        return acc;\n      }, {}),\n      hints,\n    };\n  } catch {\n    return {\n      definitions: {},\n      hints: [\n        {\n          key: `posthog/response-not-ok/${options.projectId}`,\n          text: 'Failed to fetch PostHog',\n        },\n      ],\n    };\n  }\n}\n\nexport const getAppHost = (apiHost?: string) => {\n  const host = apiHost ?? process.env.POSTHOG_HOST;\n\n  if (!host) {\n    throw new Error('POSTHOG_HOST is not set');\n  }\n\n  let hostname: string;\n  try {\n    hostname = new URL(host).hostname;\n  } catch {\n    hostname = host;\n  }\n\n  if (hostname === 'us.i.posthog.com') {\n    return 'https://us.posthog.com';\n  }\n\n  if (hostname === 'eu.i.posthog.com') {\n    return 'https://eu.posthog.com';\n  }\n\n  return host;\n};\n"]}