posthog-node 5.4.0 → 5.5.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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,57 @@
1
1
  # Next
2
2
 
3
- # 5.4.0 – 2025-09-07
3
+ # 5.5.1 – 2025-07-15
4
+
5
+ 1. wrap `InconclusiveMatchError`s in `logMsgIfDebug` for local flag evaluations on `sendFeatureFlags`
6
+
7
+ # 5.5.0 – 2025-07-10
8
+
9
+ 1. feat: make the `sendFeatureFlags` parameter more declarative and ergonomic. Implementation notes below:
10
+
11
+ Modified `sendFeatureFlags` to be type `boolean | SendFeatureFlagsOptions`, (which is defined thusly)
12
+
13
+ ```ts
14
+ export interface SendFeatureFlagsOptions {
15
+ onlyEvaluateLocally?: boolean
16
+ personProperties?: Record<string, any>
17
+ groupProperties?: Record<string, Record<string, any>>
18
+ }
19
+ ```
20
+
21
+ This lets users declare (1) whether to use local evaluation, and (2) which properties to supply explicitly for that evaluation, every time they want to send feature flags. It also supports the old boolean behavior if folks don't care and would rather the SDK infer it.
22
+
23
+ Now, you can make calls like this
24
+
25
+ ```ts
26
+ posthog.captureImmediate({
27
+ distinctId: "user123",
28
+ event: "test event",
29
+ sendFeatureFlags: {
30
+ onlyEvaluateLocally: true,
31
+ personProperties: {
32
+ plan: "premium",
33
+ },
34
+ },
35
+ properties: {
36
+ foo: "bar",
37
+ },
38
+ });
39
+ ```
40
+
41
+ or simply
42
+
43
+ ```
44
+ posthog.captureImmediate({
45
+ distinctId: "user123",
46
+ event: "test event",
47
+ sendFeatureFlags: true // this will still infer local evaluation if it appears to be configured, but it won't try to pull properties from the event message
48
+ properties: {
49
+ foo: "bar",
50
+ },
51
+ });
52
+ ```
53
+
54
+ # 5.4.0 – 2025-07-09
4
55
 
5
56
  feat: respect local evaluation preferences with `sendFeatureFlags`; add property overrides from the event to those local computations so that the locally evaluated flags can be more accuratee. NB: this change chagnes the default behavior of `capture` and `captureImmediately` – we will now only send feature flag data along with those events if `sendFeatureFlags` is explicitly specified, instead of optimistically sending along locally evaluated flags by default.
6
57
 
@@ -31,6 +82,7 @@ feat: respect local evaluation preferences with `sendFeatureFlags`; add property
31
82
  ## Breaking changes
32
83
 
33
84
  1. feat: migrate to native fetch, Node 20+ required
85
+ 2. PostHog Node now compresses messages with GZip before sending them to our servers when the runtime supports compression. This reduces network bandwidth and improves performance. Network traffic interceptors and test assertions on payloads must handle GZip decompression to inspect the data. Alternatively, you can disable compression by setting `disableCompression: true` in the client configuration during tests.
34
86
 
35
87
  # 5.0.0-alpha.1 - 2025-04-29
36
88
 
@@ -937,7 +937,7 @@ function setupExpressErrorHandler(_posthog, app) {
937
937
  });
938
938
  }
939
939
 
940
- var version = "5.4.0";
940
+ var version = "5.5.1";
941
941
 
942
942
  var PostHogPersistedProperty;
