@posthog/core 1.41.0 → 1.42.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.
Files changed (50) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +9 -0
  4. package/dist/index.mjs +2 -2
  5. package/dist/metrics/config.d.ts +9 -0
  6. package/dist/metrics/config.d.ts.map +1 -0
  7. package/dist/metrics/config.js +49 -0
  8. package/dist/metrics/config.mjs +15 -0
  9. package/dist/metrics/index.d.ts +8 -1
  10. package/dist/metrics/index.d.ts.map +1 -1
  11. package/dist/metrics/index.js +39 -19
  12. package/dist/metrics/index.mjs +36 -19
  13. package/dist/metrics/types.d.ts +1 -1
  14. package/dist/metrics/types.d.ts.map +1 -1
  15. package/dist/posthog-core-stateless.d.ts +9 -1
  16. package/dist/posthog-core-stateless.d.ts.map +1 -1
  17. package/dist/posthog-core-stateless.js +43 -0
  18. package/dist/posthog-core-stateless.mjs +43 -0
  19. package/dist/surveys/activation.d.ts +20 -0
  20. package/dist/surveys/activation.d.ts.map +1 -0
  21. package/dist/surveys/activation.js +45 -0
  22. package/dist/surveys/activation.mjs +8 -0
  23. package/dist/surveys/events.d.ts +1 -4
  24. package/dist/surveys/events.d.ts.map +1 -1
  25. package/dist/surveys/index.d.ts +2 -0
  26. package/dist/surveys/index.d.ts.map +1 -1
  27. package/dist/surveys/index.js +16 -2
  28. package/dist/surveys/index.mjs +3 -1
  29. package/dist/surveys/keys.d.ts +14 -0
  30. package/dist/surveys/keys.d.ts.map +1 -0
  31. package/dist/surveys/keys.js +45 -0
  32. package/dist/surveys/keys.mjs +8 -0
  33. package/dist/types.d.ts +87 -76
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js +65 -76
  36. package/dist/types.mjs +66 -77
  37. package/package.json +2 -2
  38. package/src/index.ts +3 -0
  39. package/src/metrics/config.ts +23 -0
  40. package/src/metrics/index.spec.ts +49 -0
  41. package/src/metrics/index.ts +66 -24
  42. package/src/metrics/types.ts +1 -0
  43. package/src/posthog-core-stateless.ts +51 -1
  44. package/src/surveys/activation.spec.ts +61 -0
  45. package/src/surveys/activation.ts +28 -0
  46. package/src/surveys/events.ts +1 -5
  47. package/src/surveys/index.ts +2 -0
  48. package/src/surveys/keys.spec.ts +29 -0
  49. package/src/surveys/keys.ts +23 -0
  50. package/src/types.ts +88 -76
