@posthog/convex 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +299 -0
  2. package/README.md +237 -29
  3. package/dist/client/index.d.ts +126 -0
  4. package/dist/client/index.d.ts.map +1 -0
  5. package/dist/client/index.js +213 -0
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/component/_generated/api.d.ts +34 -0
  8. package/dist/component/_generated/api.d.ts.map +1 -0
  9. package/dist/component/_generated/api.js +31 -0
  10. package/dist/component/_generated/api.js.map +1 -0
  11. package/dist/component/_generated/component.d.ts +134 -0
  12. package/dist/component/_generated/component.d.ts.map +1 -0
  13. package/dist/component/_generated/component.js +11 -0
  14. package/dist/component/_generated/component.js.map +1 -0
  15. package/dist/component/_generated/dataModel.d.ts +46 -0
  16. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  17. package/dist/component/_generated/dataModel.js +11 -0
  18. package/dist/component/_generated/dataModel.js.map +1 -0
  19. package/dist/component/_generated/server.d.ts +121 -0
  20. package/dist/component/_generated/server.d.ts.map +1 -0
  21. package/dist/component/_generated/server.js +78 -0
  22. package/dist/component/_generated/server.js.map +1 -0
  23. package/dist/component/convex.config.d.ts +3 -0
  24. package/dist/component/convex.config.d.ts.map +1 -0
  25. package/dist/component/convex.config.js +3 -0
  26. package/dist/component/convex.config.js.map +1 -0
  27. package/dist/component/lib.d.ts +119 -0
  28. package/dist/component/lib.d.ts.map +1 -0
  29. package/dist/component/lib.js +221 -0
  30. package/dist/component/lib.js.map +1 -0
  31. package/dist/component/schema.d.ts +3 -0
  32. package/dist/component/schema.d.ts.map +1 -0
  33. package/dist/component/schema.js +3 -0
  34. package/dist/component/schema.js.map +1 -0
  35. package/package.json +78 -7
  36. package/src/client/index.test.ts +505 -0
  37. package/src/client/index.ts +363 -0
  38. package/src/client/setup.test.ts +20 -0
  39. package/src/component/_generated/api.ts +50 -0
  40. package/src/component/_generated/component.ts +203 -0
  41. package/src/component/_generated/dataModel.ts +60 -0
  42. package/src/component/_generated/server.ts +156 -0
  43. package/src/component/convex.config.ts +3 -0
  44. package/src/component/lib.ts +259 -0
  45. package/src/component/schema.ts +3 -0
  46. package/src/component/setup.test.ts +11 -0
  47. package/src/test.ts +15 -0
