posthog-node 4.5.0 → 4.5.2

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.
@@ -3,7 +3,7 @@ import { PostHog, PostHogOptions } from './posthog-node';
3
3
  export default class ErrorTracking {
4
4
  private client;
5
5
  private _exceptionAutocaptureEnabled;
6
- static captureException(client: PostHog, error: unknown, distinctId: string, hint: EventHint, additionalProperties?: Record<string | number, any>): Promise<void>;
6
+ static captureException(client: PostHog, error: unknown, hint: EventHint, distinctId?: string, additionalProperties?: Record<string | number, any>): Promise<void>;
7
7
  constructor(client: PostHog, options: PostHogOptions);
8
8
  private startAutocaptureIfEnabled;
9
9
  private onException;
@@ -76,5 +76,5 @@ export declare class PostHog extends PostHogCoreStateless implements PostHogNode
76
76
  reloadFeatureFlags(): Promise<void>;
77
77
  shutdown(shutdownTimeoutMs?: number): Promise<void>;
78
78
  private addLocalPersonAndGroupProperties;
79
- captureException(error: unknown, distinctId: string, additionalProperties?: Record<string | number, any>): void;
79
+ captureException(error: unknown, distinctId?: string, additionalProperties?: Record<string | number, any>): void;
80
80
  }
@@ -51,6 +51,7 @@ export type PostHogFeatureFlag = {
51
51
  };
52
52
  deleted: boolean;
53
53
  active: boolean;
54
+ /** @deprecated This field will be removed in a future version. **/
54
55
  is_simple_flag: boolean;
55
56
  rollout_percentage: null | number;
56
57
  ensure_experience_continuity: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posthog-node",
3
- "version": "4.5.0",
3
+ "version": "4.5.2",
4
4
  "description": "PostHog Node.js integration",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,11 +15,14 @@ export default class ErrorTracking {
15
15
  static async captureException(
16
16
  client: PostHog,
17
17
  error: unknown,
18
- distinctId: string,
19
18
  hint: EventHint,
19
+ distinctId?: string,
20
20
  additionalProperties?: Record<string | number, any>
21
21
  ): Promise<void> {
22
22
  const properties: EventMessage['properties'] = { ...additionalProperties }
23
+
24
+ // Given stateless nature of Node SDK we capture exceptions using personless processing when no
25
+ // user can be determined because a distinct_id is not provided e.g. exception autocapture
23
26
  if (!distinctId) {
24
27
  properties.$process_person_profile = false
25
28
  }
@@ -51,9 +54,7 @@ export default class ErrorTracking {
51
54
  }
52
55
 
53
56
  private onException(exception: unknown, hint: EventHint): void {
54
- // Given stateless nature of Node SDK we capture exceptions using personless processing
55
- // when no user can be determined e.g. in the case of exception autocapture
56
- ErrorTracking.captureException(this.client, exception, uuidv7(), hint, { $process_person_profile: false })
57
+ ErrorTracking.captureException(this.client, exception, hint)
57
58
  }
58
59
 
59
60
  private async onFatalError(): Promise<void> {
@@ -31,7 +31,7 @@ export function setupExpressErrorHandler(
31
31
  const hint = { mechanism: { type: 'middleware', handled: false } }
32
32
  // Given stateless nature of Node SDK we capture exceptions using personless processing
33
33
  // when no user can be determined e.g. in the case of exception autocapture
34
- ErrorTracking.captureException(_posthog, error, uuidv7(), hint, { $process_person_profile: false })
34
+ ErrorTracking.captureException(_posthog, error, hint, uuidv7(), { $process_person_profile: false })
35
35
  next(error)
36
36
  })
37
37
  }
@@ -135,8 +135,13 @@ export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
135
135
  return await _getFlags(distinctId, groups, disableGeoip)
136
136
  }
137
137
 
138
+ if (event === '$feature_flag_called') {
139
+ // If we're capturing a $feature_flag_called event, we don't want to enrich the event with cached flags that may be out of date.
140
+ return {}
141
+ }
142
+
138
143
  if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