@@ -0,0 +1,61 @@
1
+ import { describe, expect, it } from '@jest/globals'
2
+ import { SurveySchedule } from '../types'
3
+ import { canSurveyActivateRepeatedly, doesSurveyActivateByEvent } from './activation'
4
+
5
+ describe('doesSurveyActivateByEvent', () => {
6
+ const cases: [string, Parameters<typeof doesSurveyActivateByEvent>[0], boolean][] = [
7
+ ['non-empty event values', { conditions: { events: { values: [{ name: 'event' }] } } }, true],
8
+ ['empty event values', { conditions: { events: { values: [] } } }, false],
9
+ ['undefined conditions', { conditions: undefined }, false],
10
+ ['null conditions', { conditions: null }, false],
11
+ ['null events', { conditions: { events: null } }, false],
12
+ ]
13
+
14
+ it.each(cases)('%s returns %p', (_name, survey, expected) => {
15
+ expect(doesSurveyActivateByEvent(survey)).toBe(expected)
16
+ })
17
+ })
18
+
19
+ describe('canSurveyActivateRepeatedly', () => {
20
+ const cases: [string, Parameters<typeof canSurveyActivateRepeatedly>[0], boolean][] = [
21
+ ['always schedule without events', { schedule: SurveySchedule.Always }, true],
22
+ [
23
+ 'events with repeated activation enabled',
24
+ {
25
+ schedule: SurveySchedule.Once,
26
+ conditions: { events: { repeatedActivation: true, values: [{ name: 'event' }] } },
27
+ },
28
+ true,
29
+ ],
30
+ [
31
+ 'events with repeated activation disabled',
32
+ {
33
+ schedule: SurveySchedule.Once,
34
+ conditions: { events: { repeatedActivation: false, values: [{ name: 'event' }] } },
35
+ },
36
+ false,
37
+ ],
38
+ [
39
+ 'events without repeated activation',
40
+ {
41
+ schedule: SurveySchedule.Once,
42
+ conditions: { events: { values: [{ name: 'event' }] } },
43
+ },
44
+ false,
45
+ ],
46
+ [
47
+ 'repeated activation without event values',
48
+ {
49
+ schedule: SurveySchedule.Once,
50
+ conditions: { events: { repeatedActivation: true, values: [] } },
51
+ },
52
+ false,
53
+ ],
54
+ ['undefined schedule without events', { schedule: undefined }, false],
55
+ ['null schedule without events', { schedule: null }, false],
56
+ ]
57
+
58
+ it.each(cases)('%s returns %p', (_name, survey, expected) => {
59
+ expect(canSurveyActivateRepeatedly(survey)).toBe(expected)
60
+ })
61
+ })
@@ -0,0 +1,28 @@
1
+ import { Survey, SurveySchedule } from '../types'
2
+
3
+ // conditions is narrowed to the fields these predicates actually read, so both SDKs'
4
+ // Survey shapes (which still drift in nested condition types) satisfy it structurally.
5
+ type SurveyForRepeatActivation = Pick<Survey, 'schedule'> & {
6
+ conditions?: {
7
+ events?: {
8
+ repeatedActivation?: boolean
9
+ values?: { name: string }[]
10
+ } | null
11
+ } | null
12
+ }
13
+
14
+ export function doesSurveyActivateByEvent(survey: SurveyForRepeatActivation): boolean {
15
+ return !!survey.conditions?.events?.values?.length
16
+ }
17
+
18
+ /**
19
+ * Platform-independent part of "can this survey show again after being seen":
20
+ * event-repeated activation or an 'always' schedule. SDKs may OR in
21
+ * platform-specific state (e.g. the web SDK's in-progress partial responses).
22
+ */
23
+ export function canSurveyActivateRepeatedly(survey: SurveyForRepeatActivation): boolean {
24
+ return (
25
+ (doesSurveyActivateByEvent(survey) && !!survey.conditions?.events?.repeatedActivation) ||
26
+ survey.schedule === SurveySchedule.Always
27
+ )
28
+ }
@@ -1,3 +1,4 @@
1
+ import { SurveyWithIteration } from './keys'
1
2
  import { SurveyResponses, SurveyResponseValue } from '../types'
2
3
  import { isArray, isNullish, isUndefined } from '../utils'
3
4
 
