Files
zeekay 1cfa22a698 merge: upstream/main (sync, brand preserved)
# Conflicts:
#	.github/workflows/check-insights-major-version.yml
#	.github/workflows/check-posthog-major-version.yml
#	.github/workflows/ci-security.yaml
#	.github/workflows/validate-versioning.yml
#	AGENTS.md
#	AGENTS.md~upstream_main
#	packages/ai/src/otel/capture.ts
#	packages/ai/src/otel/mappers/aiSdk.ts
#	packages/ai/src/otel/mappers/index.ts
#	packages/ai/src/otel/types.ts
#	packages/core/src/__tests__/posthog.init.spec.ts
#	packages/react-native/src/PostHogProvider.tsx
#	packages/react/src/context/PostHogContext.ts
#	packages/react/src/context/PostHogProvider.tsx
#	tooling/eslint-plugin-insights/no-unsafe-web-global.js
2026-06-02 10:52:56 -07:00

289 lines
8.8 KiB
TypeScript

import { insights } from './insights.js'
import { action, mutation } from './_generated/server.js'
import { v } from 'convex/values'
// --- Fire-and-forget methods (mutations) ---
// When the identify callback is configured, distinctId is resolved automatically
// from the signed-in user. Pass distinctId explicitly to override or when the
// user is not signed in.
export const testCapture = mutation({
args: {
distinctId: v.optional(v.string()),
event: v.string(),
properties: v.optional(v.any()),
groups: v.optional(v.any()),
sendFeatureFlags: v.optional(v.boolean()),
timestamp: v.optional(v.string()),
uuid: v.optional(v.string()),
disableGeoip: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
await insights.capture(ctx, {
distinctId: args.distinctId,
event: args.event,
properties: args.properties,
groups: args.groups,
sendFeatureFlags: args.sendFeatureFlags,
timestamp: args.timestamp ? new Date(args.timestamp) : undefined,
uuid: args.uuid || undefined,
disableGeoip: args.disableGeoip,
})
return { success: true }
},
})
export const testIdentify = mutation({
args: {
distinctId: v.optional(v.string()),
properties: v.optional(v.any()),
disableGeoip: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
await insights.identify(ctx, {
distinctId: args.distinctId,
properties: args.properties,
disableGeoip: args.disableGeoip,
})
return { success: true }
},
})
export const testGroupIdentify = mutation({
args: {
groupType: v.string(),
groupKey: v.string(),
properties: v.optional(v.any()),
distinctId: v.optional(v.string()),
disableGeoip: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
await insights.groupIdentify(ctx, {
groupType: args.groupType,
groupKey: args.groupKey,
properties: args.properties,
distinctId: args.distinctId || undefined,
disableGeoip: args.disableGeoip,
})
return { success: true }
},
})
export const testAlias = mutation({
args: {
distinctId: v.optional(v.string()),
alias: v.string(),
disableGeoip: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
await insights.alias(ctx, {
distinctId: args.distinctId,
alias: args.alias,
disableGeoip: args.disableGeoip,
})
return { success: true }
},
})
export const testCaptureException = mutation({
args: {
errorMessage: v.string(),
errorType: v.optional(v.union(v.literal('error'), v.literal('string'), v.literal('object'))),
distinctId: v.optional(v.string()),
additionalProperties: v.optional(v.any()),
},
handler: async (ctx, args) => {
let error: unknown
switch (args.errorType ?? 'error') {
case 'error':
error = new Error(args.errorMessage)
break
case 'string':
error = args.errorMessage
break
case 'object':
error = { message: args.errorMessage }
break
}
await insights.captureException(ctx, {
error,
distinctId: args.distinctId || undefined,
additionalProperties: args.additionalProperties,
})
return { success: true }
},
})
// --- Feature flag methods (actions) ---
const featureFlagArgs = {
distinctId: v.optional(v.string()),
flagKey: v.string(),
groups: v.optional(v.any()),
personProperties: v.optional(v.any()),
groupProperties: v.optional(v.any()),
disableGeoip: v.optional(v.boolean()),
}
function featureFlagOptions(args: {
groups?: unknown
personProperties?: unknown
groupProperties?: unknown
disableGeoip?: boolean
}) {
return {
groups: args.groups as Record<string, string> | undefined,
personProperties: args.personProperties as Record<string, string> | undefined,
groupProperties: args.groupProperties as Record<string, Record<string, string>> | undefined,
disableGeoip: args.disableGeoip,
}
}
export const testGetFeatureFlag = action({
args: featureFlagArgs,
handler: async (ctx, args) => {
const value = await insights.getFeatureFlag(ctx, {
key: args.flagKey,
distinctId: args.distinctId,
...featureFlagOptions(args),
})
return { flagKey: args.flagKey, value }
},
})
export const testIsFeatureEnabled = action({
args: featureFlagArgs,
handler: async (ctx, args) => {
const enabled = await insights.isFeatureEnabled(ctx, {
key: args.flagKey,
distinctId: args.distinctId,
...featureFlagOptions(args),
})
return { flagKey: args.flagKey, enabled }
},
})
export const testGetFeatureFlagPayload = action({
args: {
...featureFlagArgs,
matchValue: v.optional(v.union(v.boolean(), v.string())),
},
handler: async (ctx, args) => {
const payload = await insights.getFeatureFlagPayload(ctx, {
key: args.flagKey,
distinctId: args.distinctId,
matchValue: args.matchValue,
...featureFlagOptions(args),
})
return { flagKey: args.flagKey, payload }
},
})
export const testGetFeatureFlagResult = action({
args: featureFlagArgs,
handler: async (ctx, args) => {
const result = await insights.getFeatureFlagResult(ctx, {
key: args.flagKey,
distinctId: args.distinctId,
...featureFlagOptions(args),
})
return { flagKey: args.flagKey, result }
},
})
export const testGetAllFlags = query({
args: {
distinctId: v.optional(v.string()),
groups: v.optional(v.any()),
personProperties: v.optional(v.any()),
groupProperties: v.optional(v.any()),
disableGeoip: v.optional(v.boolean()),
flagKeys: v.optional(v.array(v.string())),
},
handler: async (ctx, args) => {
const flags = await posthog.getAllFlags(ctx, {
distinctId: args.distinctId,
groups: args.groups as Record<string, string> | undefined,
personProperties: args.personProperties as Record<string, string> | undefined,
groupProperties: args.groupProperties as Record<string, Record<string, string>> | undefined,
disableGeoip: args.disableGeoip,
flagKeys: args.flagKeys,
})
return { flags }
},
})
// --- Cache inspection helpers (used by the demo UI to surface the cron's progress) ---
export const flagDefinitionsStatus = query({
args: {},
handler: async (ctx) => {
const row = await ctx.runQuery(components.posthog.lib.getFlagDefinitions, {})
if (!row) return null
let flagKeys: string[] = []
try {
const parsed = JSON.parse(row.data) as { flags?: Array<{ key?: string }> }
flagKeys = parsed.flags?.map((f) => f.key ?? '<unnamed>') ?? []
} catch {
// ignore parse errors — keys list stays empty.
}
return {
fetchedAt: row.fetchedAt,
etag: row.etag ?? null,
flagCount: flagKeys.length,
flagKeys,
}
},
})
// --- Remote feature flag evaluation wrappers ---
//
// These are action-context actions that hit PostHog's `/flags` endpoint via the client's
// `evaluate*` methods. Use them when local eval isn't possible (no personal API key, experience
// continuity flags, static cohorts, properties you don't have server-side).
const remoteFlagArgs = {
distinctId: v.optional(v.string()),
flagKey: v.string(),
groups: v.optional(v.any()),
personProperties: v.optional(v.any()),
groupProperties: v.optional(v.any()),
disableGeoip: v.optional(v.boolean()),
flagKeys: v.optional(v.array(v.string())),
},
handler: async (ctx, args) => {
const flags = await insights.getAllFlags(ctx, {
distinctId: args.distinctId,
groups: args.groups as Record<string, string> | undefined,
personProperties: args.personProperties as Record<string, string> | undefined,
groupProperties: args.groupProperties as Record<string, Record<string, string>> | undefined,
disableGeoip: args.disableGeoip,
flagKeys: args.flagKeys,
})
return { flags }
},
})
export const testGetAllFlagsAndPayloads = action({
args: {
distinctId: v.optional(v.string()),
groups: v.optional(v.any()),
personProperties: v.optional(v.any()),
groupProperties: v.optional(v.any()),
disableGeoip: v.optional(v.boolean()),
flagKeys: v.optional(v.array(v.string())),
},
handler: async (ctx, args) => {
const result = await insights.getAllFlagsAndPayloads(ctx, {
distinctId: args.distinctId,
groups: args.groups as Record<string, string> | undefined,
personProperties: args.personProperties as Record<string, string> | undefined,
groupProperties: args.groupProperties as Record<string, Record<string, string>> | undefined,
disableGeoip: args.disableGeoip,
flagKeys: args.flagKeys,
})
return result
},
})