posthog-node 3.6.2 → 4.0.0-beta.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 +11 -0
- package/lib/index.cjs.js +1128 -2255
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +47 -25
- package/lib/index.esm.js +1128 -2255
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +44 -30
- package/lib/posthog-core/src/types.d.ts +19 -3
- package/lib/posthog-core/src/utils.d.ts +5 -4
- package/lib/posthog-core/src/vendor/uuidv7.d.ts +179 -0
- package/lib/posthog-node/src/posthog-node.d.ts +7 -8
- package/lib/posthog-node/src/types.d.ts +3 -1
- package/lib/posthog-node/test/test-utils.d.ts +8 -0
- package/package.json +1 -1
- package/src/posthog-node.ts +22 -14
- package/src/types.ts +3 -1
- package/test/feature-flags.spec.ts +63 -133
- package/test/posthog-node.spec.ts +1 -2
- package/test/test-utils.ts +64 -0
|
@@ -4,6 +4,7 @@ import { PostHog as PostHog, PostHogOptions } from '../src/posthog-node'
|
|
|
4
4
|
import { matchProperty, InconclusiveMatchError, relativeDateParseForFeatureFlagMatching } from '../src/feature-flags'
|
|
5
5
|
jest.mock('../src/fetch')
|
|
6
6
|
import fetch from '../src/fetch'
|
|
7
|
+
import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
|
|
7
8
|
|
|
8
9
|
jest.spyOn(console, 'debug').mockImplementation()
|
|
9
10
|
|
|
@@ -13,71 +14,6 @@ const posthogImmediateResolveOptions: PostHogOptions = {
|
|
|
13
14
|
fetchRetryCount: 0,
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const apiImplementation = ({
|
|
17
|
-
localFlags,
|
|
18
|
-
decideFlags,
|
|
19
|
-
decideFlagPayloads,
|
|
20
|
-
decideStatus = 200,
|
|
21
|
-
}: {
|
|
22
|
-
localFlags?: any
|
|
23
|
-
decideFlags?: any
|
|
24
|
-
decideFlagPayloads?: any
|
|
25
|
-
decideStatus?: number
|
|
26
|
-
}) => {
|
|
27
|
-
return (url: any): Promise<any> => {
|
|
28
|
-
if ((url as any).includes('/decide/')) {
|
|
29
|
-
return Promise.resolve({
|
|
30
|
-
status: decideStatus,
|
|
31
|
-
text: () => Promise.resolve('ok'),
|
|
32
|
-
json: () => {
|
|
33
|
-
if (decideStatus !== 200) {
|
|
34
|
-
return Promise.resolve(decideFlags)
|
|
35
|
-
} else {
|
|
36
|
-
return Promise.resolve({
|
|
37
|
-
featureFlags: decideFlags,
|
|
38
|
-
featureFlagPayloads: decideFlagPayloads,
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
}) as any
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if ((url as any).includes('api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts')) {
|
|
46
|
-
return Promise.resolve({
|
|
47
|
-
status: 200,
|
|
48
|
-
text: () => Promise.resolve('ok'),
|
|
49
|
-
json: () => Promise.resolve(localFlags),
|
|
50
|
-
}) as any
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if ((url as any).includes('batch/')) {
|
|
54
|
-
return Promise.resolve({
|
|
55
|
-
status: 200,
|
|
56
|
-
text: () => Promise.resolve('ok'),
|
|
57
|
-
json: () =>
|
|
58
|
-
Promise.resolve({
|
|
59
|
-
status: 'ok',
|
|
60
|
-
}),
|
|
61
|
-
}) as any
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return Promise.resolve({
|
|
65
|
-
status: 400,
|
|
66
|
-
text: () => Promise.resolve('ok'),
|
|
67
|
-
json: () =>
|
|
68
|
-
Promise.resolve({
|
|
69
|
-
status: 'ok',
|
|
70
|
-
}),
|
|
71
|
-
}) as any
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const anyLocalEvalCall = [
|
|
76
|
-
'http://example.com/api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts',
|
|
77
|
-
expect.any(Object),
|
|
78
|
-
]
|
|
79
|
-
export const anyDecideCall = ['http://example.com/decide/?v=3', expect.any(Object)]
|
|
80
|
-
|
|
81
17
|
describe('local evaluation', () => {
|
|
82
18
|
let posthog: PostHog
|
|
83
19
|
|
|
@@ -2051,80 +1987,74 @@ describe('match properties', () => {
|
|
|
2051
1987
|
expect(matchProperty(property_d, { key: '2022-04-05 11:34:13 +00:00' })).toBe(false)
|
|
2052
1988
|
})
|
|
2053
1989
|
|
|
2054
|
-
it(
|
|
1990
|
+
it.each([
|
|
1991
|
+
['is_date_before', '-6h', '2022-03-01', true],
|
|
1992
|
+
['is_date_before', '-6h', '2022-04-30', true],
|
|
1993
|
+
// :TRICKY: MonthIndex is 0 indexed, so 3 is actually the 4th month, April.
|
|
1994
|
+
['is_date_before', '-6h', new Date(Date.UTC(2022, 3, 30, 1, 2, 3)), true],
|
|
1995
|
+
// false because date comparison, instead of datetime, so reduces to same date
|
|
1996
|
+
['is_date_before', '-6h', new Date('2022-04-30T01:02:03+02:00'), true], // europe/madrid
|
|
1997
|
+
['is_date_before', '-6h', new Date('2022-04-30T20:02:03+02:00'), false], // europe/madrid
|
|
1998
|
+
['is_date_before', '-6h', new Date('2022-04-30T19:59:03+02:00'), true], // europe/madrid
|
|
1999
|
+
['is_date_before', '-6h', new Date('2022-04-30'), true],
|
|
2000
|
+
['is_date_before', '-6h', '2022-05-30', false],
|
|
2001
|
+
// is date after
|
|
2002
|
+
['is_date_after', '1h', '2022-05-02', true],
|
|
2003
|
+
['is_date_after', '1h', '2022-05-30', true],
|
|
2004
|
+
['is_date_after', '1h', new Date(2022, 4, 30), true],
|
|
2005
|
+
['is_date_after', '1h', new Date('2022-05-30'), true],
|
|
2006
|
+
['is_date_after', '1h', '2022-04-30', false],
|
|
2007
|
+
// # Try all possible relative dates
|
|
2008
|
+
['is_date_before', '1h', '2022-05-01 00:00:00', false],
|
|
2009
|
+
['is_date_before', '1h', '2022-04-30 22:00:00', true],
|
|
2010
|
+
['is_date_before', '-1d', '2022-04-29 23:59:00 GMT', true],
|
|
2011
|
+
['is_date_before', '-1d', '2022-04-30 00:00:01 GMT', false],
|
|
2012
|
+
['is_date_before', '1w', '2022-04-23 00:00:00 GMT', true],
|
|
2013
|
+
['is_date_before', '1w', '2022-04-24 00:00:00 GMT', false],
|
|
2014
|
+
['is_date_before', '1w', '2022-04-24 00:00:01 GMT', false],
|
|
2015
|
+
['is_date_before', '1m', '2022-03-01 00:00:00 GMT', true],
|
|
2016
|
+
['is_date_before', '1m', '2022-04-01 00:00:00 GMT', false],
|
|
2017
|
+
['is_date_before', '1m', '2022-04-05 00:00:01 GMT', false],
|
|
2018
|
+
|
|
2019
|
+
['is_date_before', '-1y', '2021-04-28 00:00:00 GMT', true],
|
|
2020
|
+
['is_date_before', '-1y', '2021-05-01 00:00:01 GMT', false],
|
|
2021
|
+
|
|
2022
|
+
['is_date_after', '122h', '2022-05-01 00:00:00 GMT', true],
|
|
2023
|
+
['is_date_after', '122h', '2022-04-23 01:00:00 GMT', false],
|
|
2024
|
+
|
|
2025
|
+
['is_date_after', '2d', '2022-05-01 00:00:00 GMT', true],
|
|
2026
|
+
['is_date_after', '2d', '2022-04-29 00:00:01 GMT', true],
|
|
2027
|
+
['is_date_after', '2d', '2022-04-29 00:00:00 GMT', false],
|
|
2028
|
+
|
|
2029
|
+
['is_date_after', '02w', '2022-05-01 00:00:00 GMT', true],
|
|
2030
|
+
['is_date_after', '02w', '2022-04-16 00:00:00 GMT', false],
|
|
2031
|
+
|
|
2032
|
+
['is_date_after', '-1m', '2022-04-01 00:00:01 GMT', true],
|
|
2033
|
+
['is_date_after', '-1m', '2022-04-01 00:00:00 GMT', false],
|
|
2034
|
+
|
|
2035
|
+
['is_date_after', '1y', '2022-05-01 00:00:00 GMT', true],
|
|
2036
|
+
['is_date_after', '1y', '2021-05-01 00:00:01 GMT', true],
|
|
2037
|
+
['is_date_after', '1y', '2021-05-01 00:00:00 GMT', false],
|
|
2038
|
+
['is_date_after', '1y', '2021-04-30 00:00:00 GMT', false],
|
|
2039
|
+
['is_date_after', '1y', '2021-03-01 12:13:00 GMT', false],
|
|
2040
|
+
])('with relative date operators: %s, %s, %s', (operator, value, date, expectation) => {
|
|
2055
2041
|
jest.setSystemTime(new Date('2022-05-01'))
|
|
2042
|
+
expect(matchProperty({ key: 'key', value, operator }, { key: date })).toBe(expectation)
|
|
2056
2043
|
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
expect(matchProperty(property_a, { key: '2022-04-30' })).toBe(true)
|
|
2044
|
+
return
|
|
2045
|
+
})
|
|
2060
2046
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
// false because date comparison, instead of datetime, so reduces to same date
|
|
2064
|
-
expect(matchProperty(property_a, { key: new Date(2022, 3, 30, 19, 2, 3) })).toBe(false)
|
|
2065
|
-
expect(matchProperty(property_a, { key: new Date('2022-04-30T01:02:03+02:00') })).toBe(true) // europe/madrid
|
|
2066
|
-
expect(matchProperty(property_a, { key: new Date('2022-04-30T20:02:03+02:00') })).toBe(false) // europe/madrid
|
|
2067
|
-
expect(matchProperty(property_a, { key: new Date('2022-04-30T19:59:03+02:00') })).toBe(true) // europe/madrid
|
|
2068
|
-
expect(matchProperty(property_a, { key: new Date('2022-04-30') })).toBe(true)
|
|
2069
|
-
expect(matchProperty(property_a, { key: '2022-05-30' })).toBe(false)
|
|
2047
|
+
it('with relative date operators handles invalid keys', () => {
|
|
2048
|
+
jest.setSystemTime(new Date('2022-05-01'))
|
|
2070
2049
|
|
|
2071
2050
|
// # can't be an invalid string
|
|
2072
|
-
expect(() => matchProperty(
|
|
2051
|
+
expect(() => matchProperty({ key: 'key', value: '1d', operator: 'is_date_before' }, { key: 'abcdef' })).toThrow(
|
|
2052
|
+
InconclusiveMatchError
|
|
2053
|
+
)
|
|
2073
2054
|
// however js understands numbers as date offsets from utc epoch
|
|
2074
|
-
expect(() => matchProperty(
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
expect(matchProperty(property_b, { key: '2022-05-02' })).toBe(true)
|
|
2078
|
-
expect(matchProperty(property_b, { key: '2022-05-30' })).toBe(true)
|
|
2079
|
-
expect(matchProperty(property_b, { key: new Date(2022, 4, 30) })).toBe(true)
|
|
2080
|
-
expect(matchProperty(property_b, { key: new Date('2022-05-30') })).toBe(true)
|
|
2081
|
-
expect(matchProperty(property_b, { key: '2022-04-30' })).toBe(false)
|
|
2082
|
-
|
|
2083
|
-
// # Try all possible relative dates
|
|
2084
|
-
const property_e = { key: 'key', value: '1h', operator: 'is_date_before' }
|
|
2085
|
-
expect(matchProperty(property_e, { key: '2022-05-01 00:00:00' })).toBe(false)
|
|
2086
|
-
expect(matchProperty(property_e, { key: '2022-04-30 22:00:00' })).toBe(true)
|
|
2087
|
-
|
|
2088
|
-
const property_f = { key: 'key', value: '-1d', operator: 'is_date_before' }
|
|
2089
|
-
expect(matchProperty(property_f, { key: '2022-04-29 23:59:00 GMT' })).toBe(true)
|
|
2090
|
-
expect(matchProperty(property_f, { key: '2022-04-30 00:00:01 GMT' })).toBe(false)
|
|
2091
|
-
|
|
2092
|
-
const property_g = { key: 'key', value: '1w', operator: 'is_date_before' }
|
|
2093
|
-
expect(matchProperty(property_g, { key: '2022-04-23 00:00:00 GMT' })).toBe(true)
|
|
2094
|
-
expect(matchProperty(property_g, { key: '2022-04-24 00:00:00 GMT' })).toBe(false)
|
|
2095
|
-
expect(matchProperty(property_g, { key: '2022-04-24 00:00:01 GMT' })).toBe(false)
|
|
2096
|
-
|
|
2097
|
-
const property_h = { key: 'key', value: '1m', operator: 'is_date_before' }
|
|
2098
|
-
expect(matchProperty(property_h, { key: '2022-03-01 00:00:00 GMT' })).toBe(true)
|
|
2099
|
-
expect(matchProperty(property_h, { key: '2022-04-05 00:00:00 GMT' })).toBe(false)
|
|
2100
|
-
|
|
2101
|
-
const property_i = { key: 'key', value: '-1y', operator: 'is_date_before' }
|
|
2102
|
-
expect(matchProperty(property_i, { key: '2021-04-28 00:00:00 GMT' })).toBe(true)
|
|
2103
|
-
expect(matchProperty(property_i, { key: '2021-05-01 00:00:01 GMT' })).toBe(false)
|
|
2104
|
-
|
|
2105
|
-
const property_j = { key: 'key', value: '122h', operator: 'is_date_after' }
|
|
2106
|
-
expect(matchProperty(property_j, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
|
|
2107
|
-
expect(matchProperty(property_j, { key: '2022-04-23 01:00:00 GMT' })).toBe(false)
|
|
2108
|
-
|
|
2109
|
-
const property_k = { key: 'key', value: '2d', operator: 'is_date_after' }
|
|
2110
|
-
expect(matchProperty(property_k, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
|
|
2111
|
-
expect(matchProperty(property_k, { key: '2022-04-29 00:00:01 GMT' })).toBe(true)
|
|
2112
|
-
expect(matchProperty(property_k, { key: '2022-04-29 00:00:00 GMT' })).toBe(false)
|
|
2113
|
-
|
|
2114
|
-
const property_l = { key: 'key', value: '02w', operator: 'is_date_after' }
|
|
2115
|
-
expect(matchProperty(property_l, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
|
|
2116
|
-
expect(matchProperty(property_l, { key: '2022-04-16 00:00:00 GMT' })).toBe(false)
|
|
2117
|
-
|
|
2118
|
-
const property_m = { key: 'key', value: '-1m', operator: 'is_date_after' }
|
|
2119
|
-
expect(matchProperty(property_m, { key: '2022-04-01 00:00:01 GMT' })).toBe(true)
|
|
2120
|
-
expect(matchProperty(property_m, { key: '2022-04-01 00:00:00 GMT' })).toBe(false)
|
|
2121
|
-
|
|
2122
|
-
const property_n = { key: 'key', value: '1y', operator: 'is_date_after' }
|
|
2123
|
-
expect(matchProperty(property_n, { key: '2022-05-01 00:00:00 GMT' })).toBe(true)
|
|
2124
|
-
expect(matchProperty(property_n, { key: '2021-05-01 00:00:01 GMT' })).toBe(true)
|
|
2125
|
-
expect(matchProperty(property_n, { key: '2021-05-01 00:00:00 GMT' })).toBe(false)
|
|
2126
|
-
expect(matchProperty(property_n, { key: '2021-04-30 00:00:00 GMT' })).toBe(false)
|
|
2127
|
-
expect(matchProperty(property_n, { key: '2021-03-01 12:13:00 GMT' })).toBe(false)
|
|
2055
|
+
expect(() => matchProperty({ key: 'key', value: '1d', operator: 'is_date_before' }, { key: 1 })).not.toThrow(
|
|
2056
|
+
InconclusiveMatchError
|
|
2057
|
+
)
|
|
2128
2058
|
})
|
|
2129
2059
|
|
|
2130
2060
|
it('null or undefined property value', () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { PostHog as PostHog } from '../src/posthog-node'
|
|
3
3
|
jest.mock('../src/fetch')
|
|
4
4
|
import fetch from '../src/fetch'
|
|
5
|
-
import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './
|
|
5
|
+
import { anyDecideCall, anyLocalEvalCall, apiImplementation } from './test-utils'
|
|
6
6
|
import { waitForPromises, wait } from '../../posthog-core/test/test-utils/test-utils'
|
|
7
7
|
import { randomUUID } from 'crypto'
|
|
8
8
|
|
|
@@ -260,7 +260,6 @@ describe('PostHog Node.js', () => {
|
|
|
260
260
|
await waitForPromises()
|
|
261
261
|
jest.runOnlyPendingTimers()
|
|
262
262
|
batchEvents = getLastBatchEvents()
|
|
263
|
-
console.warn(batchEvents)
|
|
264
263
|
expect(batchEvents?.[0].properties).toEqual({
|
|
265
264
|
$groups: { org: 123 },
|
|
266
265
|
foo: 'bar',
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const apiImplementation = ({
|
|
2
|
+
localFlags,
|
|
3
|
+
decideFlags,
|
|
4
|
+
decideFlagPayloads,
|
|
5
|
+
decideStatus = 200,
|
|
6
|
+
}: {
|
|
7
|
+
localFlags?: any
|
|
8
|
+
decideFlags?: any
|
|
9
|
+
decideFlagPayloads?: any
|
|
10
|
+
decideStatus?: number
|
|
11
|
+
}) => {
|
|
12
|
+
return (url: any): Promise<any> => {
|
|
13
|
+
if ((url as any).includes('/decide/')) {
|
|
14
|
+
return Promise.resolve({
|
|
15
|
+
status: decideStatus,
|
|
16
|
+
text: () => Promise.resolve('ok'),
|
|
17
|
+
json: () => {
|
|
18
|
+
if (decideStatus !== 200) {
|
|
19
|
+
return Promise.resolve(decideFlags)
|
|
20
|
+
} else {
|
|
21
|
+
return Promise.resolve({
|
|
22
|
+
featureFlags: decideFlags,
|
|
23
|
+
featureFlagPayloads: decideFlagPayloads,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
}) as any
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if ((url as any).includes('api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts')) {
|
|
31
|
+
return Promise.resolve({
|
|
32
|
+
status: 200,
|
|
33
|
+
text: () => Promise.resolve('ok'),
|
|
34
|
+
json: () => Promise.resolve(localFlags),
|
|
35
|
+
}) as any
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ((url as any).includes('batch/')) {
|
|
39
|
+
return Promise.resolve({
|
|
40
|
+
status: 200,
|
|
41
|
+
text: () => Promise.resolve('ok'),
|
|
42
|
+
json: () =>
|
|
43
|
+
Promise.resolve({
|
|
44
|
+
status: 'ok',
|
|
45
|
+
}),
|
|
46
|
+
}) as any
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
status: 400,
|
|
51
|
+
text: () => Promise.resolve('ok'),
|
|
52
|
+
json: () =>
|
|
53
|
+
Promise.resolve({
|
|
54
|
+
status: 'ok',
|
|
55
|
+
}),
|
|
56
|
+
}) as any
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const anyLocalEvalCall = [
|
|
61
|
+
'http://example.com/api/feature_flag/local_evaluation?token=TEST_API_KEY&send_cohorts',
|
|
62
|
+
expect.any(Object),
|
|
63
|
+
]
|
|
64
|
+
export const anyDecideCall = ['http://example.com/decide/?v=3', expect.any(Object)]
|