@@ -73,8 +74,3 @@ type SurveyQuestionForResponses = {
73
74
  type SurveyForResponses = {
74
75
  questions: SurveyQuestionForResponses[]
75
76
  }
76
-
77
- type SurveyWithIteration = {
78
- id: string
79
- current_iteration?: number | null
80
- }
@@ -16,3 +16,5 @@ export {
16
16
  getLanguageFromStoredPersonProperties,
17
17
  normalizeLanguageCode,
18
18
  } from './translations'
19
+ export { canSurveyActivateRepeatedly, doesSurveyActivateByEvent } from './activation'
20
+ export { getSurveyIterationKey, isSurveyKeyForSurvey, type SurveyWithIteration } from './keys'
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from '@jest/globals'
2
+ import { getSurveyIterationKey, isSurveyKeyForSurvey } from './keys'
3
+
4
+ describe('getSurveyIterationKey', () => {
5
+ const cases: [number | null | undefined, string][] = [
6
+ [2, 'survey-1_2'],
7
+ [1, 'survey-1_1'],
8
+ [0, 'survey-1'],
9
+ [null, 'survey-1'],
10
+ [undefined, 'survey-1'],
11
+ ]
12
+
13
+ it.each(cases)('current_iteration %p produces key %p', (currentIteration, expected) => {
14
+ expect(getSurveyIterationKey({ id: 'survey-1', current_iteration: currentIteration })).toBe(expected)
15
+ })
16
+ })
17
+
18
+ describe('isSurveyKeyForSurvey', () => {
19
+ it.each([
20
+ ['bare id', 'abc', 'abc', true],
21
+ ['first iteration', 'abc_1', 'abc', true],
22
+ ['later iteration', 'abc_12', 'abc', true],
23
+ ['different survey id', 'def_1', 'abc', false],
24
+ ['prefix collision with bare id', 'abcd', 'abc', false],
25
+ ['prefix collision with iteration key', 'abcd_1', 'abc', false],
26
+ ])('%s: key %p for survey %p returns %p', (_name, key, surveyId, expected) => {
27
+ expect(isSurveyKeyForSurvey(key, surveyId)).toBe(expected)
28
+ })
29
+ })
@@ -0,0 +1,23 @@
1
+ import { Survey } from '../types'
2
+
3
+ export type SurveyWithIteration = Pick<Survey, 'id' | 'current_iteration'>
4
+
5
+ /**
6
+ * True when a stored display-state key belongs to the given survey: its bare id
7
+ * or any iteration-qualified `id_n` key.
8
+ */
9
+ export function isSurveyKeyForSurvey(key: string, surveyId: string): boolean {
10
+ return key === surveyId || key.startsWith(`${surveyId}_`)
11
+ }
12
+
13
+ /**
14
+ * Iteration-qualified survey identifier ('id' or 'id_iteration'), used to key
15
+ * per-survey display state (seen, in-progress, ...). Keying by iteration lets a
16
+ * repeating survey become visible again when a new iteration starts.
17
+ */
18
+ export function getSurveyIterationKey(survey: SurveyWithIteration): string {
19
+ if (survey.current_iteration && survey.current_iteration > 0) {
20
+ return `${survey.id}_${survey.current_iteration}`
21
+ }
22
+ return survey.id
23
+ }
package/src/types.ts CHANGED
@@ -633,43 +633,49 @@ export type SurveyAppearance = {
633
633
  widgetColor?: string
634
634
  }
635
635
 
636
- export enum SurveyPosition {
637
- TopLeft = 'top_left',
638
- TopCenter = 'top_center',
639
- TopRight = 'top_right',
640
- MiddleLeft = 'middle_left',
641
- MiddleCenter = 'middle_center',
642
- MiddleRight = 'middle_right',
643
- Left = 'left',
644
- Right = 'right',
645
- Center = 'center',
646
- }
636
+ export const SurveyPosition = {
637
+ TopLeft: 'top_left',
638
+ TopCenter: 'top_center',
639
+ TopRight: 'top_right',
640
+ MiddleLeft: 'middle_left',
641
+ MiddleCenter: 'middle_center',
642
+ MiddleRight: 'middle_right',
643
+ Left: 'left',
644
+ Right: 'right',
645
+ Center: 'center',
646
+ } as const
647
+ export type SurveyPosition = (typeof SurveyPosition)[keyof typeof SurveyPosition]
647
648
 
648
- export enum SurveyWidgetType {
649
- Button = 'button',
650
- Tab = 'tab',
651
- Selector = 'selector',
652
- }
649
+ export const SurveyWidgetType = {
650
+ Button: 'button',
651
+ Tab: 'tab',
652
+ Selector: 'selector',
653
+ } as const
654
+ export type SurveyWidgetType = (typeof SurveyWidgetType)[keyof typeof SurveyWidgetType]
653
655
 
654
- export enum SurveyType {
655
- Popover = 'popover',
656
- API = 'api',
657
- Widget = 'widget',
658
- ExternalSurvey = 'external_survey',
659
- }
656
+ export const SurveyType = {
657
+ Popover: 'popover',
658
+ API: 'api',
659
+ Widget: 'widget',
660
+ ExternalSurvey: 'external_survey',
661
+ } as const
662
+ export type SurveyType = (typeof SurveyType)[keyof typeof SurveyType]
660
663
 
661
664
  export type SurveyQuestion = BasicSurveyQuestion | LinkSurveyQuestion | RatingSurveyQuestion | MultipleSurveyQuestion
662
665
 
663
- export enum SurveyQuestionDescriptionContentType {
664
- Html = 'html',
665
- Text = 'text',
666
- }
666
+ export const SurveyQuestionDescriptionContentType = {
667
+ Html: 'html',
668
+ Text: 'text',
669
+ } as const
670
+ export type SurveyQuestionDescriptionContentType =
671
+ (typeof SurveyQuestionDescriptionContentType)[keyof typeof SurveyQuestionDescriptionContentType]
667
672
 
668
673
  // Survey validation types
669
- export enum SurveyValidationType {
670
- MinLength = 'min_length',
671
- MaxLength = 'max_length',
672
- }
674
+ export const SurveyValidationType = {
675
+ MinLength: 'min_length',
676
+ MaxLength: 'max_length',
677
+ } as const
678
+ export type SurveyValidationType = (typeof SurveyValidationType)[keyof typeof SurveyValidationType]
673
679
 
674
680
  export interface SurveyValidationRule {
675
681
  type: SurveyValidationType
@@ -710,16 +716,16 @@ type SurveyQuestionBase = {
710
716
  }
711
717
 
712
718
  export type BasicSurveyQuestion = SurveyQuestionBase & {
713
- type: SurveyQuestionType.Open
719
+ type: typeof SurveyQuestionType.Open
714
720
  }
715
721
 
716
722
  export type LinkSurveyQuestion = SurveyQuestionBase & {
717
- type: SurveyQuestionType.Link
723
+ type: typeof SurveyQuestionType.Link
718
724
  link?: string | null
719
725
  }
720
726
 
721
727
  export type RatingSurveyQuestion = SurveyQuestionBase & {
722
- type: SurveyQuestionType.Rating
728
+ type: typeof SurveyQuestionType.Rating
723
729
  display: SurveyRatingDisplay
724
730
  scale: 2 | 3 | 5 | 7 | 10
725
731
  lowerBoundLabel: string
@@ -727,49 +733,52 @@ export type RatingSurveyQuestion = SurveyQuestionBase & {
727
733
  skipSubmitButton?: boolean
728
734
  }
729
735
 
730
- export enum SurveyRatingDisplay {
731
- Number = 'number',
732
- Emoji = 'emoji',
733
- }
736
+ export const SurveyRatingDisplay = {
737
+ Number: 'number',
738
+ Emoji: 'emoji',
739
+ } as const
740
+ export type SurveyRatingDisplay = (typeof SurveyRatingDisplay)[keyof typeof SurveyRatingDisplay]
734
741
 
735
742
  export type MultipleSurveyQuestion = SurveyQuestionBase & {
736
- type: SurveyQuestionType.SingleChoice | SurveyQuestionType.MultipleChoice
743
+ type: typeof SurveyQuestionType.SingleChoice | typeof SurveyQuestionType.MultipleChoice
737
744
  choices: string[]
738
745
  hasOpenChoice?: boolean
739
746
  shuffleOptions?: boolean
740
747
  skipSubmitButton?: boolean
741
748
  }
742
749
 
743
- export enum SurveyQuestionType {
744
- Open = 'open',
745
- MultipleChoice = 'multiple_choice',
746
- SingleChoice = 'single_choice',
747
- Rating = 'rating',
748
- Link = 'link',
749
- }
750
+ export const SurveyQuestionType = {
751
+ Open: 'open',
752
+ MultipleChoice: 'multiple_choice',
753
+ SingleChoice: 'single_choice',
754
+ Rating: 'rating',
755
+ Link: 'link',
756
+ } as const
757
+ export type SurveyQuestionType = (typeof SurveyQuestionType)[keyof typeof SurveyQuestionType]
750
758
 
751
- export enum SurveyQuestionBranchingType {
752
- NextQuestion = 'next_question',
753
- End = 'end',
754
- ResponseBased = 'response_based',
755
- SpecificQuestion = 'specific_question',
756
- }
759
+ export const SurveyQuestionBranchingType = {
760
+ NextQuestion: 'next_question',
761
+ End: 'end',
762
+ ResponseBased: 'response_based',
763
+ SpecificQuestion: 'specific_question',
764
+ } as const
765
+ export type SurveyQuestionBranchingType = (typeof SurveyQuestionBranchingType)[keyof typeof SurveyQuestionBranchingType]
757
766
 
758
767
  export type NextQuestionBranching = {
759
- type: SurveyQuestionBranchingType.NextQuestion
768
+ type: typeof SurveyQuestionBranchingType.NextQuestion
760
769
  }
761
770
 
762
771
  export type EndBranching = {
763
- type: SurveyQuestionBranchingType.End
772
+ type: typeof SurveyQuestionBranchingType.End
764
773
  }
765
774
 
766
775
  export type ResponseBasedBranching = {
767
- type: SurveyQuestionBranchingType.ResponseBased
776
+ type: typeof SurveyQuestionBranchingType.ResponseBased
768
777
  responseValues: Record<string, any>
769
778
  }
770
779
 
771
780
  export type SpecificQuestionBranching = {
772
- type: SurveyQuestionBranchingType.SpecificQuestion
781
+ type: typeof SurveyQuestionBranchingType.SpecificQuestion
773
782
  index: number
774
783
  }
775
784
 
@@ -783,20 +792,22 @@ export type SurveyResponses = Record<string, SurveyResponseValue>
783
792
 
784
793
  export type SurveyCallback = (surveys: Survey[]) => void
785
794
 
786
- export enum SurveyMatchType {
787
- Regex = 'regex',
788
- NotRegex = 'not_regex',
789
- Exact = 'exact',
790
- IsNot = 'is_not',
791
- Icontains = 'icontains',
792
- NotIcontains = 'not_icontains',
793
- }
795
+ export const SurveyMatchType = {
796
+ Regex: 'regex',
797
+ NotRegex: 'not_regex',
798
+ Exact: 'exact',
799
+ IsNot: 'is_not',
800
+ Icontains: 'icontains',
801
+ NotIcontains: 'not_icontains',
802
+ } as const
803
+ export type SurveyMatchType = (typeof SurveyMatchType)[keyof typeof SurveyMatchType]
794
804
 
795
- export enum SurveySchedule {
796
- Once = 'once',
797
- Recurring = 'recurring',
798
- Always = 'always',
799
- }
805
+ export const SurveySchedule = {
806
+ Once: 'once',
807
+ Recurring: 'recurring',
808
+ Always: 'always',
809
+ } as const
810
+ export type SurveySchedule = (typeof SurveySchedule)[keyof typeof SurveySchedule]
800
811
 
801
812
  export type SurveyElement = {
802
813
  text?: string
@@ -853,9 +864,9 @@ export type Survey = {
853
864
  }
854
865
  start_date?: string
855
866
  end_date?: string
856
- current_iteration?: number
857
- current_iteration_start_date?: string
858
- schedule?: SurveySchedule
867
+ current_iteration?: number | null
868
+ current_iteration_start_date?: string | null
869
+ schedule?: SurveySchedule | null
859
870
  }
860
871
 
861
872
  export type SurveyActionType = {
@@ -865,11 +876,12 @@ export type SurveyActionType = {
865
876
  }
866
877
 
867
878
  /** Sync with plugin-server/src/types.ts */
868
- export enum ActionStepStringMatching {
869
- Contains = 'contains',
870
- Exact = 'exact',
871
- Regex = 'regex',
872
- }
879
+ export const ActionStepStringMatching = {
880
+ Contains: 'contains',
881
+ Exact: 'exact',
882
+ Regex: 'regex',
883
+ } as const
884
+ export type ActionStepStringMatching = (typeof ActionStepStringMatching)[keyof typeof ActionStepStringMatching]
873
885
 
874
886
  export type ActionStepType = {
875
887
  event?: string