@contractspec/lib.analytics 1.56.1 → 1.58.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 (87) hide show
  1. package/dist/browser/churn/index.js +77 -0
  2. package/dist/browser/churn/predictor.js +77 -0
  3. package/dist/browser/cohort/index.js +117 -0
  4. package/dist/browser/cohort/tracker.js +117 -0
  5. package/dist/browser/funnel/analyzer.js +68 -0
  6. package/dist/browser/funnel/index.js +68 -0
  7. package/dist/browser/growth/hypothesis-generator.js +46 -0
  8. package/dist/browser/growth/index.js +46 -0
  9. package/dist/browser/index.js +723 -0
  10. package/dist/browser/lifecycle/index.js +287 -0
  11. package/dist/browser/lifecycle/metric-collectors.js +58 -0
  12. package/dist/browser/lifecycle/posthog-bridge.js +79 -0
  13. package/dist/browser/lifecycle/posthog-metric-source.js +205 -0
  14. package/dist/browser/posthog/event-source.js +138 -0
  15. package/dist/browser/posthog/index.js +138 -0
  16. package/dist/browser/types.js +0 -0
  17. package/dist/churn/index.d.ts +2 -2
  18. package/dist/churn/index.d.ts.map +1 -0
  19. package/dist/churn/index.js +77 -2
  20. package/dist/churn/predictor.d.ts +15 -19
  21. package/dist/churn/predictor.d.ts.map +1 -1
  22. package/dist/churn/predictor.js +72 -68
  23. package/dist/cohort/index.d.ts +2 -2
  24. package/dist/cohort/index.d.ts.map +1 -0
  25. package/dist/cohort/index.js +117 -2
  26. package/dist/cohort/tracker.d.ts +3 -7
  27. package/dist/cohort/tracker.d.ts.map +1 -1
  28. package/dist/cohort/tracker.js +106 -90
  29. package/dist/funnel/analyzer.d.ts +4 -8
  30. package/dist/funnel/analyzer.d.ts.map +1 -1
  31. package/dist/funnel/analyzer.js +67 -62
  32. package/dist/funnel/index.d.ts +2 -2
  33. package/dist/funnel/index.d.ts.map +1 -0
  34. package/dist/funnel/index.js +69 -3
  35. package/dist/growth/hypothesis-generator.d.ts +11 -15
  36. package/dist/growth/hypothesis-generator.d.ts.map +1 -1
  37. package/dist/growth/hypothesis-generator.js +46 -39
  38. package/dist/growth/index.d.ts +2 -2
  39. package/dist/growth/index.d.ts.map +1 -0
  40. package/dist/growth/index.js +47 -3
  41. package/dist/index.d.ts +8 -8
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +724 -10
  44. package/dist/lifecycle/index.d.ts +4 -3
  45. package/dist/lifecycle/index.d.ts.map +1 -0
  46. package/dist/lifecycle/index.js +287 -3
  47. package/dist/lifecycle/metric-collectors.d.ts +22 -26
  48. package/dist/lifecycle/metric-collectors.d.ts.map +1 -1
  49. package/dist/lifecycle/metric-collectors.js +55 -44
  50. package/dist/lifecycle/posthog-bridge.d.ts +9 -13
  51. package/dist/lifecycle/posthog-bridge.d.ts.map +1 -1
  52. package/dist/lifecycle/posthog-bridge.js +77 -25
  53. package/dist/lifecycle/posthog-metric-source.d.ts +43 -0
  54. package/dist/lifecycle/posthog-metric-source.d.ts.map +1 -0
  55. package/dist/lifecycle/posthog-metric-source.js +206 -0
  56. package/dist/node/churn/index.js +77 -0
  57. package/dist/node/churn/predictor.js +77 -0
  58. package/dist/node/cohort/index.js +117 -0
  59. package/dist/node/cohort/tracker.js +117 -0
  60. package/dist/node/funnel/analyzer.js +68 -0
  61. package/dist/node/funnel/index.js +68 -0
  62. package/dist/node/growth/hypothesis-generator.js +46 -0
  63. package/dist/node/growth/index.js +46 -0
  64. package/dist/node/index.js +723 -0
  65. package/dist/node/lifecycle/index.js +287 -0
  66. package/dist/node/lifecycle/metric-collectors.js +58 -0
  67. package/dist/node/lifecycle/posthog-bridge.js +79 -0
  68. package/dist/node/lifecycle/posthog-metric-source.js +205 -0
  69. package/dist/node/posthog/event-source.js +138 -0
  70. package/dist/node/posthog/index.js +138 -0
  71. package/dist/node/types.js +0 -0
  72. package/dist/posthog/event-source.d.ts +21 -0
  73. package/dist/posthog/event-source.d.ts.map +1 -0
  74. package/dist/posthog/event-source.js +139 -0
  75. package/dist/posthog/index.d.ts +2 -0
  76. package/dist/posthog/index.d.ts.map +1 -0
  77. package/dist/posthog/index.js +139 -0
  78. package/dist/types.d.ts +52 -55
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js +1 -0
  81. package/package.json +189 -39
  82. package/dist/churn/predictor.js.map +0 -1
  83. package/dist/cohort/tracker.js.map +0 -1
  84. package/dist/funnel/analyzer.js.map +0 -1
  85. package/dist/growth/hypothesis-generator.js.map +0 -1
  86. package/dist/lifecycle/metric-collectors.js.map +0 -1
  87. package/dist/lifecycle/posthog-bridge.js.map +0 -1