package/package.json CHANGED
@@ -1,10 +1,81 @@
1
1
  {
2
2
  "name": "@posthog/convex",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @posthog/convex",
3
+ "description": "Send analytics events to PostHog from your Convex backend.",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/PostHog/posthog-js.git",
7
+ "directory": "packages/convex"
8
+ },
9
+ "homepage": "https://github.com/PostHog/posthog-js/tree/main/packages/convex#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/PostHog/posthog-js/issues"
12
+ },
13
+ "author": "PostHog Inc.",
14
+ "version": "0.1.1",
15
+ "license": "MIT",
5
16
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
17
+ "convex",
18
+ "component",
19
+ "posthog",
20
+ "analytics"
21
+ ],
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "exports": {
27
+ "./package.json": "./package.json",
28
+ ".": {
29
+ "types": "./dist/client/index.d.ts",
30
+ "default": "./dist/client/index.js"
31
+ },
32
+ "./test": "./src/test.ts",
33
+ "./_generated/component.js": {
34
+ "types": "./dist/component/_generated/component.d.ts"
35
+ },
36
+ "./_generated/component": {
37
+ "types": "./dist/component/_generated/component.d.ts"
38
+ },
39
+ "./convex.config.js": {
40
+ "types": "./dist/component/convex.config.d.ts",
41
+ "default": "./dist/component/convex.config.js"
42
+ },
43
+ "./convex.config": {
44
+ "types": "./dist/component/convex.config.d.ts",
45
+ "default": "./dist/component/convex.config.js"
46
+ }
47
+ },
48
+ "types": "./dist/client/index.d.ts",
49
+ "module": "./dist/client/index.js",
50
+ "dependencies": {
51
+ "@posthog/core": "1.23.1",
52
+ "posthog-node": "5.24.17"
53
+ },
54
+ "devDependencies": {
55
+ "@convex-dev/eslint-plugin": "^1.1.1",
56
+ "@edge-runtime/vm": "^5.0.0",
57
+ "@jest/globals": "^29.7.0",
58
+ "@types/node": "^24.10.12",
59
+ "convex": "1.31.7",
60
+ "convex-test": "0.0.41",
61
+ "globals": "^17.3.0",
62
+ "jest": "29.7.0",
63
+ "ts-jest": "29.4.0",
64
+ "typescript": "5.8.2",
65
+ "vite": "7.3.1"
66
+ },
67
+ "peerDependencies": {
68
+ "convex": "^1.31.7"
69
+ },
70
+ "scripts": {
71
+ "build": "tsc --project ./tsconfig.build.json",
72
+ "build:codegen": "npx convex codegen --component-dir ./src/component && pnpm build",
73
+ "clean": "rm -rf dist *.tsbuildinfo && pnpm build:codegen",
74
+ "typecheck": "tsc --noEmit",
75
+ "lint": "eslint src",
76
+ "lint:fix": "eslint src --fix",
77
+ "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest",
78
+ "test:integration": "jest run --typecheck",
79
+ "package": "pnpm pack --out $PACKAGE_DEST/%s.tgz"
80
+ }
81
+ }
@@ -0,0 +1,505 @@
1
+ import { describe, expect, test, jest } from '@jest/globals'
2
+ import { PostHog, normalizeError } from './index.js'
3
+ import type { BeforeSendFn, IdentifyFn } from './index.js'
4
+
5
+ function mockSchedulerCtx() {
6
+ return {
7
+ scheduler: {
8
+ runAfter: jest.fn(),
9
+ },
10
+ }
11
+ }
12
+
13
+ describe('PostHog client', () => {
14
+ test('constructor uses defaults from env', () => {
15
+ process.env.POSTHOG_API_KEY = 'test-key'
16
+ process.env.POSTHOG_HOST = 'https://test.posthog.com'
17
+
18
+ const posthog = new PostHog({} as never)
19
+ expect(posthog).toBeInstanceOf(PostHog)
20
+
21
+ delete process.env.POSTHOG_API_KEY
22
+ delete process.env.POSTHOG_HOST
23
+ })
24
+
25
+ test('constructor accepts explicit options', () => {
26
+ const posthog = new PostHog({} as never, {
27
+ apiKey: 'explicit-key',
28
+ host: 'https://custom.posthog.com',
29
+ })
30
+ expect(posthog).toBeInstanceOf(PostHog)
31
+ })
32
+
33
+ test('exposes capture, identify, groupIdentify, alias, captureException methods', () => {
34
+ const posthog = new PostHog({} as never, { apiKey: 'test' })
35
+
36
+ expect(typeof posthog.capture).toBe('function')
37
+ expect(typeof posthog.identify).toBe('function')
38
+ expect(typeof posthog.groupIdentify).toBe('function')
39
+ expect(typeof posthog.alias).toBe('function')
40
+ expect(typeof posthog.captureException).toBe('function')
41
+ })
42
+
43
+ test('exposes feature flag methods', () => {
44
+ const posthog = new PostHog({} as never, { apiKey: 'test' })
45
+
46
+ expect(typeof posthog.getFeatureFlag).toBe('function')
47
+ expect(typeof posthog.isFeatureEnabled).toBe('function')
48
+ expect(typeof posthog.getFeatureFlagPayload).toBe('function')
49
+ expect(typeof posthog.getFeatureFlagResult).toBe('function')
50
+ expect(typeof posthog.getAllFlags).toBe('function')
51
+ expect(typeof posthog.getAllFlagsAndPayloads).toBe('function')
52
+ })
53
+ })
54
+
55
+ describe('normalizeError', () => {
56
+ test('extracts message, stack, and name from Error instances', () => {
57
+ const error = new Error('test error')
58
+ error.name = 'TestError'
59
+ const result = normalizeError(error)
60
+
61
+ expect(result.message).toBe('test error')
62
+ expect(result.name).toBe('TestError')
63
+ expect(result.stack).toBeDefined()
64
+ })
65
+
66
+ test('wraps string errors', () => {
67
+ const result = normalizeError('something went wrong')
68
+
69
+ expect(result.message).toBe('something went wrong')
70
+ expect(result.stack).toBeUndefined()
71
+ expect(result.name).toBeUndefined()
72
+ })
73
+
74
+ test('extracts from error-like objects', () => {
75
+ const result = normalizeError({
76
+ message: 'obj error',
77
+ stack: 'at line 1',
78
+ name: 'ObjError',
79
+ })
80
+
81
+ expect(result.message).toBe('obj error')
82
+ expect(result.stack).toBe('at line 1')
83
+ expect(result.name).toBe('ObjError')
84
+ })
85
+
86
+ test('ignores non-string stack/name on error-like objects', () => {
87
+ const result = normalizeError({
88
+ message: 'obj error',
89
+ stack: 123,
90
+ name: null,
91
+ })
92
+
93
+ expect(result.message).toBe('obj error')
94
+ expect(result.stack).toBeUndefined()
95
+ expect(result.name).toBeUndefined()
96
+ })
97
+
98
+ test('stringifies unknown values', () => {
99
+ expect(normalizeError(42)).toEqual({ message: '42' })
100
+ expect(normalizeError(null)).toEqual({ message: 'null' })
101
+ expect(normalizeError(undefined)).toEqual({ message: 'undefined' })
102
+ })
103
+ })
104
+
105
+ describe('captureException', () => {
106
+ test('schedules captureException action with serialized error', async () => {
107
+ const component = {
108
+ lib: { captureException: 'captureException_ref' },
109
+ }
110
+ const posthog = new PostHog(component as never, { apiKey: 'key' })
111
+ const ctx = mockSchedulerCtx()
112
+
113
+ await posthog.captureException(ctx as never, {
114
+ error: new TypeError('bad type'),
115
+ distinctId: 'user-1',
116
+ additionalProperties: { context: 'signup' },
117
+ })
118
+
119
+ expect(ctx.scheduler.runAfter).toHaveBeenCalledTimes(1)
120
+ const [delay, ref, args] = ctx.scheduler.runAfter.mock.calls[0]
121
+ expect(delay).toBe(0)
122
+ expect(ref).toBe('captureException_ref')
123
+ expect(args.errorMessage).toBe('bad type')
124
+ expect(args.errorName).toBe('TypeError')
125
+ expect(args.errorStack).toBeDefined()
126
+ expect(args.distinctId).toBe('user-1')
127
+ expect(args.additionalProperties).toEqual({ context: 'signup' })
128
+ })
129
+
130
+ test('handles string errors', async () => {
131
+ const component = {
132
+ lib: { captureException: 'captureException_ref' },
133
+ }
134
+ const posthog = new PostHog(component as never, { apiKey: 'key' })
135
+ const ctx = mockSchedulerCtx()
136
+
137
+ await posthog.captureException(ctx as never, {
138
+ error: 'string error',
139
+ })
140
+
141
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
142
+ expect(args.errorMessage).toBe('string error')
143
+ expect(args.errorStack).toBeUndefined()
144
+ expect(args.errorName).toBeUndefined()
145
+ })
146
+ })
147
+
148
+ describe('beforeSend', () => {
149
+ test('allows events through when no beforeSend is configured', async () => {
150
+ const component = { lib: { capture: 'capture_ref' } }
151
+ const posthog = new PostHog(component as never, { apiKey: 'key' })
152
+ const ctx = mockSchedulerCtx()
153
+
154
+ await posthog.capture(ctx as never, {
155
+ distinctId: 'user-1',
156
+ event: 'page_view',
157
+ })
158
+
159
+ expect(ctx.scheduler.runAfter).toHaveBeenCalledTimes(1)
160
+ })
161
+
162
+ test('blocks events when beforeSend returns null', async () => {
163
+ const component = { lib: { capture: 'capture_ref' } }
164
+ const beforeSend: BeforeSendFn = () => null
165
+ const posthog = new PostHog(component as never, {
166
+ apiKey: 'key',
167
+ beforeSend,
168
+ })
169
+ const ctx = mockSchedulerCtx()
170
+
171
+ await posthog.capture(ctx as never, {
172
+ distinctId: 'user-1',
173
+ event: 'page_view',
174
+ })
175
+
176
+ expect(ctx.scheduler.runAfter).not.toHaveBeenCalled()
177
+ })
178
+
179
+ test('modifies event properties via beforeSend', async () => {
180
+ const component = { lib: { capture: 'capture_ref' } }
181
+ const beforeSend: BeforeSendFn = (event) => ({
182
+ ...event,
183
+ properties: { ...event.properties, injected: true },
184
+ })
185
+ const posthog = new PostHog(component as never, {
186
+ apiKey: 'key',
187
+ beforeSend,
188
+ })
189
+ const ctx = mockSchedulerCtx()
190
+
191
+ await posthog.capture(ctx as never, {
192
+ distinctId: 'user-1',
193
+ event: 'page_view',
194
+ properties: { page: '/home' },
195
+ })
196
+
197
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
198
+ expect(args.properties).toEqual({ page: '/home', injected: true })
199
+ })
200
+
201
+ test('chains multiple beforeSend functions', async () => {
202
+ const component = { lib: { capture: 'capture_ref' } }
203
+ const fn1: BeforeSendFn = (event) => ({
204
+ ...event,
205
+ properties: { ...event.properties, first: true },
206
+ })
207
+ const fn2: BeforeSendFn = (event) => ({
208
+ ...event,
209
+ properties: { ...event.properties, second: true },
210
+ })
211
+ const posthog = new PostHog(component as never, {
212
+ apiKey: 'key',
213
+ beforeSend: [fn1, fn2],
214
+ })
215
+ const ctx = mockSchedulerCtx()
216
+
217
+ await posthog.capture(ctx as never, {
218
+ distinctId: 'user-1',
219
+ event: 'test',
220
+ })
221
+
222
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
223
+ expect(args.properties).toEqual({ first: true, second: true })
224
+ })
225
+
226
+ test('short-circuits chain when a function returns null', async () => {
227
+ const component = { lib: { capture: 'capture_ref' } }
228
+ const fn1: BeforeSendFn = () => null
229
+ const fn2: BeforeSendFn = jest.fn((event) => event)
230
+ const posthog = new PostHog(component as never, {
231
+ apiKey: 'key',
232
+ beforeSend: [fn1, fn2],
233
+ })
234
+ const ctx = mockSchedulerCtx()
235
+
236
+ await posthog.capture(ctx as never, {
237
+ distinctId: 'user-1',
238
+ event: 'test',
239
+ })
240
+
241
+ expect(ctx.scheduler.runAfter).not.toHaveBeenCalled()
242
+ expect(fn2).not.toHaveBeenCalled()
243
+ })
244
+
245
+ test('applies beforeSend to identify events', async () => {
246
+ const component = { lib: { identify: 'identify_ref' } }
247
+ const beforeSend: BeforeSendFn = (event) => {
248
+ expect(event.event).toBe('$identify')
249
+ return null
250
+ }
251
+ const posthog = new PostHog(component as never, {
252
+ apiKey: 'key',
253
+ beforeSend,
254
+ })
255
+ const ctx = mockSchedulerCtx()
256
+
257
+ await posthog.identify(ctx as never, {
258
+ distinctId: 'user-1',
259
+ })
260
+
261
+ expect(ctx.scheduler.runAfter).not.toHaveBeenCalled()
262
+ })
263
+
264
+ test('applies beforeSend to alias events', async () => {
265
+ const component = { lib: { alias: 'alias_ref' } }
266
+ const beforeSend: BeforeSendFn = (event) => {
267
+ expect(event.event).toBe('$create_alias')
268
+ return event
269
+ }
270
+ const posthog = new PostHog(component as never, {
271
+ apiKey: 'key',
272
+ beforeSend,
273
+ })
274
+ const ctx = mockSchedulerCtx()
275
+
276
+ await posthog.alias(ctx as never, {
277
+ distinctId: 'user-1',
278
+ alias: 'alias-1',
279
+ })
280
+
281
+ expect(ctx.scheduler.runAfter).toHaveBeenCalledTimes(1)
282
+ })
283
+
284
+ test('applies beforeSend to captureException events', async () => {
285
+ const component = {
286
+ lib: { captureException: 'captureException_ref' },
287
+ }
288
+ const beforeSend: BeforeSendFn = (event) => {
289
+ expect(event.event).toBe('$exception')
290
+ return null
291
+ }
292
+ const posthog = new PostHog(component as never, {
293
+ apiKey: 'key',
294
+ beforeSend,
295
+ })
296
+ const ctx = mockSchedulerCtx()
297
+
298
+ await posthog.captureException(ctx as never, {
299
+ error: new Error('test'),
300
+ })
301
+
302
+ expect(ctx.scheduler.runAfter).not.toHaveBeenCalled()
303
+ })
304
+
305
+ test('applies beforeSend to groupIdentify events', async () => {
306
+ const component = {
307
+ lib: { groupIdentify: 'groupIdentify_ref' },
308
+ }
309
+ const beforeSend: BeforeSendFn = (event) => {
310
+ expect(event.event).toBe('$groupidentify')
311
+ return event
312
+ }
313
+ const posthog = new PostHog(component as never, {
314
+ apiKey: 'key',
315
+ beforeSend,
316
+ })
317
+ const ctx = mockSchedulerCtx()
318
+
319
+ await posthog.groupIdentify(ctx as never, {
320
+ groupType: 'company',
321
+ groupKey: 'acme',
322
+ })
323
+
324
+ expect(ctx.scheduler.runAfter).toHaveBeenCalledTimes(1)
325
+ })
326
+ })
327
+
328
+ describe('identify callback', () => {
329
+ const identifyReturning: (distinctId: string) => IdentifyFn = (distinctId) => async () => ({ distinctId })
330
+
331
+ const identifyReturningNull: IdentifyFn = async () => null
332
+
333
+ test('uses identify callback result for capture', async () => {
334
+ const component = { lib: { capture: 'capture_ref' } }
335
+ const posthog = new PostHog(component as never, {
336
+ apiKey: 'key',
337
+ identify: identifyReturning('auth-user-1'),
338
+ })
339
+ const ctx = mockSchedulerCtx()
340
+
341
+ await posthog.capture(ctx as never, { event: 'test_event' })
342
+
343
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
344
+ expect(args.distinctId).toBe('auth-user-1')
345
+ })
346
+
347
+ test('falls back to explicit distinctId when identify returns null', async () => {
348
+ const component = { lib: { capture: 'capture_ref' } }
349
+ const posthog = new PostHog(component as never, {
350
+ apiKey: 'key',
351
+ identify: identifyReturningNull,
352
+ })
353
+ const ctx = mockSchedulerCtx()
354
+
355
+ await posthog.capture(ctx as never, {
356
+ distinctId: 'explicit-user',
357
+ event: 'test_event',
358
+ })
359
+
360
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
361
+ expect(args.distinctId).toBe('explicit-user')
362
+ })
363
+
364
+ test('throws when neither identify nor explicit distinctId resolves', async () => {
365
+ const component = { lib: { capture: 'capture_ref' } }
366
+ const posthog = new PostHog(component as never, {
367
+ apiKey: 'key',
368
+ identify: identifyReturningNull,
369
+ })
370
+ const ctx = mockSchedulerCtx()
371
+
372
+ await expect(posthog.capture(ctx as never, { event: 'test_event' })).rejects.toThrow('Could not resolve distinctId')
373
+ })
374
+
375
+ test('throws when no identify configured and no explicit distinctId', async () => {
376
+ const component = { lib: { capture: 'capture_ref' } }
377
+ const posthog = new PostHog(component as never, { apiKey: 'key' })
378
+ const ctx = mockSchedulerCtx()
379
+
380
+ await expect(posthog.capture(ctx as never, { event: 'test_event' })).rejects.toThrow('Could not resolve distinctId')
381
+ })
382
+
383
+ test('identify callback takes precedence over explicit distinctId', async () => {
384
+ const component = { lib: { capture: 'capture_ref' } }
385
+ const posthog = new PostHog(component as never, {
386
+ apiKey: 'key',
387
+ identify: identifyReturning('auth-user'),
388
+ })
389
+ const ctx = mockSchedulerCtx()
390
+
391
+ await posthog.capture(ctx as never, {
392
+ distinctId: 'explicit-user',
393
+ event: 'test_event',
394
+ })
395
+
396
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
397
+ expect(args.distinctId).toBe('auth-user')
398
+ })
399
+
400
+ test('passes ctx to identify callback', async () => {
401
+ const component = { lib: { capture: 'capture_ref' } }
402
+ const identify = jest.fn(async () => ({ distinctId: 'resolved' }))
403
+ const posthog = new PostHog(component as never, {
404
+ apiKey: 'key',
405
+ identify,
406
+ })
407
+ const ctx = mockSchedulerCtx()
408
+
409
+ await posthog.capture(ctx as never, { event: 'test_event' })
410
+
411
+ expect(identify).toHaveBeenCalledWith(ctx)
412
+ })
413
+
414
+ test('works with identify method', async () => {
415
+ const component = { lib: { identify: 'identify_ref' } }
416
+ const posthog = new PostHog(component as never, {
417
+ apiKey: 'key',
418
+ identify: identifyReturning('auth-user'),
419
+ })
420
+ const ctx = mockSchedulerCtx()
421
+
422
+ await posthog.identify(ctx as never, {})
423
+
424
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
425
+ expect(args.distinctId).toBe('auth-user')
426
+ })
427
+
428
+ test('works with alias method', async () => {
429
+ const component = { lib: { alias: 'alias_ref' } }
430
+ const posthog = new PostHog(component as never, {
431
+ apiKey: 'key',
432
+ identify: identifyReturning('auth-user'),
433
+ })
434
+ const ctx = mockSchedulerCtx()
435
+
436
+ await posthog.alias(ctx as never, { alias: 'new-alias' })
437
+
438
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
439
+ expect(args.distinctId).toBe('auth-user')
440
+ })
441
+
442
+ test('captureException works without distinctId when identify is not configured', async () => {
443
+ const component = {
444
+ lib: { captureException: 'captureException_ref' },
445
+ }
446
+ const posthog = new PostHog(component as never, { apiKey: 'key' })
447
+ const ctx = mockSchedulerCtx()
448
+
449
+ await posthog.captureException(ctx as never, {
450
+ error: new Error('test'),
451
+ })
452
+
453
+ expect(ctx.scheduler.runAfter).toHaveBeenCalledTimes(1)
454
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
455
+ expect(args.distinctId).toBeUndefined()
456
+ })
457
+
458
+ test('captureException uses identify callback when available', async () => {
459
+ const component = {
460
+ lib: { captureException: 'captureException_ref' },
461
+ }
462
+ const posthog = new PostHog(component as never, {
463
+ apiKey: 'key',
464
+ identify: identifyReturning('auth-user'),
465
+ })
466
+ const ctx = mockSchedulerCtx()
467
+
468
+ await posthog.captureException(ctx as never, {
469
+ error: new Error('test'),
470
+ })
471
+
472
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
473
+ expect(args.distinctId).toBe('auth-user')
474
+ })
475
+
476
+ test('works with feature flag methods', async () => {
477
+ const component = { lib: { getFeatureFlag: 'getFeatureFlag_ref' } }
478
+ const posthog = new PostHog(component as never, {
479
+ apiKey: 'key',
480
+ identify: identifyReturning('auth-user'),
481
+ })
482
+ const ctx = {
483
+ runAction: jest.fn(async (_ref: unknown, _args: Record<string, unknown>) => true),
484
+ }
485
+
486
+ await posthog.getFeatureFlag(ctx as never, { key: 'my-flag' })
487
+
488
+ const [, args] = ctx.runAction.mock.calls[0]
489
+ expect(args.distinctId).toBe('auth-user')
490
+ })
491
+
492
+ test('explicit distinctId still works without identify callback', async () => {
493
+ const component = { lib: { capture: 'capture_ref' } }
494
+ const posthog = new PostHog(component as never, { apiKey: 'key' })
495
+ const ctx = mockSchedulerCtx()
496
+
497
+ await posthog.capture(ctx as never, {
498
+ distinctId: 'explicit-user',
499
+ event: 'test_event',
500
+ })
501
+
502
+ const [, , args] = ctx.scheduler.runAfter.mock.calls[0]
503
+ expect(args.distinctId).toBe('explicit-user')
504
+ })
505
+ })