139
- // Otherwise we may as well check for the flags locally and include them if there
144
+ // Otherwise we may as well check for the flags locally and include them if they are already loaded
140
145
  const groupsWithStringValues: Record<string, string> = {}
141
146
  for (const [key, value] of Object.entries(groups || {})) {
142
147
  groupsWithStringValues[key] = String(value)
@@ -486,8 +491,8 @@ export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
486
491
  return { allPersonProperties, allGroupProperties }
487
492
  }
488
493
 
489
- captureException(error: unknown, distinctId: string, additionalProperties?: Record<string | number, any>): void {
494
+ captureException(error: unknown, distinctId?: string, additionalProperties?: Record<string | number, any>): void {
490
495
  const syntheticException = new Error('PostHog syntheticException')
491
- ErrorTracking.captureException(this, error, distinctId, { syntheticException }, additionalProperties)
496
+ ErrorTracking.captureException(this, error, { syntheticException }, distinctId, additionalProperties)
492
497
  }
493
498
  }
package/src/types.ts CHANGED
@@ -58,6 +58,7 @@ export type PostHogFeatureFlag = {
58
58
  }
59
59
  deleted: boolean
60
60
  active: boolean
61
+ /** @deprecated This field will be removed in a future version. **/
61
62
  is_simple_flag: boolean
62
63
  rollout_percentage: null | number
63
64
  ensure_experience_continuity: boolean
