posthog-node 5.3.1 → 5.5.0
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 +52 -0
- package/lib/edge/index.cjs +79 -38
- package/lib/edge/index.cjs.map +1 -1
- package/lib/edge/index.mjs +79 -38
- package/lib/edge/index.mjs.map +1 -1
- package/lib/index.d.ts +9 -2
- package/lib/node/index.cjs +79 -38
- package/lib/node/index.cjs.map +1 -1
- package/lib/node/index.mjs +79 -38
- package/lib/node/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Next
|
|
2
2
|
|
|
3
|
+
# 5.5.0 – 2025-07-10
|
|
4
|
+
|
|
5
|
+
1. feat: make the `sendFeatureFlags` parameter more declarative and ergonomic. Implementation notes below:
|
|
6
|
+
|
|
7
|
+
Modified `sendFeatureFlags` to be type `boolean | SendFeatureFlagsOptions`, (which is defined thusly)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export interface SendFeatureFlagsOptions {
|
|
11
|
+
onlyEvaluateLocally?: boolean
|
|
12
|
+
personProperties?: Record<string, any>
|
|
13
|
+
groupProperties?: Record<string, Record<string, any>>
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
Now, you can make calls like this
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
posthog.captureImmediate({
|
|
23
|
+
distinctId: "user123",
|
|
24
|
+
event: "test event",
|
|
25
|
+
sendFeatureFlags: {
|
|
26
|
+
onlyEvaluateLocally: true,
|
|
27
|
+
personProperties: {
|
|
28
|
+
plan: "premium",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
properties: {
|
|
32
|
+
foo: "bar",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
or simply
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
posthog.captureImmediate({
|
|
41
|
+
distinctId: "user123",
|
|
42
|
+
event: "test event",
|
|
43
|
+
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
|
|
44
|
+
properties: {
|
|
45
|
+
foo: "bar",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
# 5.4.0 – 2025-07-09
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
3
54
|
# 5.3.1 - 2025-07-07
|
|
4
55
|
|
|
5
56
|
1. feat: decouple feature flag local evaluation from personal API keys; support decrypting remote config payloads without relying on the feature flags poller
|
|
@@ -27,6 +78,7 @@
|
|
|
27
78
|
## Breaking changes
|
|
28
79
|
|
|
29
80
|
1. feat: migrate to native fetch, Node 20+ required
|
|
81
|
+
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.
|
|
30
82
|
|
|
31
83
|
# 5.0.0-alpha.1 - 2025-04-29
|
|
32
84
|
|
package/lib/edge/index.cjs
CHANGED
|
@@ -937,7 +937,7 @@ function setupExpressErrorHandler(_posthog, app) {
|
|
|
937
937
|
});
|
|
938
938
|
}
|
|
939
939
|
|
|
940
|
-
var version = "5.
|
|
940
|
+
var version = "5.5.0";
|
|
941
941
|
|
|
942
942
|
var PostHogPersistedProperty;
|
|
943
943
|
(function (PostHogPersistedProperty) {
|
|
@@ -2231,7 +2231,9 @@ class FeatureFlagsPoller {
|
|
|
2231
2231
|
payloads[flag.key] = matchPayload;
|
|
2232
2232
|
}
|
|
2233
2233
|
} catch (e) {
|
|
2234
|
-
if (e instanceof InconclusiveMatchError)
|
|
2234
|
+
if (e instanceof InconclusiveMatchError) {
|
|
2235
|
+
this.onError?.(new Error(`Unable to compute flag locally: ${flag.key} - ${e.message}`));
|
|
2236
|
+
} else if (e instanceof Error) {
|
|
2235
2237
|
this.onError?.(new Error(`Error computing flag locally: ${flag.key}: ${e}`));
|
|
2236
2238
|
}
|
|
2237
2239
|
fallbackToFlags = true;
|
|
@@ -2855,32 +2857,17 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
2855
2857
|
uuid
|
|
2856
2858
|
});
|
|
2857
2859
|
};
|
|
2858
|
-
const _getFlags = async (distinctId, groups, disableGeoip) => {
|
|
2859
|
-
return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
|
|
2860
|
-
};
|
|
2861
2860
|
// :TRICKY: If we flush, or need to shut down, to not lose events we want this promise to resolve before we flush
|
|
2862
2861
|
const capturePromise = Promise.resolve().then(async () => {
|
|
2863
2862
|
if (sendFeatureFlags) {
|
|
2864
|
-
// If we are sending feature flags, we
|
|
2865
|
-
|
|
2866
|
-
return await
|
|
2863
|
+
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
2864
|
+
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
2865
|
+
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
2867
2866
|
}
|
|
2868
2867
|
if (event === '$feature_flag_called') {
|
|
2869
2868
|
// 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.
|
|
2870
2869
|
return {};
|
|
2871
2870
|
}
|
|
2872
|
-
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
2873
|
-
// Otherwise we may as well check for the flags locally and include them if they are already loaded
|
|
2874
|
-
const groupsWithStringValues = {};
|
|
2875
|
-
for (const [key, value] of Object.entries(groups || {})) {
|
|
2876
|
-
groupsWithStringValues[key] = String(value);
|
|
2877
|
-
}
|
|
2878
|
-
return await this.getAllFlags(distinctId, {
|
|
2879
|
-
groups: groupsWithStringValues,
|
|
2880
|
-
disableGeoip,
|
|
2881
|
-
onlyEvaluateLocally: true
|
|
2882
|
-
});
|
|
2883
|
-
}
|
|
2884
2871
|
return {};
|
|
2885
2872
|
}).then(flags => {
|
|
2886
2873
|
// Derive the relevant flag properties to add
|
|
@@ -2929,31 +2916,16 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
2929
2916
|
uuid
|
|
2930
2917
|
});
|
|
2931
2918
|
};
|
|
2932
|
-
const _getFlags = async (distinctId, groups, disableGeoip) => {
|
|
2933
|
-
return (await super.getFeatureFlagsStateless(distinctId, groups, undefined, undefined, disableGeoip)).flags;
|
|
2934
|
-
};
|
|
2935
2919
|
const capturePromise = Promise.resolve().then(async () => {
|
|
2936
2920
|
if (sendFeatureFlags) {
|
|
2937
|
-
// If we are sending feature flags, we
|
|
2938
|
-
|
|
2939
|
-
return await
|
|
2921
|
+
// If we are sending feature flags, we evaluate them locally if the user prefers it, otherwise we fall back to remote evaluation
|
|
2922
|
+
const sendFeatureFlagsOptions = typeof sendFeatureFlags === 'object' ? sendFeatureFlags : undefined;
|
|
2923
|
+
return await this.getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions);
|
|
2940
2924
|
}
|
|
2941
2925
|
if (event === '$feature_flag_called') {
|
|
2942
2926
|
// 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.
|
|
2943
2927
|
return {};
|
|
2944
2928
|
}
|
|
2945
|
-
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
2946
|
-
// Otherwise we may as well check for the flags locally and include them if they are already loaded
|
|
2947
|
-
const groupsWithStringValues = {};
|
|
2948
|
-
for (const [key, value] of Object.entries(groups || {})) {
|
|
2949
|
-
groupsWithStringValues[key] = String(value);
|
|
2950
|
-
}
|
|
2951
|
-
return await this.getAllFlags(distinctId, {
|
|
2952
|
-
groups: groupsWithStringValues,
|
|
2953
|
-
disableGeoip,
|
|
2954
|
-
onlyEvaluateLocally: true
|
|
2955
|
-
});
|
|
2956
|
-
}
|
|
2957
2929
|
return {};
|
|
2958
2930
|
}).then(flags => {
|
|
2959
2931
|
// Derive the relevant flag properties to add
|
|
@@ -3288,6 +3260,75 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
3288
3260
|
}
|
|
3289
3261
|
}
|
|
3290
3262
|
}
|
|
3263
|
+
extractPropertiesFromEvent(eventProperties, groups) {
|
|
3264
|
+
if (!eventProperties) {
|
|
3265
|
+
return {
|
|
3266
|
+
personProperties: {},
|
|
3267
|
+
groupProperties: {}
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
const personProperties = {};
|
|
3271
|
+
const groupProperties = {};
|
|
3272
|
+
for (const [key, value] of Object.entries(eventProperties)) {
|
|
3273
|
+
// If the value is a plain object and the key exists in groups, treat it as group properties
|
|
3274
|
+
if (isPlainObject(value) && groups && key in groups) {
|
|
3275
|
+
const groupProps = {};
|
|
3276
|
+
for (const [groupKey, groupValue] of Object.entries(value)) {
|
|
3277
|
+
groupProps[String(groupKey)] = String(groupValue);
|
|
3278
|
+
}
|
|
3279
|
+
groupProperties[String(key)] = groupProps;
|
|
3280
|
+
} else {
|
|
3281
|
+
// Otherwise treat as person property
|
|
3282
|
+
personProperties[String(key)] = String(value);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
return {
|
|
3286
|
+
personProperties,
|
|
3287
|
+
groupProperties
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
async getFeatureFlagsForEvent(distinctId, groups, disableGeoip, sendFeatureFlagsOptions) {
|
|
3291
|
+
// Use properties directly from options if they exist
|
|
3292
|
+
const finalPersonProperties = sendFeatureFlagsOptions?.personProperties || {};
|
|
3293
|
+
const finalGroupProperties = sendFeatureFlagsOptions?.groupProperties || {};
|
|
3294
|
+
// Check if we should only evaluate locally
|
|
3295
|
+
const onlyEvaluateLocally = sendFeatureFlagsOptions?.onlyEvaluateLocally ?? false;
|
|
3296
|
+
// If onlyEvaluateLocally is true, only use local evaluation
|
|
3297
|
+
if (onlyEvaluateLocally) {
|
|
3298
|
+
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
3299
|
+
const groupsWithStringValues = {};
|
|
3300
|
+
for (const [key, value] of Object.entries(groups || {})) {
|
|
3301
|
+
groupsWithStringValues[key] = String(value);
|
|
3302
|
+
}
|
|
3303
|
+
return await this.getAllFlags(distinctId, {
|
|
3304
|
+
groups: groupsWithStringValues,
|
|
3305
|
+
personProperties: finalPersonProperties,
|
|
3306
|
+
groupProperties: finalGroupProperties,
|
|
3307
|
+
disableGeoip,
|
|
3308
|
+
onlyEvaluateLocally: true
|
|
3309
|
+
});
|
|
3310
|
+
} else {
|
|
3311
|
+
// If onlyEvaluateLocally is true but we don't have local flags, return empty
|
|
3312
|
+
return {};
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
// 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)
|
|
3316
|
+
if ((this.featureFlagsPoller?.featureFlags?.length || 0) > 0) {
|
|
3317
|
+
const groupsWithStringValues = {};
|
|
3318
|
+
for (const [key, value] of Object.entries(groups || {})) {
|
|
3319
|
+
groupsWithStringValues[key] = String(value);
|
|
3320
|
+
}
|
|
3321
|
+
return await this.getAllFlags(distinctId, {
|
|
3322
|
+
groups: groupsWithStringValues,
|
|
3323
|
+
personProperties: finalPersonProperties,
|
|
3324
|
+
groupProperties: finalGroupProperties,
|
|
3325
|
+
disableGeoip,
|
|
3326
|
+
onlyEvaluateLocally: true
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
// Fall back to remote evaluation if local evaluation is not available
|
|
3330
|
+
return (await super.getFeatureFlagsStateless(distinctId, groups, finalPersonProperties, finalGroupProperties, disableGeoip)).flags;
|
|
3331
|
+
}
|
|
3291
3332
|
addLocalPersonAndGroupProperties(distinctId, groups, personProperties, groupProperties) {
|
|
3292
3333
|
const allPersonProperties = {
|
|
3293
3334
|
distinct_id: distinctId,
|