943
943
  (function (PostHogPersistedProperty) {
@@ -2232,7 +2232,7 @@ class FeatureFlagsPoller {
2232
2232
  }
2233
2233
  } catch (e) {
2234
2234
  if (e instanceof InconclusiveMatchError) {
2235
- this.onError?.(new Error(`Unable to compute flag locally: ${flag.key} - ${e.message}`));
2235
+ this.logMsgIfDebug(() => console.debug(`InconclusiveMatchError when computing flag locally: ${flag.key}: ${e}`));
2236
2236
  } else if (e instanceof Error) {
2237
2237
  this.onError?.(new Error(`Error computing flag locally: ${flag.key}: ${e}`));
2238
2238
  }
@@ -2329,6 +2329,9 @@ class FeatureFlagsPoller {
2329
2329
  let matches = false;
2330
2330
  if (propertyType === 'cohort') {
2331
2331
  matches = matchCohort(prop, properties, this.cohorts, this.debugMode);
2332
+ } else if (propertyType === 'flag') {
2333
+ this.logMsgIfDebug(() => console.warn(`[FEATURE FLAGS] Flag dependency filters are not supported in local evaluation. ` + `Skipping condition for flag '${flag.key}' with dependency on flag '${prop.key || 'unknown'}'`));
2334
+ continue;
2332
2335
  } else {
2333
2336
  matches = matchProperty(prop, properties, warnFunction);
2334
2337
  }
@@ -2666,6 +2669,11 @@ function matchPropertyGroup(propertyGroup, propertyValues, cohortProperties, deb
2666
2669
  let matches;
2667
2670
  if (prop.type === 'cohort') {
2668
2671
  matches = matchCohort(prop, propertyValues, cohortProperties, debugMode);
2672
+ } else if (prop.type === 'flag') {
2673
+ if (debugMode) {
2674
+ console.warn(`[FEATURE FLAGS] Flag dependency filters are not supported in local evaluation. ` + `Skipping condition with dependency on flag '${prop.key || 'unknown'}'`);
2675
+ }
2676
+ continue;
2669
2677
  } else {
2670
2678
  matches = matchProperty(prop, propertyValues);
2671
2679
  }
@@ -2861,7 +2869,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
2861
2869
  const capturePromise = Promise.resolve().then(async () => {
2862
2870
  if (sendFeatureFlags) {
2863
2871
  // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2864
- return await this.getFeatureFlagsForEvent(distinctId, groups, properties, disableGeoip);
2872
+ const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2873
+ return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2865
2874
  }
2866
2875
  if (event === '$feature_flag_called') {
2867
2876
  // 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.
@@ -2918,7 +2927,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
2918
2927
  const capturePromise = Promise.resolve().then(async () => {
2919
2928
  if (sendFeatureFlags) {
2920
2929
  // If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
2921
- return await this.getFeatureFlagsForEvent(distinctId, groups, properties, disableGeoip);
2930
+ const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
2931
+ return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
2922
2932
  }
2923
2933
  if (event === '$feature_flag_called') {
2924
2934
  // 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.
@@ -3285,13 +3295,32 @@ class PostHogBackendClient extends PostHogCoreStateless {
3285
3295
  groupProperties
3286
3296
  };
3287
3297
  }
3288
- async getFeatureFlagsForEvent(distinctId, groups, eventProperties, disableGeoip) {
3289
- // Extract person and group properties from the event properties
3290
- const {
3291
- personProperties: cleanPersonProperties,
3292
- groupProperties: cleanGroupProperties
3293
- } = this.extractPropertiesFromEvent(eventProperties, groups);
3294
- // Prefer local evaluation if available
3298
+ async getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions) {
3299
+ // Use properties directly from options if they exist
3300
+ const finalPersonProperties = sendFeatureFlagsOptions?.personProperties || {};
3301
+ const finalGroupProperties = sendFeatureFlagsOptions?.groupProperties || {};
3302
+ // Check if we should only evaluate locally
3303
+ const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? false;
3304
+ // If onlyEvaluateLocally is true, only use local evaluation
3305
+ if (onlyEvaluateLocally) {
3306
+ if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
3307
+ const groupsWithStringValues = {};
3308
+ for (const [key, value] of Object.entries(groups || {})) {
3309
+ groupsWithStringValues[key] = String(value);
3310
+ }
3311
+ return await this.getAllFlags(distinctId, {
3312
+ groups: groupsWithStringValues,
3313
+ personProperties: finalPersonProperties,
3314
+ groupProperties: finalGroupProperties,
3315
+ disableGeoip,
3316
+ onlyEvaluateLocally: true
3317
+ });
3318
+ } else {
3319
+ // If onlyEvaluateLocally is true but we don't have local flags, return empty
3320
+ return {};
3321
+ }
3322
+ }
3323
+ // Prefer local evaluation if available (default behavior; I'd rather not penalize users who haven't updated to the new API but still want to use local evaluation)
3295
3324
  if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
3296
3325
  const groupsWithStringValues = {};
3297
3326
  for (const [key, value] of Object.entries(groups || {})) {
@@ -3299,14 +3328,14 @@ class PostHogBackendClient extends PostHogCoreStateless {
3299
3328
  }
3300
3329
  return await this.getAllFlags(distinctId, {
3301
3330
  groups: groupsWithStringValues,
3302
- personProperties: cleanPersonProperties,
3303
- groupProperties: cleanGroupProperties,
3331
+ personProperties: finalPersonProperties,
3332
+ groupProperties: finalGroupProperties,
3304
3333
  disableGeoip,
3305
3334
  onlyEvaluateLocally: true
3306
3335
  });
3307
3336
  }
3308
- // Fall back to remote evaluation if local evaluation is not available/is not being used
3309
- return (await super.getFeatureFlagsStateless(distinctId, groups, cleanPersonProperties, cleanGroupProperties, disableGeoip)).flags;
3337
+ // Fall back to remote evaluation if local evaluation is not available
3338
+ return (await super.getFeatureFlagsStateless(distinctId, groups, finalPersonProperties, finalGroupProperties, disableGeoip)).flags;
3310
3339
  }
3311
3340
  addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
3312
3341
  const allPersonProperties = {