@@ -5,6 +5,7 @@ import { matchProperty, InconclusiveMatchError, relativeDateParseForFeatureFlagM
5
5
  jest.mock('../src/fetch')
6
6
  import fetch from '../src/fetch'
7
7
  import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
8
+ import { waitForPromises } from 'posthog-core/test/test-utils/test-utils'
8
9
 
9
10
  jest.spyOn(console, 'debug').mockImplementation()
10
11
 
@@ -31,7 +32,6 @@ describe('local evaluation', () => {
31
32
  id: 1,
32
33
  name: 'Beta Feature',
33
34
  key: 'person-flag',
34
- is_simple_flag: true,
35
35
  active: true,
36
36
  filters: {
37
37
  groups: [
@@ -99,7 +99,6 @@ describe('local evaluation', () => {
99
99
  id: 1,
100
100
  name: 'Beta Feature',
101
101
  key: 'person-flag',
102
- is_simple_flag: true,
103
102
  active: true,
104
103
  filters: {
105
104
  groups: [
@@ -143,7 +142,6 @@ describe('local evaluation', () => {
143
142
  id: 1,
144
143
  name: 'Beta Feature',
145
144
  key: 'group-flag',
146
- is_simple_flag: true,
147
145
  active: true,
148
146
  filters: {
149
147
  aggregation_group_type_index: 0,
@@ -222,7 +220,6 @@ describe('local evaluation', () => {
222
220
  id: 1,
223
221
  name: 'Beta Feature',
224
222
  key: 'group-flag',
225
- is_simple_flag: true,
226
223
  active: true,
227
224
  filters: {
228
225
  aggregation_group_type_index: 0,
@@ -271,7 +268,6 @@ describe('local evaluation', () => {
271
268
  id: 1,
272
269
  name: 'Beta Feature',
273
270
  key: 'complex-flag',
274
- is_simple_flag: false,
275
271
  active: true,
276
272
  filters: {
277
273
  groups: [
@@ -547,7 +543,6 @@ describe('local evaluation', () => {
547
543
  id: 1,
548
544
  name: 'Beta Feature',
549
545
  key: 'beta-feature',
550
- is_simple_flag: true,
551
546
  active: true,
552
547
  filters: {
553
548
  groups: [
@@ -586,7 +581,6 @@ describe('local evaluation', () => {
586
581
  id: 1,
587
582
  name: 'Beta Feature',
588
583
  key: 'beta-feature',
589
- is_simple_flag: true,
590
584
  active: true,
591
585
  filters: {
592
586
  groups: [
@@ -629,7 +623,6 @@ describe('local evaluation', () => {
629
623
  id: 1,
630
624
  name: 'Beta Feature',
631
625
  key: 'beta-feature',
632
- is_simple_flag: true,
633
626
  active: true,
634
627
  ensure_experience_continuity: true,
635
628
  filters: {
@@ -665,7 +658,6 @@ describe('local evaluation', () => {
665
658
  id: 1,
666
659
  name: 'Beta Feature',
667
660
  key: 'beta-feature',
668
- is_simple_flag: false,
669
661
  active: true,
670
662
  rollout_percentage: 100,
671
663
  filters: {
@@ -681,7 +673,6 @@ describe('local evaluation', () => {
681
673
  id: 2,
682
674
  name: 'Beta Feature',
683
675
  key: 'disabled-feature',
684
- is_simple_flag: false,
685
676
  active: true,
686
677
  filters: {
687
678
  groups: [
@@ -696,7 +687,6 @@ describe('local evaluation', () => {
696
687
  id: 3,
697
688
  name: 'Beta Feature',
698
689
  key: 'beta-feature2',
699
- is_simple_flag: false,
700
690
  active: true,
701
691
  filters: {
702
692
  groups: [
@@ -739,7 +729,6 @@ describe('local evaluation', () => {
739
729
  id: 1,
740
730
  name: 'Beta Feature',
741
731
  key: 'beta-feature',
742
- is_simple_flag: false,
743
732
  active: true,
744
733
  rollout_percentage: 100,
745
734
  filters: {
@@ -758,7 +747,6 @@ describe('local evaluation', () => {
758
747
  id: 2,
759
748
  name: 'Beta Feature',
760
749
  key: 'disabled-feature',
761
- is_simple_flag: false,
762
750
  active: true,
763
751
  filters: {
764
752
  groups: [
@@ -776,7 +764,6 @@ describe('local evaluation', () => {
776
764
  id: 3,
777
765
  name: 'Beta Feature',
778
766
  key: 'beta-feature2',
779
- is_simple_flag: false,
780
767
  active: true,
781
768
  filters: {
782
769
  groups: [
@@ -822,7 +809,6 @@ describe('local evaluation', () => {
822
809
  id: 1,
823
810
  name: 'Beta Feature',
824
811
  key: 'beta-feature',
825
- is_simple_flag: false,
826
812
  active: true,
827
813
  rollout_percentage: 100,
828
814
  filters: {
@@ -838,7 +824,6 @@ describe('local evaluation', () => {
838
824
  id: 2,
839
825
  name: 'Beta Feature',
840
826
  key: 'disabled-feature',
841
- is_simple_flag: false,
842
827
  active: true,
843
828
  filters: {
844
829
  groups: [
@@ -853,7 +838,6 @@ describe('local evaluation', () => {
853
838
  id: 3,
854
839
  name: 'Beta Feature',
855
840
  key: 'beta-feature2',
856
- is_simple_flag: false,
857
841
  active: true,
858
842
  filters: {
859
843
  groups: [
@@ -894,7 +878,6 @@ describe('local evaluation', () => {
894
878
  id: 1,
895
879
  name: 'Beta Feature',
896
880
  key: 'beta-feature',
897
- is_simple_flag: false,
898
881
  active: true,
899
882
  rollout_percentage: 100,
900
883
  filters: {
@@ -913,7 +896,6 @@ describe('local evaluation', () => {
913
896
  id: 2,
914
897
  name: 'Beta Feature',
915
898
  key: 'disabled-feature',
916
- is_simple_flag: false,
917
899
  active: true,
918
900
  filters: {
919
901
  groups: [
@@ -931,7 +913,6 @@ describe('local evaluation', () => {
931
913
  id: 3,
932
914
  name: 'Beta Feature',
933
915
  key: 'beta-feature2',
934
- is_simple_flag: false,
935
916
  active: true,
936
917
  filters: {
937
918
  groups: [
@@ -1027,7 +1008,6 @@ describe('local evaluation', () => {
1027
1008
  id: 1,
1028
1009
  name: 'Beta Feature',
1029
1010
  key: 'beta-feature',
1030
- is_simple_flag: false,
1031
1011
  active: true,
1032
1012
  rollout_percentage: 100,
1033
1013
  filters: {
@@ -1043,7 +1023,6 @@ describe('local evaluation', () => {
1043
1023
  id: 2,
1044
1024
  name: 'Beta Feature',
1045
1025
  key: 'disabled-feature',
1046
- is_simple_flag: false,
1047
1026
  active: true,
1048
1027
  filters: {
1049
1028
  groups: [
@@ -1080,7 +1059,6 @@ describe('local evaluation', () => {
1080
1059
  id: 1,
1081
1060
  name: 'Beta Feature',
1082
1061
  key: 'beta-feature',
1083
- is_simple_flag: false,
1084
1062
  active: true,
1085
1063
  rollout_percentage: 100,
1086
1064
  filters: {
@@ -1099,7 +1077,6 @@ describe('local evaluation', () => {
1099
1077
  id: 2,
1100
1078
  name: 'Beta Feature',
1101
1079
  key: 'disabled-feature',
1102
- is_simple_flag: false,
1103
1080
  active: true,
1104
1081
  filters: {
1105
1082
  groups: [
@@ -1139,7 +1116,6 @@ describe('local evaluation', () => {
1139
1116
  id: 1,
1140
1117
  name: 'Beta Feature',
1141
1118
  key: 'beta-feature',
1142
- is_simple_flag: false,
1143
1119
  active: true,
1144
1120
  rollout_percentage: 100,
1145
1121
  filters: {
@@ -1155,7 +1131,6 @@ describe('local evaluation', () => {
1155
1131
  id: 2,
1156
1132
  name: 'Beta Feature',
1157
1133
  key: 'disabled-feature',
1158
- is_simple_flag: false,
1159
1134
  active: true,
1160
1135
  filters: {
1161
1136
  groups: [
@@ -1191,7 +1166,6 @@ describe('local evaluation', () => {
1191
1166
  id: 1,
1192
1167
  name: 'Beta Feature',
1193
1168
  key: 'beta-feature',
1194
- is_simple_flag: false,
1195
1169
  active: false,
1196
1170
  rollout_percentage: 100,
1197
1171
  filters: {
@@ -1207,7 +1181,6 @@ describe('local evaluation', () => {
1207
1181
  id: 2,
1208
1182
  name: 'Beta Feature',
1209
1183
  key: 'disabled-feature',
1210
- is_simple_flag: false,
1211
1184
  active: true,
1212
1185
  filters: {
1213
1186
  groups: [
@@ -1236,7 +1209,6 @@ describe('local evaluation', () => {
1236
1209
  id: 1,
1237
1210
  name: 'Beta Feature',
1238
1211
  key: 'beta-feature',
1239
- is_simple_flag: false,
1240
1212
  active: true,
1241
1213
  rollout_percentage: 100,
1242
1214
  filters: {
@@ -1318,7 +1290,6 @@ describe('local evaluation', () => {
1318
1290
  id: 1,
1319
1291
  name: 'Beta Feature',
1320
1292
  key: 'beta-feature',
1321
- is_simple_flag: false,
1322
1293
  active: true,
1323
1294
  rollout_percentage: 100,
1324
1295
  filters: {
@@ -1409,7 +1380,6 @@ describe('local evaluation', () => {
1409
1380
  id: 1,
1410
1381
  name: 'Beta Feature',
1411
1382
  key: 'beta-feature',
1412
- is_simple_flag: true,
1413
1383
  active: true,
1414
1384
  filters: {
1415
1385
  groups: [
@@ -1478,7 +1448,6 @@ describe('local evaluation', () => {
1478
1448
  id: 1,
1479
1449
  name: 'Beta Feature',
1480
1450
  key: 'beta-feature',
1481
- is_simple_flag: true,
1482
1451
  active: true,
1483
1452
  filters: {
1484
1453
  groups: [
@@ -1563,7 +1532,6 @@ describe('local evaluation', () => {
1563
1532
  id: 1,
1564
1533
  name: 'Beta Feature',
1565
1534
  key: 'beta-feature',
1566
- is_simple_flag: true,
1567
1535
  active: true,
1568
1536
  filters: {
1569
1537
  groups: [
@@ -1632,7 +1600,6 @@ describe('local evaluation', () => {
1632
1600
  id: 1,
1633
1601
  name: 'Beta Feature',
1634
1602
  key: 'beta-feature',
1635
- is_simple_flag: true,
1636
1603
  active: true,
1637
1604
  filters: {
1638
1605
  groups: [
@@ -1706,7 +1673,6 @@ describe('local evaluation', () => {
1706
1673
  id: 1,
1707
1674
  name: 'Beta Feature',
1708
1675
  key: 'person-flag',
1709
- is_simple_flag: true,
1710
1676
  active: true,
1711
1677
  filters: {
1712
1678
  groups: [
@@ -1765,7 +1731,6 @@ describe('local evaluation', () => {
1765
1731
  id: 1,
1766
1732
  name: 'Beta Feature',
1767
1733
  key: 'beta-feature',
1768
- is_simple_flag: true,
1769
1734
  active: true,
1770
1735
  filters: {
1771
1736
  groups: [
@@ -1832,6 +1797,84 @@ describe('local evaluation', () => {
1832
1797
  })
1833
1798
  })
1834
1799
 
1800
+ describe('getFeatureFlag', () => {
1801
+ it('should capture $feature_flag_called when called, but not add all cached flags', async () => {
1802
+ const flags = {
1803
+ flags: [
1804
+ {
1805
+ id: 1,
1806
+ name: 'Beta Feature',
1807
+ key: 'complex-flag',
1808
+ active: true,
1809
+ filters: {
1810
+ groups: [
1811
+ {
1812
+ variant: null,
1813
+ properties: [{ key: 'region', type: 'person', value: 'USA', operator: 'exact' }],
1814
+ rollout_percentage: 100,
1815
+ },
1816
+ ],
1817
+ },
1818
+ },
1819
+ {
1820
+ id: 2,
1821
+ name: 'Gamma Feature',
1822
+ key: 'simple-flag',
1823
+ active: true,
1824
+ filters: {
1825
+ groups: [
1826
+ {
1827
+ variant: null,
1828
+ properties: [],
1829
+ rollout_percentage: 100,
1830
+ },
1831
+ ],
1832
+ },
1833
+ },
1834
+ ],
1835
+ }
1836
+ mockedFetch.mockImplementation(apiImplementation({ localFlags: flags }))
1837
+ const posthog = new PostHog('TEST_API_KEY', {
1838
+ host: 'http://example.com',
1839
+ personalApiKey: 'TEST_PERSONAL_API_KEY',
1840
+ ...posthogImmediateResolveOptions,
1841
+ })
1842
+ let capturedMessage: any
1843
+ posthog.on('capture', (message) => {
1844
+ capturedMessage = message
1845
+ })
1846
+
1847
+ expect(
1848
+ await posthog.getFeatureFlag('complex-flag', 'some-distinct-id', {
1849
+ personProperties: {
1850
+ region: 'USA',
1851
+ } as unknown as Record<string, string>,
1852
+ })
1853
+ ).toEqual(true)
1854
+
1855
+ await waitForPromises()
1856
+
1857
+ expect(capturedMessage).toMatchObject({
1858
+ distinct_id: 'some-distinct-id',
1859
+ event: '$feature_flag_called',
1860
+ library: posthog.getLibraryId(),
1861
+ library_version: posthog.getLibraryVersion(),
1862
+ properties: {
1863
+ '$feature/complex-flag': true,
1864
+ $feature_flag: 'complex-flag',
1865
+ $feature_flag_response: true,
1866
+ $groups: undefined,
1867
+ $lib: posthog.getLibraryId(),
1868
+ $lib_version: posthog.getLibraryVersion(),
1869
+ locally_evaluated: true,
1870
+ },
1871
+ })
1872
+
1873
+ expect(capturedMessage.properties).not.toHaveProperty('$active_feature_flags')
1874
+ expect(capturedMessage.properties).not.toHaveProperty('$feature/simple-flag')
1875
+ })
1876
+ })
1877
+
1835
1878
  describe('match properties', () => {
1836
1879
  jest.useFakeTimers()
1837
1880
 
@@ -2073,8 +2116,8 @@ describe('match properties', () => {
2073
2116
  ['is_date_after', '1h', new Date('2022-05-30'), true],
2074
2117
  ['is_date_after', '1h', '2022-04-30', false],
2075
2118
  // # Try all possible relative dates
2076
- ['is_date_before', '1h', '2022-05-01 00:00:00', false],
2077
- ['is_date_before', '1h', '2022-04-30 22:00:00', true],
2119
+ ['is_date_before', '1h', '2022-05-01 00:00:00 GMT', false],
2120
+ ['is_date_before', '1h', '2022-04-30 22:00:00 GMT', true],
2078
2121
  ['is_date_before', '-1d', '2022-04-29 23:59:00 GMT', true],
2079
2122
  ['is_date_before', '-1d', '2022-04-30 00:00:01 GMT', false],
2080
2123
  ['is_date_before', '1w', '2022-04-23 00:00:00 GMT', true],
@@ -2357,7 +2400,6 @@ describe('consistency tests', () => {
2357
2400
  name: '',
2358
2401
  key: 'simple-flag',
2359
2402
  active: true,
2360
- is_simple_flag: false,
2361
2403
  filters: {
2362
2404
  groups: [{ properties: [], rollout_percentage: 45 }],
2363
2405
  },
@@ -3390,7 +3432,6 @@ describe('consistency tests', () => {
3390
3432
  id: 1,
3391
3433
  name: 'Beta Feature',
3392
3434
  key: 'multivariate-flag',
3393
- is_simple_flag: false,
3394
3435
  active: true,
3395
3436
  filters: {
3396
3437
  groups: [{ properties: [], rollout_percentage: 55 }],
@@ -506,7 +506,6 @@ describe('PostHog Node.js', () => {
506
506
  id: 1,
507
507
  name: 'Beta Feature',
508
508
  key: 'beta-feature-local',
509
- is_simple_flag: false,
510
509
  active: true,
511
510
  rollout_percentage: 100,
512
511
  filters: {
@@ -533,7 +532,6 @@ describe('PostHog Node.js', () => {
533
532
  id: 1,
534
533
  name: 'Beta Feature',
535
534
  key: 'person-flag',
536
- is_simple_flag: true,
537
535
  active: true,
538
536
  filters: {
539
537
  groups: [
@@ -556,7 +554,6 @@ describe('PostHog Node.js', () => {
556
554
  id: 1,
557
555
  name: 'Beta Feature',
558
556
  key: 'false-flag',
559
- is_simple_flag: true,
560
557
  active: true,
561
558
  filters: {
562
559
  groups: [
@@ -1081,7 +1078,9 @@ describe('PostHog Node.js', () => {
1081
1078
  expect(mockedFetch).toHaveBeenCalledTimes(0)
1082
1079
 
1083
1080
  await expect(posthog.getFeatureFlagPayload('false-flag', '123', true)).resolves.toEqual(300)
1084
- expect(mockedFetch).toHaveBeenCalledTimes(0)
1081
+ // Check no non-batch API calls were made
1082
+ const additionalNonBatchCalls = mockedFetch.mock.calls.filter((call) => !call[0].includes('/batch'))
1083
+ expect(additionalNonBatchCalls.length).toBe(0)
1085
1084
  })
1086
1085
 
1087
1086
  it('should not double parse json with getFeatureFlagPayloads and server eval', async () => {