@@ -0,0 +1,138 @@
1
+ // src/posthog/event-source.ts
2
+ class PosthogAnalyticsEventSource {
3
+ reader;
4
+ limitPerEvent;
5
+ tenantPropertyKey;
6
+ defaultRangeDays;
7
+ constructor(reader, options = {}) {
8
+ this.reader = reader;
9
+ this.limitPerEvent = options.limitPerEvent ?? 1000;
10
+ this.tenantPropertyKey = options.tenantPropertyKey ?? "tenantId";
11
+ this.defaultRangeDays = options.defaultRangeDays ?? 30;
12
+ }
13
+ async getEventsForFunnel(definition, dateRange) {
14
+ const eventNames = definition.steps.map((step) => step.eventName);
15
+ return this.getEventsByNames(eventNames, dateRange);
16
+ }
17
+ async getEventsForCohort(definition, dateRange, eventNames) {
18
+ const events = eventNames && eventNames.length > 0 ? eventNames : ["*"];
19
+ if (events[0] === "*") {
20
+ return this.getEventsByNames([], dateRange, definition.periods * 500);
21
+ }
22
+ return this.getEventsByNames(events, dateRange, definition.periods * 500);
23
+ }
24
+ async getUserActivity(userId, dateRange, limit = 1000) {
25
+ if (!this.reader.getEvents) {
26
+ throw new Error("Analytics reader does not support event queries.");
27
+ }
28
+ const response = await this.reader.getEvents({
29
+ distinctId: userId,
30
+ dateRange,
31
+ limit
32
+ });
33
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
34
+ }
35
+ async getGrowthMetrics(metricNames, dateRange) {
36
+ const range = resolveRange(dateRange, this.defaultRangeDays);
37
+ const previous = shiftRange(range);
38
+ const results = [];
39
+ for (const metricName of metricNames) {
40
+ const current = await this.countEvents(metricName, range);
41
+ const previousCount = await this.countEvents(metricName, previous);
42
+ results.push({
43
+ name: metricName,
44
+ current,
45
+ previous: previousCount
46
+ });
47
+ }
48
+ return results;
49
+ }
50
+ async getEventsByNames(eventNames, dateRange, limitPerEvent = this.limitPerEvent) {
51
+ if (!this.reader.getEvents) {
52
+ throw new Error("Analytics reader does not support event queries.");
53
+ }
54
+ if (eventNames.length === 0) {
55
+ const response = await this.reader.getEvents({
56
+ dateRange,
57
+ limit: limitPerEvent
58
+ });
59
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
60
+ }
61
+ const events = [];
62
+ for (const eventName of eventNames) {
63
+ const response = await this.reader.getEvents({
64
+ event: eventName,
65
+ dateRange,
66
+ limit: limitPerEvent
67
+ });
68
+ response.results.forEach((event) => {
69
+ events.push(toAnalyticsEvent(event, this.tenantPropertyKey));
70
+ });
71
+ }
72
+ return events;
73
+ }
74
+ async countEvents(eventName, range) {
75
+ if (!this.reader.queryHogQL) {
76
+ throw new Error("Analytics reader does not support HogQL queries.");
77
+ }
78
+ const result = await this.reader.queryHogQL({
79
+ query: [
80
+ "select",
81
+ " count() as total",
82
+ "from events",
83
+ "where event = {eventName}",
84
+ "and timestamp >= {dateFrom}",
85
+ "and timestamp < {dateTo}"
86
+ ].join(`
87
+ `),
88
+ values: {
89
+ eventName,
90
+ dateFrom: range.from.toISOString(),
91
+ dateTo: range.to.toISOString()
92
+ }
93
+ });
94
+ return readSingleNumber(result) ?? 0;
95
+ }
96
+ }
97
+ function toAnalyticsEvent(event, tenantPropertyKey) {
98
+ const tenantIdValue = event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;
99
+ return {
100
+ name: event.event,
101
+ userId: event.distinctId,
102
+ tenantId: typeof tenantIdValue === "string" ? tenantIdValue : undefined,
103
+ timestamp: event.timestamp,
104
+ properties: event.properties
105
+ };
106
+ }
107
+ function resolveRange(dateRange, defaultDays) {
108
+ const to = dateRange?.to instanceof Date ? dateRange.to : dateRange?.to ? new Date(dateRange.to) : new Date;
109
+ const from = dateRange?.from instanceof Date ? dateRange.from : dateRange?.from ? new Date(dateRange.from) : new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1000);
110
+ return { from, to };
111
+ }
112
+ function shiftRange(range) {
113
+ const duration = range.to.getTime() - range.from.getTime();
114
+ return {
115
+ from: new Date(range.from.getTime() - duration),
116
+ to: new Date(range.to.getTime() - duration)
117
+ };
118
+ }
119
+ function readSingleNumber(result) {
120
+ if (!Array.isArray(result.results) || result.results.length === 0) {
121
+ return;
122
+ }
123
+ const firstRow = result.results[0];
124
+ if (Array.isArray(firstRow) && firstRow.length > 0) {
125
+ const value = firstRow[0];
126
+ if (typeof value === "number" && Number.isFinite(value))
127
+ return value;
128
+ if (typeof value === "string" && value.trim()) {
129
+ const parsed = Number(value);
130
+ if (Number.isFinite(parsed))
131
+ return parsed;
132
+ }
133
+ }
134
+ return;
135
+ }
136
+ export {
137
+ PosthogAnalyticsEventSource
138
+ };
@@ -0,0 +1,138 @@
1
+ // src/posthog/event-source.ts
2
+ class PosthogAnalyticsEventSource {
3
+ reader;
4
+ limitPerEvent;
5
+ tenantPropertyKey;
6
+ defaultRangeDays;
7
+ constructor(reader, options = {}) {
8
+ this.reader = reader;
9
+ this.limitPerEvent = options.limitPerEvent ?? 1000;
10
+ this.tenantPropertyKey = options.tenantPropertyKey ?? "tenantId";
11
+ this.defaultRangeDays = options.defaultRangeDays ?? 30;
12
+ }
13
+ async getEventsForFunnel(definition, dateRange) {
14
+ const eventNames = definition.steps.map((step) => step.eventName);
15
+ return this.getEventsByNames(eventNames, dateRange);
16
+ }
17
+ async getEventsForCohort(definition, dateRange, eventNames) {
18
+ const events = eventNames && eventNames.length > 0 ? eventNames : ["*"];
19
+ if (events[0] === "*") {
20
+ return this.getEventsByNames([], dateRange, definition.periods * 500);
21
+ }
22
+ return this.getEventsByNames(events, dateRange, definition.periods * 500);
23
+ }
24
+ async getUserActivity(userId, dateRange, limit = 1000) {
25
+ if (!this.reader.getEvents) {
26
+ throw new Error("Analytics reader does not support event queries.");
27
+ }
28
+ const response = await this.reader.getEvents({
29
+ distinctId: userId,
30
+ dateRange,
31
+ limit
32
+ });
33
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
34
+ }
35
+ async getGrowthMetrics(metricNames, dateRange) {
36
+ const range = resolveRange(dateRange, this.defaultRangeDays);
37
+ const previous = shiftRange(range);
38
+ const results = [];
39
+ for (const metricName of metricNames) {
40
+ const current = await this.countEvents(metricName, range);
41
+ const previousCount = await this.countEvents(metricName, previous);
42
+ results.push({
43
+ name: metricName,
44
+ current,
45
+ previous: previousCount
46
+ });
47
+ }
48
+ return results;
49
+ }
50
+ async getEventsByNames(eventNames, dateRange, limitPerEvent = this.limitPerEvent) {
51
+ if (!this.reader.getEvents) {
52
+ throw new Error("Analytics reader does not support event queries.");
53
+ }
54
+ if (eventNames.length === 0) {
55
+ const response = await this.reader.getEvents({
56
+ dateRange,
57
+ limit: limitPerEvent
58
+ });
59
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
60
+ }
61
+ const events = [];
62
+ for (const eventName of eventNames) {
63
+ const response = await this.reader.getEvents({
64
+ event: eventName,
65
+ dateRange,
66
+ limit: limitPerEvent
67
+ });
68
+ response.results.forEach((event) => {
69
+ events.push(toAnalyticsEvent(event, this.tenantPropertyKey));
70
+ });
71
+ }
72
+ return events;
73
+ }
74
+ async countEvents(eventName, range) {
75
+ if (!this.reader.queryHogQL) {
76
+ throw new Error("Analytics reader does not support HogQL queries.");
77
+ }
78
+ const result = await this.reader.queryHogQL({
79
+ query: [
80
+ "select",
81
+ " count() as total",
82
+ "from events",
83
+ "where event = {eventName}",
84
+ "and timestamp >= {dateFrom}",
85
+ "and timestamp < {dateTo}"
86
+ ].join(`
87
+ `),
88
+ values: {
89
+ eventName,
90
+ dateFrom: range.from.toISOString(),
91
+ dateTo: range.to.toISOString()
92
+ }
93
+ });
94
+ return readSingleNumber(result) ?? 0;
95
+ }
96
+ }
97
+ function toAnalyticsEvent(event, tenantPropertyKey) {
98
+ const tenantIdValue = event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;
99
+ return {
100
+ name: event.event,
101
+ userId: event.distinctId,
102
+ tenantId: typeof tenantIdValue === "string" ? tenantIdValue : undefined,
103
+ timestamp: event.timestamp,
104
+ properties: event.properties
105
+ };
106
+ }
107
+ function resolveRange(dateRange, defaultDays) {
108
+ const to = dateRange?.to instanceof Date ? dateRange.to : dateRange?.to ? new Date(dateRange.to) : new Date;
109
+ const from = dateRange?.from instanceof Date ? dateRange.from : dateRange?.from ? new Date(dateRange.from) : new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1000);
110
+ return { from, to };
111
+ }
112
+ function shiftRange(range) {
113
+ const duration = range.to.getTime() - range.from.getTime();
114
+ return {
115
+ from: new Date(range.from.getTime() - duration),
116
+ to: new Date(range.to.getTime() - duration)
117
+ };
118
+ }
119
+ function readSingleNumber(result) {
120
+ if (!Array.isArray(result.results) || result.results.length === 0) {
121
+ return;
122
+ }
123
+ const firstRow = result.results[0];
124
+ if (Array.isArray(firstRow) && firstRow.length > 0) {
125
+ const value = firstRow[0];
126
+ if (typeof value === "number" && Number.isFinite(value))
127
+ return value;
128
+ if (typeof value === "string" && value.trim()) {
129
+ const parsed = Number(value);
130
+ if (Number.isFinite(parsed))
131
+ return parsed;
132
+ }
133
+ }
134
+ return;
135
+ }
136
+ export {
137
+ PosthogAnalyticsEventSource
138
+ };
File without changes
@@ -0,0 +1,21 @@
1
+ import type { AnalyticsReader, DateRangeInput } from '@contractspec/lib.contracts/integrations/providers/analytics';
2
+ import type { AnalyticsEvent, CohortDefinition, FunnelDefinition, GrowthMetric } from '../types';
3
+ export interface PosthogAnalyticsEventSourceOptions {
4
+ limitPerEvent?: number;
5
+ tenantPropertyKey?: string;
6
+ defaultRangeDays?: number;
7
+ }
8
+ export declare class PosthogAnalyticsEventSource {
9
+ private readonly reader;
10
+ private readonly limitPerEvent;
11
+ private readonly tenantPropertyKey;
12
+ private readonly defaultRangeDays;
13
+ constructor(reader: AnalyticsReader, options?: PosthogAnalyticsEventSourceOptions);
14
+ getEventsForFunnel(definition: FunnelDefinition, dateRange?: DateRangeInput): Promise<AnalyticsEvent[]>;
15
+ getEventsForCohort(definition: CohortDefinition, dateRange?: DateRangeInput, eventNames?: string[]): Promise<AnalyticsEvent[]>;
16
+ getUserActivity(userId: string, dateRange?: DateRangeInput, limit?: number): Promise<AnalyticsEvent[]>;
17
+ getGrowthMetrics(metricNames: string[], dateRange?: DateRangeInput): Promise<GrowthMetric[]>;
18
+ private getEventsByNames;
19
+ private countEvents;
20
+ }
21
+ //# sourceMappingURL=event-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-source.d.ts","sourceRoot":"","sources":["../../src/posthog/event-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,8DAA8D,CAAC;AACtE,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACb,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,kCAAkC;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAGxC,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,kCAAuC;IAQ5C,kBAAkB,CACtB,UAAU,EAAE,gBAAgB,EAC5B,SAAS,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,cAAc,EAAE,CAAC;IAKtB,kBAAkB,CACtB,UAAU,EAAE,gBAAgB,EAC5B,SAAS,CAAC,EAAE,cAAc,EAC1B,UAAU,CAAC,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,cAAc,EAAE,CAAC;IAQtB,eAAe,CACnB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,cAAc,EAC1B,KAAK,SAAO,GACX,OAAO,CAAC,cAAc,EAAE,CAAC;IActB,gBAAgB,CACpB,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,YAAY,EAAE,CAAC;YAgBZ,gBAAgB;YA+BhB,WAAW;CAwB1B"}
@@ -0,0 +1,139 @@
1
+ // @bun
2
+ // src/posthog/event-source.ts
3
+ class PosthogAnalyticsEventSource {
4
+ reader;
5
+ limitPerEvent;
6
+ tenantPropertyKey;
7
+ defaultRangeDays;
8
+ constructor(reader, options = {}) {
9
+ this.reader = reader;
10
+ this.limitPerEvent = options.limitPerEvent ?? 1000;
11
+ this.tenantPropertyKey = options.tenantPropertyKey ?? "tenantId";
12
+ this.defaultRangeDays = options.defaultRangeDays ?? 30;
13
+ }
14
+ async getEventsForFunnel(definition, dateRange) {
15
+ const eventNames = definition.steps.map((step) => step.eventName);
16
+ return this.getEventsByNames(eventNames, dateRange);
17
+ }
18
+ async getEventsForCohort(definition, dateRange, eventNames) {
19
+ const events = eventNames && eventNames.length > 0 ? eventNames : ["*"];
20
+ if (events[0] === "*") {
21
+ return this.getEventsByNames([], dateRange, definition.periods * 500);
22
+ }
23
+ return this.getEventsByNames(events, dateRange, definition.periods * 500);
24
+ }
25
+ async getUserActivity(userId, dateRange, limit = 1000) {
26
+ if (!this.reader.getEvents) {
27
+ throw new Error("Analytics reader does not support event queries.");
28
+ }
29
+ const response = await this.reader.getEvents({
30
+ distinctId: userId,
31
+ dateRange,
32
+ limit
33
+ });
34
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
35
+ }
36
+ async getGrowthMetrics(metricNames, dateRange) {
37
+ const range = resolveRange(dateRange, this.defaultRangeDays);
38
+ const previous = shiftRange(range);
39
+ const results = [];
40
+ for (const metricName of metricNames) {
41
+ const current = await this.countEvents(metricName, range);
42
+ const previousCount = await this.countEvents(metricName, previous);
43
+ results.push({
44
+ name: metricName,
45
+ current,
46
+ previous: previousCount
47
+ });
48
+ }
49
+ return results;
50
+ }
51
+ async getEventsByNames(eventNames, dateRange, limitPerEvent = this.limitPerEvent) {
52
+ if (!this.reader.getEvents) {
53
+ throw new Error("Analytics reader does not support event queries.");
54
+ }
55
+ if (eventNames.length === 0) {
56
+ const response = await this.reader.getEvents({
57
+ dateRange,
58
+ limit: limitPerEvent
59
+ });
60
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
61
+ }
62
+ const events = [];
63
+ for (const eventName of eventNames) {
64
+ const response = await this.reader.getEvents({
65
+ event: eventName,
66
+ dateRange,
67
+ limit: limitPerEvent
68
+ });
69
+ response.results.forEach((event) => {
70
+ events.push(toAnalyticsEvent(event, this.tenantPropertyKey));
71
+ });
72
+ }
73
+ return events;
74
+ }
75
+ async countEvents(eventName, range) {
76
+ if (!this.reader.queryHogQL) {
77
+ throw new Error("Analytics reader does not support HogQL queries.");
78
+ }
79
+ const result = await this.reader.queryHogQL({
80
+ query: [
81
+ "select",
82
+ " count() as total",
83
+ "from events",
84
+ "where event = {eventName}",
85
+ "and timestamp >= {dateFrom}",
86
+ "and timestamp < {dateTo}"
87
+ ].join(`
88
+ `),
89
+ values: {
90
+ eventName,
91
+ dateFrom: range.from.toISOString(),
92
+ dateTo: range.to.toISOString()
93
+ }
94
+ });
95
+ return readSingleNumber(result) ?? 0;
96
+ }
97
+ }
98
+ function toAnalyticsEvent(event, tenantPropertyKey) {
99
+ const tenantIdValue = event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;
100
+ return {
101
+ name: event.event,
102
+ userId: event.distinctId,
103
+ tenantId: typeof tenantIdValue === "string" ? tenantIdValue : undefined,
104
+ timestamp: event.timestamp,
105
+ properties: event.properties
106
+ };
107
+ }
108
+ function resolveRange(dateRange, defaultDays) {
109
+ const to = dateRange?.to instanceof Date ? dateRange.to : dateRange?.to ? new Date(dateRange.to) : new Date;
110
+ const from = dateRange?.from instanceof Date ? dateRange.from : dateRange?.from ? new Date(dateRange.from) : new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1000);
111
+ return { from, to };
112
+ }
113
+ function shiftRange(range) {
114
+ const duration = range.to.getTime() - range.from.getTime();
115
+ return {
116
+ from: new Date(range.from.getTime() - duration),
117
+ to: new Date(range.to.getTime() - duration)
118
+ };
119
+ }
120
+ function readSingleNumber(result) {
121
+ if (!Array.isArray(result.results) || result.results.length === 0) {
122
+ return;
123
+ }
124
+ const firstRow = result.results[0];
125
+ if (Array.isArray(firstRow) && firstRow.length > 0) {
126
+ const value = firstRow[0];
127
+ if (typeof value === "number" && Number.isFinite(value))
128
+ return value;
129
+ if (typeof value === "string" && value.trim()) {
130
+ const parsed = Number(value);
131
+ if (Number.isFinite(parsed))
132
+ return parsed;
133
+ }
134
+ }
135
+ return;
136
+ }
137
+ export {
138
+ PosthogAnalyticsEventSource
139
+ };
@@ -0,0 +1,2 @@
1
+ export * from './event-source';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/posthog/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,139 @@
1
+ // @bun
2
+ // src/posthog/event-source.ts
3
+ class PosthogAnalyticsEventSource {
4
+ reader;
5
+ limitPerEvent;
6
+ tenantPropertyKey;
7
+ defaultRangeDays;
8
+ constructor(reader, options = {}) {
9
+ this.reader = reader;
10
+ this.limitPerEvent = options.limitPerEvent ?? 1000;
11
+ this.tenantPropertyKey = options.tenantPropertyKey ?? "tenantId";
12
+ this.defaultRangeDays = options.defaultRangeDays ?? 30;
13
+ }
14
+ async getEventsForFunnel(definition, dateRange) {
15
+ const eventNames = definition.steps.map((step) => step.eventName);
16
+ return this.getEventsByNames(eventNames, dateRange);
17
+ }
18
+ async getEventsForCohort(definition, dateRange, eventNames) {
19
+ const events = eventNames && eventNames.length > 0 ? eventNames : ["*"];
20
+ if (events[0] === "*") {
21
+ return this.getEventsByNames([], dateRange, definition.periods * 500);
22
+ }
23
+ return this.getEventsByNames(events, dateRange, definition.periods * 500);
24
+ }
25
+ async getUserActivity(userId, dateRange, limit = 1000) {
26
+ if (!this.reader.getEvents) {
27
+ throw new Error("Analytics reader does not support event queries.");
28
+ }
29
+ const response = await this.reader.getEvents({
30
+ distinctId: userId,
31
+ dateRange,
32
+ limit
33
+ });
34
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
35
+ }
36
+ async getGrowthMetrics(metricNames, dateRange) {
37
+ const range = resolveRange(dateRange, this.defaultRangeDays);
38
+ const previous = shiftRange(range);
39
+ const results = [];
40
+ for (const metricName of metricNames) {
41
+ const current = await this.countEvents(metricName, range);
42
+ const previousCount = await this.countEvents(metricName, previous);
43
+ results.push({
44
+ name: metricName,
45
+ current,
46
+ previous: previousCount
47
+ });
48
+ }
49
+ return results;
50
+ }
51
+ async getEventsByNames(eventNames, dateRange, limitPerEvent = this.limitPerEvent) {
52
+ if (!this.reader.getEvents) {
53
+ throw new Error("Analytics reader does not support event queries.");
54
+ }
55
+ if (eventNames.length === 0) {
56
+ const response = await this.reader.getEvents({
57
+ dateRange,
58
+ limit: limitPerEvent
59
+ });
60
+ return response.results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
61
+ }
62
+ const events = [];
63
+ for (const eventName of eventNames) {
64
+ const response = await this.reader.getEvents({
65
+ event: eventName,
66
+ dateRange,
67
+ limit: limitPerEvent
68
+ });
69
+ response.results.forEach((event) => {
70
+ events.push(toAnalyticsEvent(event, this.tenantPropertyKey));
71
+ });
72
+ }
73
+ return events;
74
+ }
75
+ async countEvents(eventName, range) {
76
+ if (!this.reader.queryHogQL) {
77
+ throw new Error("Analytics reader does not support HogQL queries.");
78
+ }
79
+ const result = await this.reader.queryHogQL({
80
+ query: [
81
+ "select",
82
+ " count() as total",
83
+ "from events",
84
+ "where event = {eventName}",
85
+ "and timestamp >= {dateFrom}",
86
+ "and timestamp < {dateTo}"
87
+ ].join(`
88
+ `),
89
+ values: {
90
+ eventName,
91
+ dateFrom: range.from.toISOString(),
92
+ dateTo: range.to.toISOString()
93
+ }
94
+ });
95
+ return readSingleNumber(result) ?? 0;
96
+ }
97
+ }
98
+ function toAnalyticsEvent(event, tenantPropertyKey) {
99
+ const tenantIdValue = event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;
100
+ return {
101
+ name: event.event,
102
+ userId: event.distinctId,
103
+ tenantId: typeof tenantIdValue === "string" ? tenantIdValue : undefined,
104
+ timestamp: event.timestamp,
105
+ properties: event.properties
106
+ };
107
+ }
108
+ function resolveRange(dateRange, defaultDays) {
109
+ const to = dateRange?.to instanceof Date ? dateRange.to : dateRange?.to ? new Date(dateRange.to) : new Date;
110
+ const from = dateRange?.from instanceof Date ? dateRange.from : dateRange?.from ? new Date(dateRange.from) : new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1000);
111
+ return { from, to };
112
+ }
113
+ function shiftRange(range) {
114
+ const duration = range.to.getTime() - range.from.getTime();
115
+ return {
116
+ from: new Date(range.from.getTime() - duration),
117
+ to: new Date(range.to.getTime() - duration)
118
+ };
119
+ }
120
+ function readSingleNumber(result) {
121
+ if (!Array.isArray(result.results) || result.results.length === 0) {
122
+ return;
123
+ }
124
+ const firstRow = result.results[0];
125
+ if (Array.isArray(firstRow) && firstRow.length > 0) {
126
+ const value = firstRow[0];
127
+ if (typeof value === "number" && Number.isFinite(value))
128
+ return value;
129
+ if (typeof value === "string" && value.trim()) {
130
+ const parsed = Number(value);
131
+ if (Number.isFinite(parsed))
132
+ return parsed;
133
+ }
134
+ }
135
+ return;
136
+ }
137
+ export {
138
+ PosthogAnalyticsEventSource
139
+ };