@contractspec/lib.analytics 1.56.0 → 1.57.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.
@@ -1 +1 @@
1
- {"version":3,"file":"predictor.d.ts","names":[],"sources":["../../src/churn/predictor.ts"],"sourcesContent":[],"mappings":";;;UAGiB,qBAAA;;EAAA,eAAA,CAAA,EAAA,MAAqB;EAOzB,WAAA,CAAA,EAAA,MAAc;EAMH,SAAA,CAAA,EAAA,MAAA;;AAOW,cAbtB,cAAA,CAasB;EAAW,iBAAA,aAAA;;;;wBAPtB;gBAOR,mBAAmB"}
1
+ {"version":3,"file":"predictor.d.ts","names":[],"sources":["../../src/churn/predictor.ts"],"mappings":";;;UAGiB,qBAAA;EACf,aAAA;EACA,eAAA;EACA,WAAA;EACA,SAAA;AAAA;AAAA,cAGW,cAAA;EAAA,iBACM,aAAA;EAAA,iBACA,eAAA;EAAA,iBACA,WAAA;EAAA,iBACA,SAAA;cAEL,OAAA,GAAU,qBAAA;EAOtB,KAAA,CAAM,MAAA,EAAQ,cAAA,KAAmB,WAAA;EAAA,QAezB,YAAA;EAAA,QA6BA,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"tracker.d.ts","names":[],"sources":["../../src/cohort/tracker.ts"],"sourcesContent":[],"mappings":";;;cAQa,aAAA;kBAED,8BACI,mBACX;AAJL"}
1
+ {"version":3,"file":"tracker.d.ts","names":[],"sources":["../../src/cohort/tracker.ts"],"mappings":";;;cAQa,aAAA;EACX,OAAA,CACE,MAAA,EAAQ,cAAA,IACR,UAAA,EAAY,gBAAA,GACX,cAAA;AAAA"}
@@ -71,7 +71,6 @@ function bucketKey(timestamp, bucket) {
71
71
  switch (bucket) {
72
72
  case "day": return dt.startOf("day").format("YYYY-MM-DD");
73
73
  case "week": return dt.startOf("week").format("YYYY-[W]WW");
74
- case "month":
75
74
  default: return dt.startOf("month").format("YYYY-MM");
76
75
  }
77
76
  }
@@ -81,7 +80,6 @@ function bucketDiff(cohortKey, timestamp, bucket) {
81
80
  switch (bucket) {
82
81
  case "day": return target.diff(start, "day");
83
82
  case "week": return target.diff(start, "week");
84
- case "month":
85
83
  default: return target.diff(start, "month");
86
84
  }
87
85
  }
@@ -89,7 +87,6 @@ function parseBucketKey(key, bucket) {
89
87
  switch (bucket) {
90
88
  case "day": return dayjs(key, "YYYY-MM-DD");
91
89
  case "week": return dayjs(key.replace("W", ""), "YYYY-ww");
92
- case "month":
93
90
  default: return dayjs(key, "YYYY-MM");
94
91
  }
95
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tracker.js","names":[],"sources":["../../src/cohort/tracker.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport type {\n AnalyticsEvent,\n CohortAnalysis,\n CohortDefinition,\n CohortStats,\n} from '../types';\n\nexport class CohortTracker {\n analyze(\n events: AnalyticsEvent[],\n definition: CohortDefinition\n ): CohortAnalysis {\n const groupedByUser = groupBy(events, (event) => event.userId);\n const cohorts = new Map<string, CohortStatsBuilder>();\n\n for (const [userId, userEvents] of groupedByUser.entries()) {\n userEvents.sort((a, b) => dateMs(a) - dateMs(b));\n const signup = userEvents[0];\n if (!signup) continue;\n const cohortKey = bucketKey(signup.timestamp, definition.bucket);\n const builder =\n cohorts.get(cohortKey) ?? new CohortStatsBuilder(cohortKey, definition);\n builder.addUser(userId);\n for (const event of userEvents) {\n builder.addEvent(userId, event);\n }\n cohorts.set(cohortKey, builder);\n }\n\n return {\n definition,\n cohorts: [...cohorts.values()].map((builder) => builder.build()),\n };\n }\n}\n\nclass CohortStatsBuilder {\n private readonly users = new Set<string>();\n private readonly retentionMap = new Map<number, Set<string>>();\n private ltv = 0;\n constructor(\n private readonly key: string,\n private readonly definition: CohortDefinition\n ) {}\n\n addUser(userId: string) {\n this.users.add(userId);\n }\n\n addEvent(userId: string, event: AnalyticsEvent) {\n const period = bucketDiff(\n this.key,\n event.timestamp,\n this.definition.bucket\n );\n if (period < 0 || period >= this.definition.periods) return;\n const bucket = this.retentionMap.get(period) ?? new Set<string>();\n bucket.add(userId);\n this.retentionMap.set(period, bucket);\n const amount =\n typeof event.properties?.amount === 'number'\n ? event.properties.amount\n : 0;\n this.ltv += amount;\n }\n\n build(): CohortStats {\n const totalUsers = this.users.size || 1;\n const retention: number[] = [];\n for (let period = 0; period < this.definition.periods; period++) {\n const active = this.retentionMap.get(period)?.size ?? 0;\n retention.push(Number((active / totalUsers).toFixed(3)));\n }\n return {\n cohortKey: this.key,\n users: this.users.size,\n retention,\n ltv: Number(this.ltv.toFixed(2)),\n };\n }\n}\n\nfunction groupBy<T>(\n items: T[],\n selector: (item: T) => string\n): Map<string, T[]> {\n const map = new Map<string, T[]>();\n for (const item of items) {\n const key = selector(item);\n const list = map.get(key) ?? [];\n list.push(item);\n map.set(key, list);\n }\n return map;\n}\n\nfunction bucketKey(\n timestamp: string | Date,\n bucket: CohortDefinition['bucket']\n): string {\n const dt = dayjs(timestamp);\n switch (bucket) {\n case 'day':\n return dt.startOf('day').format('YYYY-MM-DD');\n case 'week':\n return dt.startOf('week').format('YYYY-[W]WW');\n case 'month':\n default:\n return dt.startOf('month').format('YYYY-MM');\n }\n}\n\nfunction bucketDiff(\n cohortKey: string,\n timestamp: string | Date,\n bucket: CohortDefinition['bucket']\n): number {\n const start = parseBucketKey(cohortKey, bucket);\n const target = dayjs(timestamp);\n switch (bucket) {\n case 'day':\n return target.diff(start, 'day');\n case 'week':\n return target.diff(start, 'week');\n case 'month':\n default:\n return target.diff(start, 'month');\n }\n}\n\nfunction parseBucketKey(key: string, bucket: CohortDefinition['bucket']) {\n switch (bucket) {\n case 'day':\n return dayjs(key, 'YYYY-MM-DD');\n case 'week':\n return dayjs(key.replace('W', ''), 'YYYY-ww');\n case 'month':\n default:\n return dayjs(key, 'YYYY-MM');\n }\n}\n\nfunction dateMs(event: AnalyticsEvent) {\n return new Date(event.timestamp).getTime();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tracker.js","names":[],"sources":["../../src/cohort/tracker.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport type {\n AnalyticsEvent,\n CohortAnalysis,\n CohortDefinition,\n CohortStats,\n} from '../types';\n\nexport class CohortTracker {\n analyze(\n events: AnalyticsEvent[],\n definition: CohortDefinition\n ): CohortAnalysis {\n const groupedByUser = groupBy(events, (event) => event.userId);\n const cohorts = new Map<string, CohortStatsBuilder>();\n\n for (const [userId, userEvents] of groupedByUser.entries()) {\n userEvents.sort((a, b) => dateMs(a) - dateMs(b));\n const signup = userEvents[0];\n if (!signup) continue;\n const cohortKey = bucketKey(signup.timestamp, definition.bucket);\n const builder =\n cohorts.get(cohortKey) ?? new CohortStatsBuilder(cohortKey, definition);\n builder.addUser(userId);\n for (const event of userEvents) {\n builder.addEvent(userId, event);\n }\n cohorts.set(cohortKey, builder);\n }\n\n return {\n definition,\n cohorts: [...cohorts.values()].map((builder) => builder.build()),\n };\n }\n}\n\nclass CohortStatsBuilder {\n private readonly users = new Set<string>();\n private readonly retentionMap = new Map<number, Set<string>>();\n private ltv = 0;\n constructor(\n private readonly key: string,\n private readonly definition: CohortDefinition\n ) {}\n\n addUser(userId: string) {\n this.users.add(userId);\n }\n\n addEvent(userId: string, event: AnalyticsEvent) {\n const period = bucketDiff(\n this.key,\n event.timestamp,\n this.definition.bucket\n );\n if (period < 0 || period >= this.definition.periods) return;\n const bucket = this.retentionMap.get(period) ?? new Set<string>();\n bucket.add(userId);\n this.retentionMap.set(period, bucket);\n const amount =\n typeof event.properties?.amount === 'number'\n ? event.properties.amount\n : 0;\n this.ltv += amount;\n }\n\n build(): CohortStats {\n const totalUsers = this.users.size || 1;\n const retention: number[] = [];\n for (let period = 0; period < this.definition.periods; period++) {\n const active = this.retentionMap.get(period)?.size ?? 0;\n retention.push(Number((active / totalUsers).toFixed(3)));\n }\n return {\n cohortKey: this.key,\n users: this.users.size,\n retention,\n ltv: Number(this.ltv.toFixed(2)),\n };\n }\n}\n\nfunction groupBy<T>(\n items: T[],\n selector: (item: T) => string\n): Map<string, T[]> {\n const map = new Map<string, T[]>();\n for (const item of items) {\n const key = selector(item);\n const list = map.get(key) ?? [];\n list.push(item);\n map.set(key, list);\n }\n return map;\n}\n\nfunction bucketKey(\n timestamp: string | Date,\n bucket: CohortDefinition['bucket']\n): string {\n const dt = dayjs(timestamp);\n switch (bucket) {\n case 'day':\n return dt.startOf('day').format('YYYY-MM-DD');\n case 'week':\n return dt.startOf('week').format('YYYY-[W]WW');\n case 'month':\n default:\n return dt.startOf('month').format('YYYY-MM');\n }\n}\n\nfunction bucketDiff(\n cohortKey: string,\n timestamp: string | Date,\n bucket: CohortDefinition['bucket']\n): number {\n const start = parseBucketKey(cohortKey, bucket);\n const target = dayjs(timestamp);\n switch (bucket) {\n case 'day':\n return target.diff(start, 'day');\n case 'week':\n return target.diff(start, 'week');\n case 'month':\n default:\n return target.diff(start, 'month');\n }\n}\n\nfunction parseBucketKey(key: string, bucket: CohortDefinition['bucket']) {\n switch (bucket) {\n case 'day':\n return dayjs(key, 'YYYY-MM-DD');\n case 'week':\n return dayjs(key.replace('W', ''), 'YYYY-ww');\n case 'month':\n default:\n return dayjs(key, 'YYYY-MM');\n }\n}\n\nfunction dateMs(event: AnalyticsEvent) {\n return new Date(event.timestamp).getTime();\n}\n"],"mappings":";;;AAQA,IAAa,gBAAb,MAA2B;CACzB,QACE,QACA,YACgB;EAChB,MAAM,gBAAgB,QAAQ,SAAS,UAAU,MAAM,OAAO;EAC9D,MAAM,0BAAU,IAAI,KAAiC;AAErD,OAAK,MAAM,CAAC,QAAQ,eAAe,cAAc,SAAS,EAAE;AAC1D,cAAW,MAAM,GAAG,MAAM,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;GAChD,MAAM,SAAS,WAAW;AAC1B,OAAI,CAAC,OAAQ;GACb,MAAM,YAAY,UAAU,OAAO,WAAW,WAAW,OAAO;GAChE,MAAM,UACJ,QAAQ,IAAI,UAAU,IAAI,IAAI,mBAAmB,WAAW,WAAW;AACzE,WAAQ,QAAQ,OAAO;AACvB,QAAK,MAAM,SAAS,WAClB,SAAQ,SAAS,QAAQ,MAAM;AAEjC,WAAQ,IAAI,WAAW,QAAQ;;AAGjC,SAAO;GACL;GACA,SAAS,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,KAAK,YAAY,QAAQ,OAAO,CAAC;GACjE;;;AAIL,IAAM,qBAAN,MAAyB;CACvB,AAAiB,wBAAQ,IAAI,KAAa;CAC1C,AAAiB,+BAAe,IAAI,KAA0B;CAC9D,AAAQ,MAAM;CACd,YACE,AAAiB,KACjB,AAAiB,YACjB;EAFiB;EACA;;CAGnB,QAAQ,QAAgB;AACtB,OAAK,MAAM,IAAI,OAAO;;CAGxB,SAAS,QAAgB,OAAuB;EAC9C,MAAM,SAAS,WACb,KAAK,KACL,MAAM,WACN,KAAK,WAAW,OACjB;AACD,MAAI,SAAS,KAAK,UAAU,KAAK,WAAW,QAAS;EACrD,MAAM,SAAS,KAAK,aAAa,IAAI,OAAO,oBAAI,IAAI,KAAa;AACjE,SAAO,IAAI,OAAO;AAClB,OAAK,aAAa,IAAI,QAAQ,OAAO;EACrC,MAAM,SACJ,OAAO,MAAM,YAAY,WAAW,WAChC,MAAM,WAAW,SACjB;AACN,OAAK,OAAO;;CAGd,QAAqB;EACnB,MAAM,aAAa,KAAK,MAAM,QAAQ;EACtC,MAAM,YAAsB,EAAE;AAC9B,OAAK,IAAI,SAAS,GAAG,SAAS,KAAK,WAAW,SAAS,UAAU;GAC/D,MAAM,SAAS,KAAK,aAAa,IAAI,OAAO,EAAE,QAAQ;AACtD,aAAU,KAAK,QAAQ,SAAS,YAAY,QAAQ,EAAE,CAAC,CAAC;;AAE1D,SAAO;GACL,WAAW,KAAK;GAChB,OAAO,KAAK,MAAM;GAClB;GACA,KAAK,OAAO,KAAK,IAAI,QAAQ,EAAE,CAAC;GACjC;;;AAIL,SAAS,QACP,OACA,UACkB;CAClB,MAAM,sBAAM,IAAI,KAAkB;AAClC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,SAAS,KAAK;EAC1B,MAAM,OAAO,IAAI,IAAI,IAAI,IAAI,EAAE;AAC/B,OAAK,KAAK,KAAK;AACf,MAAI,IAAI,KAAK,KAAK;;AAEpB,QAAO;;AAGT,SAAS,UACP,WACA,QACQ;CACR,MAAM,KAAK,MAAM,UAAU;AAC3B,SAAQ,QAAR;EACE,KAAK,MACH,QAAO,GAAG,QAAQ,MAAM,CAAC,OAAO,aAAa;EAC/C,KAAK,OACH,QAAO,GAAG,QAAQ,OAAO,CAAC,OAAO,aAAa;EAEhD,QACE,QAAO,GAAG,QAAQ,QAAQ,CAAC,OAAO,UAAU;;;AAIlD,SAAS,WACP,WACA,WACA,QACQ;CACR,MAAM,QAAQ,eAAe,WAAW,OAAO;CAC/C,MAAM,SAAS,MAAM,UAAU;AAC/B,SAAQ,QAAR;EACE,KAAK,MACH,QAAO,OAAO,KAAK,OAAO,MAAM;EAClC,KAAK,OACH,QAAO,OAAO,KAAK,OAAO,OAAO;EAEnC,QACE,QAAO,OAAO,KAAK,OAAO,QAAQ;;;AAIxC,SAAS,eAAe,KAAa,QAAoC;AACvE,SAAQ,QAAR;EACE,KAAK,MACH,QAAO,MAAM,KAAK,aAAa;EACjC,KAAK,OACH,QAAO,MAAM,IAAI,QAAQ,KAAK,GAAG,EAAE,UAAU;EAE/C,QACE,QAAO,MAAM,KAAK,UAAU;;;AAIlC,SAAS,OAAO,OAAuB;AACrC,QAAO,IAAI,KAAK,MAAM,UAAU,CAAC,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","names":[],"sources":["../../src/funnel/analyzer.ts"],"sourcesContent":[],"mappings":";;;cAQa,cAAA;kBAED,8BACI,mBACX;EAJQ,QAAA,YAAc"}
1
+ {"version":3,"file":"analyzer.d.ts","names":[],"sources":["../../src/funnel/analyzer.ts"],"mappings":";;;cAQa,cAAA;EACX,OAAA,CACE,MAAA,EAAQ,cAAA,IACR,UAAA,EAAY,gBAAA,GACX,cAAA;EAAA,QAmCK,YAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"hypothesis-generator.d.ts","names":[],"sources":["../../src/growth/hypothesis-generator.ts"],"sourcesContent":[],"mappings":";;;UAEiB,0BAAA;;AAAjB;AAIa,cAAA,yBAAA,CAAyB;EAGd,iBAAA,QAAA;EAIJ,WAAA,CAAA,OAAA,CAAA,EAJI,0BAIJ;EAAiB,QAAA,CAAA,OAAA,EAAjB,YAAiB,EAAA,CAAA,EAAA,gBAAA,EAAA;EAAgB,QAAA,UAAA"}
1
+ {"version":3,"file":"hypothesis-generator.d.ts","names":[],"sources":["../../src/growth/hypothesis-generator.ts"],"mappings":";;;UAEiB,0BAAA;EACf,QAAA;AAAA;AAAA,cAGW,yBAAA;EAAA,iBACM,QAAA;cAEL,OAAA,GAAU,0BAAA;EAItB,QAAA,CAAS,OAAA,EAAS,YAAA,KAAiB,gBAAA;EAAA,QAQ3B,UAAA;EAAA,QAaA,KAAA;EAAA,QAMA,MAAA;EAAA,QAMA,SAAA;AAAA"}
package/dist/index.d.ts CHANGED
@@ -5,4 +5,6 @@ import { FunnelAnalyzer } from "./funnel/analyzer.js";
5
5
  import { GrowthHypothesisGenerator, HypothesisGeneratorOptions } from "./growth/hypothesis-generator.js";
6
6
  import { LifecycleMetricSource, LifecycleStageChangePayload, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals } from "./lifecycle/metric-collectors.js";
7
7
  import { PostHogLikeClient, trackLifecycleAssessment, trackLifecycleStageChange } from "./lifecycle/posthog-bridge.js";
8
- export { AnalyticsEvent, ChurnPredictor, ChurnPredictorOptions, ChurnSignal, CohortAnalysis, CohortDefinition, CohortEvent, CohortStats, CohortTracker, FunnelAnalysis, FunnelAnalyzer, FunnelDefinition, FunnelStep, FunnelStepResult, GrowthHypothesis, GrowthHypothesisGenerator, GrowthMetric, HypothesisGeneratorOptions, LifecycleMetricSource, LifecycleStageChangePayload, PostHogLikeClient, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
8
+ import { PosthogLifecycleMetricSource, PosthogLifecycleMetricSourceOptions } from "./lifecycle/posthog-metric-source.js";
9
+ import { PosthogAnalyticsEventSource, PosthogAnalyticsEventSourceOptions } from "./posthog/event-source.js";
10
+ export { AnalyticsEvent, ChurnPredictor, ChurnPredictorOptions, ChurnSignal, CohortAnalysis, CohortDefinition, CohortEvent, CohortStats, CohortTracker, FunnelAnalysis, FunnelAnalyzer, FunnelDefinition, FunnelStep, FunnelStepResult, GrowthHypothesis, GrowthHypothesisGenerator, GrowthMetric, HypothesisGeneratorOptions, LifecycleMetricSource, LifecycleStageChangePayload, PostHogLikeClient, PosthogAnalyticsEventSource, PosthogAnalyticsEventSourceOptions, PosthogLifecycleMetricSource, PosthogLifecycleMetricSourceOptions, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
package/dist/index.js CHANGED
@@ -6,5 +6,7 @@ import "./churn/index.js";
6
6
  import { GrowthHypothesisGenerator } from "./growth/hypothesis-generator.js";
7
7
  import { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals } from "./lifecycle/metric-collectors.js";
8
8
  import { trackLifecycleAssessment, trackLifecycleStageChange } from "./lifecycle/posthog-bridge.js";
9
+ import { PosthogLifecycleMetricSource } from "./lifecycle/posthog-metric-source.js";
10
+ import { PosthogAnalyticsEventSource } from "./posthog/event-source.js";
9
11
 
10
- export { ChurnPredictor, CohortTracker, FunnelAnalyzer, GrowthHypothesisGenerator, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
12
+ export { ChurnPredictor, CohortTracker, FunnelAnalyzer, GrowthHypothesisGenerator, PosthogAnalyticsEventSource, PosthogLifecycleMetricSource, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
@@ -1,3 +1,4 @@
1
1
  import { LifecycleMetricSource, LifecycleStageChangePayload, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals } from "./metric-collectors.js";
2
2
  import { PostHogLikeClient, trackLifecycleAssessment, trackLifecycleStageChange } from "./posthog-bridge.js";
3
- export { LifecycleMetricSource, LifecycleStageChangePayload, PostHogLikeClient, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
3
+ import { PosthogLifecycleMetricSource, PosthogLifecycleMetricSourceOptions } from "./posthog-metric-source.js";
4
+ export { LifecycleMetricSource, LifecycleStageChangePayload, PostHogLikeClient, PosthogLifecycleMetricSource, PosthogLifecycleMetricSourceOptions, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
@@ -1,4 +1,5 @@
1
1
  import { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals } from "./metric-collectors.js";
2
2
  import { trackLifecycleAssessment, trackLifecycleStageChange } from "./posthog-bridge.js";
3
+ import { PosthogLifecycleMetricSource } from "./posthog-metric-source.js";
3
4
 
4
- export { collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
5
+ export { PosthogLifecycleMetricSource, collectLifecycleMetrics, createStageChangeEvent, lifecycleEventNames, metricsToSignals, trackLifecycleAssessment, trackLifecycleStageChange };
@@ -1 +1 @@
1
- {"version":3,"file":"metric-collectors.d.ts","names":[],"sources":["../../src/lifecycle/metric-collectors.ts"],"sourcesContent":[],"mappings":";;;;UAOiB,qBAAA;oBACG;EADH,oBAAA,GAAA,EAEU,OAFW,CAAA,MAAA,GAAA,SAAA,CAAA;EAClB,gBAAA,GAAA,EAEG,OAFH,CAAA,MAAA,GAAA,SAAA,CAAA;EACO,0BAAA,GAAA,EAEM,OAFN,CAAA,MAAA,GAAA,SAAA,CAAA;EACJ,gBAAA,GAAA,EAEA,OAFA,CAAA,MAAA,GAAA,SAAA,CAAA;EACU,WAAA,GAAA,EAEf,OAFe,CAAA,MAAA,GAAA,SAAA,CAAA;EACV,eAAA,GAAA,EAED,OAFC,CAAA,MAAA,GAAA,SAAA,CAAA;;AAED,cAGT,uBAHS,EAAA,CAAA,MAAA,EAIZ,qBAJY,EAAA,GAKnB,OALmB,CAKX,uBALW,CAAA;AAAO,cAmChB,gBAnCgB,EAAA,CAAA,OAAA,EAoClB,uBApCkB,EAAA,QAAA,CAAA,EAAA,MAAA,EAAA,GAsC1B,eAtC0B,EAAA;AAGhB,cAkDA,mBApBZ,EAAA;EA7BS,SAAA,aAAA,EAAA,0BAAA;EACC,SAAA,YAAA,EAAA,yBAAA;EAAR,SAAA,gBAAA,EAAA,6BAAA;CAAO;AA8BG,UAwBI,2BAAA,CAvBN;EAiBE,QAAA,CAAA,EAAA,MAAA;EAMI,aAAA,CAAA,EAEC,cAF0B;EAO/B,SAAA,EAJA,cAYX;;;cARW,kCACF,gCACR"}
1
+ {"version":3,"file":"metric-collectors.d.ts","names":[],"sources":["../../src/lifecycle/metric-collectors.ts"],"mappings":";;;;UAOiB,qBAAA;EACf,cAAA,IAAkB,OAAA;EAClB,oBAAA,KAAyB,OAAA;EACzB,gBAAA,KAAqB,OAAA;EACrB,0BAAA,KAA+B,OAAA;EAC/B,gBAAA,KAAqB,OAAA;EACrB,WAAA,KAAgB,OAAA;EAChB,eAAA,KAAoB,OAAA;AAAA;AAAA,cAGT,uBAAA,GACX,MAAA,EAAQ,qBAAA,KACP,OAAA,CAAQ,uBAAA;AAAA,cA8BE,gBAAA,GACX,OAAA,EAAS,uBAAA,EACT,QAAA,cACC,eAAA;AAAA,cAeU,mBAAA;EAAA;;;;UAMI,2BAAA;EACf,QAAA;EACA,aAAA,GAAgB,cAAA;EAChB,SAAA,EAAW,cAAA;EACX,UAAA;AAAA;AAAA,cAGW,sBAAA,GACX,OAAA,EAAS,2BAAA,KACR,cAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"posthog-bridge.d.ts","names":[],"sources":["../../src/lifecycle/posthog-bridge.ts"],"sourcesContent":[],"mappings":";;;UAGiB,iBAAA;;IAAA,UAAA,EAAA,MAAiB;IAQrB,KAAA,EAAA,MAAA;IACH,UAAA,CAAA,EALO,MAKP,CAAA,MAAA,EAAA,OAAA,CAAA;EAEI,CAAA,EAAA,GANN,OAMM,CAAA,IAAA,CAAA,GAAA,IAAA;;AAAmB,cAHpB,wBAGoB,EAAA,CAAA,MAAA,EAFvB,iBAEuB,EAAA,QAAA,EAAA,MAAA,EAAA,UAAA,EAAnB,mBAAmB,EAAA,GAAA,OAAA,CAAA,IAAA,CAAA;AAapB,cAAA,yBACH,EAAA,CAAA,MAAA,EAAA,iBAGS,EAAA,QAAA,EAAA,MAAA,EAAA,aAAA,EAAA,MAAA,GAAA,SAAA,EAAA,SAAA,EAAA,MAAA,EAAA,GAAA,OAAA,CAAA,IAAA,CAAA"}
1
+ {"version":3,"file":"posthog-bridge.d.ts","names":[],"sources":["../../src/lifecycle/posthog-bridge.ts"],"mappings":";;;UAGiB,iBAAA;EACf,OAAA,GAAU,KAAA;IACR,UAAA;IACA,KAAA;IACA,UAAA,GAAa,MAAA;EAAA,MACT,OAAA;AAAA;AAAA,cAGK,wBAAA,GACX,MAAA,EAAQ,iBAAA,EACR,QAAA,UACA,UAAA,EAAY,mBAAA,KAAmB,OAAA;AAAA,cAapB,yBAAA,GACX,MAAA,EAAQ,iBAAA,EACR,QAAA,UACA,aAAA,sBACA,SAAA,aAAiB,OAAA"}
@@ -0,0 +1,47 @@
1
+ import { LifecycleMetricSource } from "./metric-collectors.js";
2
+ import { AnalyticsReader } from "@contractspec/lib.contracts/integrations/providers/analytics";
3
+
4
+ //#region src/lifecycle/posthog-metric-source.d.ts
5
+ interface PosthogLifecycleMetricSourceOptions {
6
+ activityEvents?: string[];
7
+ retentionWindowDays?: number;
8
+ revenueEvent?: string;
9
+ revenueProperty?: string;
10
+ customerEvent?: string;
11
+ customerProperty?: string;
12
+ teamSizeEvent?: string;
13
+ teamSizeProperty?: string;
14
+ burnMultipleEvent?: string;
15
+ burnMultipleProperty?: string;
16
+ }
17
+ declare class PosthogLifecycleMetricSource implements LifecycleMetricSource {
18
+ private readonly reader;
19
+ private readonly activityEvents?;
20
+ private readonly retentionWindowDays;
21
+ private readonly revenueEvent?;
22
+ private readonly revenueProperty?;
23
+ private readonly customerEvent?;
24
+ private readonly customerProperty?;
25
+ private readonly teamSizeEvent?;
26
+ private readonly teamSizeProperty?;
27
+ private readonly burnMultipleEvent?;
28
+ private readonly burnMultipleProperty?;
29
+ constructor(reader: AnalyticsReader, options?: PosthogLifecycleMetricSourceOptions);
30
+ getActiveUsers(): Promise<number | undefined>;
31
+ getWeeklyActiveUsers(): Promise<number | undefined>;
32
+ getRetentionRate(): Promise<number | undefined>;
33
+ getMonthlyRecurringRevenue(): Promise<number | undefined>;
34
+ getCustomerCount(): Promise<number | undefined>;
35
+ getTeamSize(): Promise<number | undefined>;
36
+ getBurnMultiple(): Promise<number | undefined>;
37
+ private countDistinctUsers;
38
+ private countDistinctUsersBetween;
39
+ private countReturningUsers;
40
+ private sumMetric;
41
+ private countDistinctUsersByProperty;
42
+ private latestMetric;
43
+ private queryHogQL;
44
+ }
45
+ //#endregion
46
+ export { PosthogLifecycleMetricSource, PosthogLifecycleMetricSourceOptions };
47
+ //# sourceMappingURL=posthog-metric-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posthog-metric-source.d.ts","names":[],"sources":["../../src/lifecycle/posthog-metric-source.ts"],"mappings":";;;;UAMiB,mCAAA;EACf,cAAA;EACA,mBAAA;EACA,YAAA;EACA,eAAA;EACA,aAAA;EACA,gBAAA;EACA,aAAA;EACA,gBAAA;EACA,iBAAA;EACA,oBAAA;AAAA;AAAA,cAGW,4BAAA,YAAwC,qBAAA;EAAA,iBAClC,MAAA;EAAA,iBACA,cAAA;EAAA,iBACA,mBAAA;EAAA,iBACA,YAAA;EAAA,iBACA,eAAA;EAAA,iBACA,aAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,aAAA;EAAA,iBACA,gBAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,oBAAA;cAGf,MAAA,EAAQ,eAAA,EACR,OAAA,GAAS,mCAAA;EAeL,cAAA,CAAA,GAAkB,OAAA;EAIlB,oBAAA,CAAA,GAAwB,OAAA;EAIxB,gBAAA,CAAA,GAAoB,OAAA;EAkBpB,0BAAA,CAAA,GAA8B,OAAA;EAK9B,gBAAA,CAAA,GAAoB,OAAA;EAQpB,WAAA,CAAA,GAAe,OAAA;EAKf,eAAA,CAAA,GAAmB,OAAA;EAAA,QAKX,kBAAA;EAAA,QAKA,yBAAA;EAAA,QAwBA,mBAAA;EAAA,QA8BA,SAAA;EAAA,QAgBA,4BAAA;EAAA,QAiBA,YAAA;EAAA,QAkBA,UAAA;AAAA"}
@@ -0,0 +1,186 @@
1
+ //#region src/lifecycle/posthog-metric-source.ts
2
+ var PosthogLifecycleMetricSource = class {
3
+ reader;
4
+ activityEvents;
5
+ retentionWindowDays;
6
+ revenueEvent;
7
+ revenueProperty;
8
+ customerEvent;
9
+ customerProperty;
10
+ teamSizeEvent;
11
+ teamSizeProperty;
12
+ burnMultipleEvent;
13
+ burnMultipleProperty;
14
+ constructor(reader, options = {}) {
15
+ this.reader = reader;
16
+ this.activityEvents = options.activityEvents;
17
+ this.retentionWindowDays = options.retentionWindowDays ?? 7;
18
+ this.revenueEvent = options.revenueEvent;
19
+ this.revenueProperty = options.revenueProperty ?? "amount";
20
+ this.customerEvent = options.customerEvent;
21
+ this.customerProperty = options.customerProperty ?? "is_customer";
22
+ this.teamSizeEvent = options.teamSizeEvent;
23
+ this.teamSizeProperty = options.teamSizeProperty ?? "team_size";
24
+ this.burnMultipleEvent = options.burnMultipleEvent;
25
+ this.burnMultipleProperty = options.burnMultipleProperty ?? "burn_multiple";
26
+ }
27
+ async getActiveUsers() {
28
+ return this.countDistinctUsers(1);
29
+ }
30
+ async getWeeklyActiveUsers() {
31
+ return this.countDistinctUsers(7);
32
+ }
33
+ async getRetentionRate() {
34
+ const windowDays = this.retentionWindowDays;
35
+ const now = /* @__PURE__ */ new Date();
36
+ const prevEnd = /* @__PURE__ */ new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1e3);
37
+ const prevStart = /* @__PURE__ */ new Date(now.getTime() - windowDays * 2 * 24 * 60 * 60 * 1e3);
38
+ const returningUsers = await this.countReturningUsers(prevStart, prevEnd, now);
39
+ const prevUsers = await this.countDistinctUsersBetween(prevStart, prevEnd);
40
+ if (prevUsers === void 0 || prevUsers === 0) return void 0;
41
+ return returningUsers / prevUsers;
42
+ }
43
+ async getMonthlyRecurringRevenue() {
44
+ if (!this.revenueEvent || !this.revenueProperty) return void 0;
45
+ return this.sumMetric(this.revenueEvent, this.revenueProperty);
46
+ }
47
+ async getCustomerCount() {
48
+ if (!this.customerEvent || !this.customerProperty) return void 0;
49
+ return this.countDistinctUsersByProperty(this.customerEvent, this.customerProperty);
50
+ }
51
+ async getTeamSize() {
52
+ if (!this.teamSizeEvent || !this.teamSizeProperty) return void 0;
53
+ return this.latestMetric(this.teamSizeEvent, this.teamSizeProperty);
54
+ }
55
+ async getBurnMultiple() {
56
+ if (!this.burnMultipleEvent || !this.burnMultipleProperty) return void 0;
57
+ return this.latestMetric(this.burnMultipleEvent, this.burnMultipleProperty);
58
+ }
59
+ async countDistinctUsers(days) {
60
+ const range = buildDateRange(days);
61
+ return this.countDistinctUsersBetween(range.from, range.to);
62
+ }
63
+ async countDistinctUsersBetween(from, to) {
64
+ const eventFilter = buildEventFilter(this.activityEvents, "activityEvent");
65
+ return readSingleNumber(await this.queryHogQL({
66
+ query: [
67
+ "select",
68
+ " countDistinct(distinct_id) as total",
69
+ "from events",
70
+ `where timestamp >= {dateFrom} and timestamp < {dateTo}`,
71
+ eventFilter.clause ? `and ${eventFilter.clause}` : ""
72
+ ].filter(Boolean).join("\n"),
73
+ values: {
74
+ dateFrom: from.toISOString(),
75
+ dateTo: to.toISOString(),
76
+ ...eventFilter.values
77
+ }
78
+ }));
79
+ }
80
+ async countReturningUsers(previousStart, previousEnd, currentEnd) {
81
+ const eventFilter = buildEventFilter(this.activityEvents, "activityEvent");
82
+ return readSingleNumber(await this.queryHogQL({
83
+ query: [
84
+ "select",
85
+ " countDistinct(distinct_id) as total",
86
+ "from events",
87
+ `where timestamp >= {currentFrom} and timestamp < {currentTo}`,
88
+ eventFilter.clause ? `and ${eventFilter.clause}` : "",
89
+ "and distinct_id in (",
90
+ " select distinct_id from events",
91
+ " where timestamp >= {previousFrom} and timestamp < {previousTo}",
92
+ eventFilter.clause ? `and ${eventFilter.clause}` : "",
93
+ ")"
94
+ ].join("\n"),
95
+ values: {
96
+ currentFrom: previousEnd.toISOString(),
97
+ currentTo: currentEnd.toISOString(),
98
+ previousFrom: previousStart.toISOString(),
99
+ previousTo: previousEnd.toISOString(),
100
+ ...eventFilter.values
101
+ }
102
+ })) ?? 0;
103
+ }
104
+ async sumMetric(eventName, propertyKey) {
105
+ return readSingleNumber(await this.queryHogQL({
106
+ query: [
107
+ "select",
108
+ ` sum(properties.${propertyKey}) as total`,
109
+ "from events",
110
+ "where event = {eventName}"
111
+ ].join("\n"),
112
+ values: { eventName }
113
+ }));
114
+ }
115
+ async countDistinctUsersByProperty(eventName, propertyKey) {
116
+ return readSingleNumber(await this.queryHogQL({
117
+ query: [
118
+ "select",
119
+ " countDistinct(distinct_id) as total",
120
+ "from events",
121
+ "where event = {eventName}",
122
+ `and properties.${propertyKey} = {propertyValue}`
123
+ ].join("\n"),
124
+ values: {
125
+ eventName,
126
+ propertyValue: true
127
+ }
128
+ }));
129
+ }
130
+ async latestMetric(eventName, propertyKey) {
131
+ return readSingleNumber(await this.queryHogQL({
132
+ query: [
133
+ "select",
134
+ ` properties.${propertyKey} as value`,
135
+ "from events",
136
+ "where event = {eventName}",
137
+ "order by timestamp desc",
138
+ "limit 1"
139
+ ].join("\n"),
140
+ values: { eventName }
141
+ }));
142
+ }
143
+ async queryHogQL(input) {
144
+ if (!this.reader.queryHogQL) throw new Error("Analytics reader does not support HogQL queries.");
145
+ return this.reader.queryHogQL(input);
146
+ }
147
+ };
148
+ function buildDateRange(days) {
149
+ const to = /* @__PURE__ */ new Date();
150
+ return {
151
+ from: /* @__PURE__ */ new Date(to.getTime() - days * 24 * 60 * 60 * 1e3),
152
+ to
153
+ };
154
+ }
155
+ function buildEventFilter(events, prefix) {
156
+ if (!events || events.length === 0) return {};
157
+ if (events.length === 1) return {
158
+ clause: `event = {${prefix}0}`,
159
+ values: { [`${prefix}0`]: events[0] }
160
+ };
161
+ const clauses = events.map((_event, index) => `event = {${prefix}${index}}`);
162
+ const values = {};
163
+ events.forEach((event, index) => {
164
+ values[`${prefix}${index}`] = event;
165
+ });
166
+ return {
167
+ clause: `(${clauses.join(" or ")})`,
168
+ values
169
+ };
170
+ }
171
+ function readSingleNumber(result) {
172
+ if (!Array.isArray(result.results) || result.results.length === 0) return;
173
+ const firstRow = result.results[0];
174
+ if (Array.isArray(firstRow) && firstRow.length > 0) {
175
+ const value = firstRow[0];
176
+ if (typeof value === "number" && Number.isFinite(value)) return value;
177
+ if (typeof value === "string" && value.trim()) {
178
+ const parsed = Number(value);
179
+ if (Number.isFinite(parsed)) return parsed;
180
+ }
181
+ }
182
+ }
183
+
184
+ //#endregion
185
+ export { PosthogLifecycleMetricSource };
186
+ //# sourceMappingURL=posthog-metric-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posthog-metric-source.js","names":[],"sources":["../../src/lifecycle/posthog-metric-source.ts"],"sourcesContent":["import type {\n AnalyticsQueryResult,\n AnalyticsReader,\n} from '@contractspec/lib.contracts/integrations/providers/analytics';\nimport type { LifecycleMetricSource } from './metric-collectors';\n\nexport interface PosthogLifecycleMetricSourceOptions {\n activityEvents?: string[];\n retentionWindowDays?: number;\n revenueEvent?: string;\n revenueProperty?: string;\n customerEvent?: string;\n customerProperty?: string;\n teamSizeEvent?: string;\n teamSizeProperty?: string;\n burnMultipleEvent?: string;\n burnMultipleProperty?: string;\n}\n\nexport class PosthogLifecycleMetricSource implements LifecycleMetricSource {\n private readonly reader: AnalyticsReader;\n private readonly activityEvents?: string[];\n private readonly retentionWindowDays: number;\n private readonly revenueEvent?: string;\n private readonly revenueProperty?: string;\n private readonly customerEvent?: string;\n private readonly customerProperty?: string;\n private readonly teamSizeEvent?: string;\n private readonly teamSizeProperty?: string;\n private readonly burnMultipleEvent?: string;\n private readonly burnMultipleProperty?: string;\n\n constructor(\n reader: AnalyticsReader,\n options: PosthogLifecycleMetricSourceOptions = {}\n ) {\n this.reader = reader;\n this.activityEvents = options.activityEvents;\n this.retentionWindowDays = options.retentionWindowDays ?? 7;\n this.revenueEvent = options.revenueEvent;\n this.revenueProperty = options.revenueProperty ?? 'amount';\n this.customerEvent = options.customerEvent;\n this.customerProperty = options.customerProperty ?? 'is_customer';\n this.teamSizeEvent = options.teamSizeEvent;\n this.teamSizeProperty = options.teamSizeProperty ?? 'team_size';\n this.burnMultipleEvent = options.burnMultipleEvent;\n this.burnMultipleProperty = options.burnMultipleProperty ?? 'burn_multiple';\n }\n\n async getActiveUsers(): Promise<number | undefined> {\n return this.countDistinctUsers(1);\n }\n\n async getWeeklyActiveUsers(): Promise<number | undefined> {\n return this.countDistinctUsers(7);\n }\n\n async getRetentionRate(): Promise<number | undefined> {\n const windowDays = this.retentionWindowDays;\n const now = new Date();\n const prevEnd = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000);\n const prevStart = new Date(\n now.getTime() - windowDays * 2 * 24 * 60 * 60 * 1000\n );\n\n const returningUsers = await this.countReturningUsers(\n prevStart,\n prevEnd,\n now\n );\n const prevUsers = await this.countDistinctUsersBetween(prevStart, prevEnd);\n if (prevUsers === undefined || prevUsers === 0) return undefined;\n return returningUsers / prevUsers;\n }\n\n async getMonthlyRecurringRevenue(): Promise<number | undefined> {\n if (!this.revenueEvent || !this.revenueProperty) return undefined;\n return this.sumMetric(this.revenueEvent, this.revenueProperty);\n }\n\n async getCustomerCount(): Promise<number | undefined> {\n if (!this.customerEvent || !this.customerProperty) return undefined;\n return this.countDistinctUsersByProperty(\n this.customerEvent,\n this.customerProperty\n );\n }\n\n async getTeamSize(): Promise<number | undefined> {\n if (!this.teamSizeEvent || !this.teamSizeProperty) return undefined;\n return this.latestMetric(this.teamSizeEvent, this.teamSizeProperty);\n }\n\n async getBurnMultiple(): Promise<number | undefined> {\n if (!this.burnMultipleEvent || !this.burnMultipleProperty) return undefined;\n return this.latestMetric(this.burnMultipleEvent, this.burnMultipleProperty);\n }\n\n private async countDistinctUsers(days: number): Promise<number | undefined> {\n const range = buildDateRange(days);\n return this.countDistinctUsersBetween(range.from, range.to);\n }\n\n private async countDistinctUsersBetween(\n from: Date,\n to: Date\n ): Promise<number | undefined> {\n const eventFilter = buildEventFilter(this.activityEvents, 'activityEvent');\n const result = await this.queryHogQL({\n query: [\n 'select',\n ' countDistinct(distinct_id) as total',\n 'from events',\n `where timestamp >= {dateFrom} and timestamp < {dateTo}`,\n eventFilter.clause ? `and ${eventFilter.clause}` : '',\n ]\n .filter(Boolean)\n .join('\\n'),\n values: {\n dateFrom: from.toISOString(),\n dateTo: to.toISOString(),\n ...eventFilter.values,\n },\n });\n return readSingleNumber(result);\n }\n\n private async countReturningUsers(\n previousStart: Date,\n previousEnd: Date,\n currentEnd: Date\n ): Promise<number> {\n const eventFilter = buildEventFilter(this.activityEvents, 'activityEvent');\n const result = await this.queryHogQL({\n query: [\n 'select',\n ' countDistinct(distinct_id) as total',\n 'from events',\n `where timestamp >= {currentFrom} and timestamp < {currentTo}`,\n eventFilter.clause ? `and ${eventFilter.clause}` : '',\n 'and distinct_id in (',\n ' select distinct_id from events',\n ' where timestamp >= {previousFrom} and timestamp < {previousTo}',\n eventFilter.clause ? `and ${eventFilter.clause}` : '',\n ')',\n ].join('\\n'),\n values: {\n currentFrom: previousEnd.toISOString(),\n currentTo: currentEnd.toISOString(),\n previousFrom: previousStart.toISOString(),\n previousTo: previousEnd.toISOString(),\n ...eventFilter.values,\n },\n });\n return readSingleNumber(result) ?? 0;\n }\n\n private async sumMetric(\n eventName: string,\n propertyKey: string\n ): Promise<number | undefined> {\n const result = await this.queryHogQL({\n query: [\n 'select',\n ` sum(properties.${propertyKey}) as total`,\n 'from events',\n 'where event = {eventName}',\n ].join('\\n'),\n values: { eventName },\n });\n return readSingleNumber(result);\n }\n\n private async countDistinctUsersByProperty(\n eventName: string,\n propertyKey: string\n ): Promise<number | undefined> {\n const result = await this.queryHogQL({\n query: [\n 'select',\n ' countDistinct(distinct_id) as total',\n 'from events',\n 'where event = {eventName}',\n `and properties.${propertyKey} = {propertyValue}`,\n ].join('\\n'),\n values: { eventName, propertyValue: true },\n });\n return readSingleNumber(result);\n }\n\n private async latestMetric(\n eventName: string,\n propertyKey: string\n ): Promise<number | undefined> {\n const result = await this.queryHogQL({\n query: [\n 'select',\n ` properties.${propertyKey} as value`,\n 'from events',\n 'where event = {eventName}',\n 'order by timestamp desc',\n 'limit 1',\n ].join('\\n'),\n values: { eventName },\n });\n return readSingleNumber(result);\n }\n\n private async queryHogQL(input: {\n query: string;\n values: Record<string, unknown>;\n }): Promise<AnalyticsQueryResult> {\n if (!this.reader.queryHogQL) {\n throw new Error('Analytics reader does not support HogQL queries.');\n }\n return this.reader.queryHogQL(input);\n }\n}\n\nfunction buildDateRange(days: number): { from: Date; to: Date } {\n const to = new Date();\n const from = new Date(to.getTime() - days * 24 * 60 * 60 * 1000);\n return { from, to };\n}\n\nfunction buildEventFilter(\n events: string[] | undefined,\n prefix: string\n): { clause?: string; values?: Record<string, unknown> } {\n if (!events || events.length === 0) return {};\n if (events.length === 1) {\n return {\n clause: `event = {${prefix}0}`,\n values: { [`${prefix}0`]: events[0] },\n };\n }\n const clauses = events.map((_event, index) => `event = {${prefix}${index}}`);\n const values: Record<string, unknown> = {};\n events.forEach((event, index) => {\n values[`${prefix}${index}`] = event;\n });\n return {\n clause: `(${clauses.join(' or ')})`,\n values,\n };\n}\n\nfunction readSingleNumber(result: AnalyticsQueryResult): number | undefined {\n if (!Array.isArray(result.results) || result.results.length === 0) {\n return undefined;\n }\n const firstRow = result.results[0];\n if (Array.isArray(firstRow) && firstRow.length > 0) {\n const value = firstRow[0];\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { AnalyticsEvent, CohortDefinition, FunnelDefinition, GrowthMetric } from "../types.js";
2
+ import { AnalyticsReader, DateRangeInput } from "@contractspec/lib.contracts/integrations/providers/analytics";
3
+
4
+ //#region src/posthog/event-source.d.ts
5
+ interface PosthogAnalyticsEventSourceOptions {
6
+ limitPerEvent?: number;
7
+ tenantPropertyKey?: string;
8
+ defaultRangeDays?: number;
9
+ }
10
+ declare class PosthogAnalyticsEventSource {
11
+ private readonly reader;
12
+ private readonly limitPerEvent;
13
+ private readonly tenantPropertyKey;
14
+ private readonly defaultRangeDays;
15
+ constructor(reader: AnalyticsReader, options?: PosthogAnalyticsEventSourceOptions);
16
+ getEventsForFunnel(definition: FunnelDefinition, dateRange?: DateRangeInput): Promise<AnalyticsEvent[]>;
17
+ getEventsForCohort(definition: CohortDefinition, dateRange?: DateRangeInput, eventNames?: string[]): Promise<AnalyticsEvent[]>;
18
+ getUserActivity(userId: string, dateRange?: DateRangeInput, limit?: number): Promise<AnalyticsEvent[]>;
19
+ getGrowthMetrics(metricNames: string[], dateRange?: DateRangeInput): Promise<GrowthMetric[]>;
20
+ private getEventsByNames;
21
+ private countEvents;
22
+ }
23
+ //#endregion
24
+ export { PosthogAnalyticsEventSource, PosthogAnalyticsEventSourceOptions };
25
+ //# sourceMappingURL=event-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-source.d.ts","names":[],"sources":["../../src/posthog/event-source.ts"],"mappings":";;;;UAWiB,kCAAA;EACf,aAAA;EACA,iBAAA;EACA,gBAAA;AAAA;AAAA,cAGW,2BAAA;EAAA,iBACM,MAAA;EAAA,iBACA,aAAA;EAAA,iBACA,iBAAA;EAAA,iBACA,gBAAA;cAGf,MAAA,EAAQ,eAAA,EACR,OAAA,GAAS,kCAAA;EAQL,kBAAA,CACJ,UAAA,EAAY,gBAAA,EACZ,SAAA,GAAY,cAAA,GACX,OAAA,CAAQ,cAAA;EAKL,kBAAA,CACJ,UAAA,EAAY,gBAAA,EACZ,SAAA,GAAY,cAAA,EACZ,UAAA,cACC,OAAA,CAAQ,cAAA;EAQL,eAAA,CACJ,MAAA,UACA,SAAA,GAAY,cAAA,EACZ,KAAA,YACC,OAAA,CAAQ,cAAA;EAcL,gBAAA,CACJ,WAAA,YACA,SAAA,GAAY,cAAA,GACX,OAAA,CAAQ,YAAA;EAAA,QAgBG,gBAAA;EAAA,QA+BA,WAAA;AAAA"}
@@ -0,0 +1,119 @@
1
+ //#region src/posthog/event-source.ts
2
+ var PosthogAnalyticsEventSource = class {
3
+ reader;
4
+ limitPerEvent;
5
+ tenantPropertyKey;
6
+ defaultRangeDays;
7
+ constructor(reader, options = {}) {
8
+ this.reader = reader;
9
+ this.limitPerEvent = options.limitPerEvent ?? 1e3;
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] === "*") return this.getEventsByNames([], dateRange, definition.periods * 500);
20
+ return this.getEventsByNames(events, dateRange, definition.periods * 500);
21
+ }
22
+ async getUserActivity(userId, dateRange, limit = 1e3) {
23
+ if (!this.reader.getEvents) throw new Error("Analytics reader does not support event queries.");
24
+ return (await this.reader.getEvents({
25
+ distinctId: userId,
26
+ dateRange,
27
+ limit
28
+ })).results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
29
+ }
30
+ async getGrowthMetrics(metricNames, dateRange) {
31
+ const range = resolveRange(dateRange, this.defaultRangeDays);
32
+ const previous = shiftRange(range);
33
+ const results = [];
34
+ for (const metricName of metricNames) {
35
+ const current = await this.countEvents(metricName, range);
36
+ const previousCount = await this.countEvents(metricName, previous);
37
+ results.push({
38
+ name: metricName,
39
+ current,
40
+ previous: previousCount
41
+ });
42
+ }
43
+ return results;
44
+ }
45
+ async getEventsByNames(eventNames, dateRange, limitPerEvent = this.limitPerEvent) {
46
+ if (!this.reader.getEvents) throw new Error("Analytics reader does not support event queries.");
47
+ if (eventNames.length === 0) return (await this.reader.getEvents({
48
+ dateRange,
49
+ limit: limitPerEvent
50
+ })).results.map((event) => toAnalyticsEvent(event, this.tenantPropertyKey));
51
+ const events = [];
52
+ for (const eventName of eventNames) (await this.reader.getEvents({
53
+ event: eventName,
54
+ dateRange,
55
+ limit: limitPerEvent
56
+ })).results.forEach((event) => {
57
+ events.push(toAnalyticsEvent(event, this.tenantPropertyKey));
58
+ });
59
+ return events;
60
+ }
61
+ async countEvents(eventName, range) {
62
+ if (!this.reader.queryHogQL) throw new Error("Analytics reader does not support HogQL queries.");
63
+ return readSingleNumber(await this.reader.queryHogQL({
64
+ query: [
65
+ "select",
66
+ " count() as total",
67
+ "from events",
68
+ "where event = {eventName}",
69
+ "and timestamp >= {dateFrom}",
70
+ "and timestamp < {dateTo}"
71
+ ].join("\n"),
72
+ values: {
73
+ eventName,
74
+ dateFrom: range.from.toISOString(),
75
+ dateTo: range.to.toISOString()
76
+ }
77
+ })) ?? 0;
78
+ }
79
+ };
80
+ function toAnalyticsEvent(event, tenantPropertyKey) {
81
+ const tenantIdValue = event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;
82
+ return {
83
+ name: event.event,
84
+ userId: event.distinctId,
85
+ tenantId: typeof tenantIdValue === "string" ? tenantIdValue : void 0,
86
+ timestamp: event.timestamp,
87
+ properties: event.properties
88
+ };
89
+ }
90
+ function resolveRange(dateRange, defaultDays) {
91
+ const to = dateRange?.to instanceof Date ? dateRange.to : dateRange?.to ? new Date(dateRange.to) : /* @__PURE__ */ new Date();
92
+ return {
93
+ from: dateRange?.from instanceof Date ? dateRange.from : dateRange?.from ? new Date(dateRange.from) : /* @__PURE__ */ new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1e3),
94
+ to
95
+ };
96
+ }
97
+ function shiftRange(range) {
98
+ const duration = range.to.getTime() - range.from.getTime();
99
+ return {
100
+ from: new Date(range.from.getTime() - duration),
101
+ to: new Date(range.to.getTime() - duration)
102
+ };
103
+ }
104
+ function readSingleNumber(result) {
105
+ if (!Array.isArray(result.results) || result.results.length === 0) return;
106
+ const firstRow = result.results[0];
107
+ if (Array.isArray(firstRow) && firstRow.length > 0) {
108
+ const value = firstRow[0];
109
+ if (typeof value === "number" && Number.isFinite(value)) return value;
110
+ if (typeof value === "string" && value.trim()) {
111
+ const parsed = Number(value);
112
+ if (Number.isFinite(parsed)) return parsed;
113
+ }
114
+ }
115
+ }
116
+
117
+ //#endregion
118
+ export { PosthogAnalyticsEventSource };
119
+ //# sourceMappingURL=event-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-source.js","names":[],"sources":["../../src/posthog/event-source.ts"],"sourcesContent":["import type {\n AnalyticsReader,\n DateRangeInput,\n} from '@contractspec/lib.contracts/integrations/providers/analytics';\nimport type {\n AnalyticsEvent,\n CohortDefinition,\n FunnelDefinition,\n GrowthMetric,\n} from '../types';\n\nexport interface PosthogAnalyticsEventSourceOptions {\n limitPerEvent?: number;\n tenantPropertyKey?: string;\n defaultRangeDays?: number;\n}\n\nexport class PosthogAnalyticsEventSource {\n private readonly reader: AnalyticsReader;\n private readonly limitPerEvent: number;\n private readonly tenantPropertyKey: string;\n private readonly defaultRangeDays: number;\n\n constructor(\n reader: AnalyticsReader,\n options: PosthogAnalyticsEventSourceOptions = {}\n ) {\n this.reader = reader;\n this.limitPerEvent = options.limitPerEvent ?? 1000;\n this.tenantPropertyKey = options.tenantPropertyKey ?? 'tenantId';\n this.defaultRangeDays = options.defaultRangeDays ?? 30;\n }\n\n async getEventsForFunnel(\n definition: FunnelDefinition,\n dateRange?: DateRangeInput\n ): Promise<AnalyticsEvent[]> {\n const eventNames = definition.steps.map((step) => step.eventName);\n return this.getEventsByNames(eventNames, dateRange);\n }\n\n async getEventsForCohort(\n definition: CohortDefinition,\n dateRange?: DateRangeInput,\n eventNames?: string[]\n ): Promise<AnalyticsEvent[]> {\n const events = eventNames && eventNames.length > 0 ? eventNames : ['*'];\n if (events[0] === '*') {\n return this.getEventsByNames([], dateRange, definition.periods * 500);\n }\n return this.getEventsByNames(events, dateRange, definition.periods * 500);\n }\n\n async getUserActivity(\n userId: string,\n dateRange?: DateRangeInput,\n limit = 1000\n ): Promise<AnalyticsEvent[]> {\n if (!this.reader.getEvents) {\n throw new Error('Analytics reader does not support event queries.');\n }\n const response = await this.reader.getEvents({\n distinctId: userId,\n dateRange,\n limit,\n });\n return response.results.map((event) =>\n toAnalyticsEvent(event, this.tenantPropertyKey)\n );\n }\n\n async getGrowthMetrics(\n metricNames: string[],\n dateRange?: DateRangeInput\n ): Promise<GrowthMetric[]> {\n const range = resolveRange(dateRange, this.defaultRangeDays);\n const previous = shiftRange(range);\n const results: GrowthMetric[] = [];\n for (const metricName of metricNames) {\n const current = await this.countEvents(metricName, range);\n const previousCount = await this.countEvents(metricName, previous);\n results.push({\n name: metricName,\n current,\n previous: previousCount,\n });\n }\n return results;\n }\n\n private async getEventsByNames(\n eventNames: string[],\n dateRange?: DateRangeInput,\n limitPerEvent = this.limitPerEvent\n ): Promise<AnalyticsEvent[]> {\n if (!this.reader.getEvents) {\n throw new Error('Analytics reader does not support event queries.');\n }\n if (eventNames.length === 0) {\n const response = await this.reader.getEvents({\n dateRange,\n limit: limitPerEvent,\n });\n return response.results.map((event) =>\n toAnalyticsEvent(event, this.tenantPropertyKey)\n );\n }\n const events: AnalyticsEvent[] = [];\n for (const eventName of eventNames) {\n const response = await this.reader.getEvents({\n event: eventName,\n dateRange,\n limit: limitPerEvent,\n });\n response.results.forEach((event) => {\n events.push(toAnalyticsEvent(event, this.tenantPropertyKey));\n });\n }\n return events;\n }\n\n private async countEvents(\n eventName: string,\n range: { from: Date; to: Date }\n ): Promise<number> {\n if (!this.reader.queryHogQL) {\n throw new Error('Analytics reader does not support HogQL queries.');\n }\n const result = await this.reader.queryHogQL({\n query: [\n 'select',\n ' count() as total',\n 'from events',\n 'where event = {eventName}',\n 'and timestamp >= {dateFrom}',\n 'and timestamp < {dateTo}',\n ].join('\\n'),\n values: {\n eventName,\n dateFrom: range.from.toISOString(),\n dateTo: range.to.toISOString(),\n },\n });\n return readSingleNumber(result) ?? 0;\n }\n}\n\nfunction toAnalyticsEvent(\n event: {\n event: string;\n distinctId: string;\n properties?: Record<string, unknown>;\n timestamp: string;\n },\n tenantPropertyKey: string\n): AnalyticsEvent {\n const tenantIdValue =\n event.properties?.[tenantPropertyKey] ?? event.properties?.tenantId;\n return {\n name: event.event,\n userId: event.distinctId,\n tenantId: typeof tenantIdValue === 'string' ? tenantIdValue : undefined,\n timestamp: event.timestamp,\n properties: event.properties,\n };\n}\n\nfunction resolveRange(\n dateRange: DateRangeInput | undefined,\n defaultDays: number\n): { from: Date; to: Date } {\n const to =\n dateRange?.to instanceof Date\n ? dateRange.to\n : dateRange?.to\n ? new Date(dateRange.to)\n : new Date();\n const from =\n dateRange?.from instanceof Date\n ? dateRange.from\n : dateRange?.from\n ? new Date(dateRange.from)\n : new Date(to.getTime() - defaultDays * 24 * 60 * 60 * 1000);\n return { from, to };\n}\n\nfunction shiftRange(range: { from: Date; to: Date }): {\n from: Date;\n to: Date;\n} {\n const duration = range.to.getTime() - range.from.getTime();\n return {\n from: new Date(range.from.getTime() - duration),\n to: new Date(range.to.getTime() - duration),\n };\n}\n\nfunction readSingleNumber(result: { results: unknown }): number | undefined {\n if (!Array.isArray(result.results) || result.results.length === 0) {\n return undefined;\n }\n const firstRow = result.results[0];\n if (Array.isArray(firstRow) && firstRow.length > 0) {\n const value = firstRow[0];\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && value.trim()) {\n const parsed = Number(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n }\n return undefined;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { PosthogAnalyticsEventSource, PosthogAnalyticsEventSourceOptions } from "./event-source.js";
2
+ export { PosthogAnalyticsEventSource, PosthogAnalyticsEventSourceOptions };
@@ -0,0 +1,3 @@
1
+ import { PosthogAnalyticsEventSource } from "./event-source.js";
2
+
3
+ export { PosthogAnalyticsEventSource };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";UAAiB,cAAA;EAAA,IAAA,EAAA,MAAA;EAQA,MAAA,EAAA,MAAU;EAMV,QAAA,CAAA,EAAA,MAAA;EAMA,SAAA,EAAA,MAAA,GAhBK,IAgBW;EAOhB,UAAA,CAAA,EAtBF,MAsBgB,CAAA,MAAA,EACjB,OAAA,CAAA;AAKd;AAIiB,UA7BA,UAAA,CA6BgB;EAMhB,EAAA,EAAA,MAAA;EAOA,SAAA,EAAA,MAAc;EAKd,KAAA,CAAA,EAAA,CAAA,KAAW,EA5CV,cA4CU,EAAA,GAAA,OAAA;AAO5B;AAOiB,UAvDA,gBAAA,CAuDgB;;SArDxB;;;UAIQ,gBAAA;QACT;;;;;UAMS,cAAA;cACH;;SAEL;;UAGQ,WAAA,SAAoB;;;UAIpB,gBAAA;;;cAGH;;UAGG,WAAA;;;;;;UAOA,cAAA;cACH;WACH;;UAGM,WAAA;;;;;;UAOA,YAAA;;;;;;UAOA,gBAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";UAAiB,cAAA;EACf,IAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA,WAAoB,IAAA;EACpB,UAAA,GAAa,MAAA;AAAA;AAAA,UAGE,UAAA;EACf,EAAA;EACA,SAAA;EACA,KAAA,IAAS,KAAA,EAAO,cAAA;AAAA;AAAA,UAGD,gBAAA;EACf,IAAA;EACA,KAAA,EAAO,UAAA;EACP,WAAA;AAAA;AAAA,UAGe,gBAAA;EACf,IAAA,EAAM,UAAA;EACN,KAAA;EACA,cAAA;EACA,WAAA;AAAA;AAAA,UAGe,cAAA;EACf,UAAA,EAAY,gBAAA;EACZ,UAAA;EACA,KAAA,EAAO,gBAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,cAAA;EACnC,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,MAAA;EACA,OAAA;EACA,SAAA,GAAY,IAAA;AAAA;AAAA,UAGG,WAAA;EACf,SAAA;EACA,KAAA;EACA,SAAA;EACA,GAAA;AAAA;AAAA,UAGe,cAAA;EACf,UAAA,EAAY,gBAAA;EACZ,OAAA,EAAS,WAAA;AAAA;AAAA,UAGM,WAAA;EACf,MAAA;EACA,KAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,IAAA;EACA,OAAA;EACA,QAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,SAAA;EACA,MAAA;EACA,UAAA;EACA,MAAA;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.analytics",
3
- "version": "1.56.0",
3
+ "version": "1.57.0",
4
4
  "description": "Product analytics and growth metrics",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -30,13 +30,14 @@
30
30
  "test": "bun test"
31
31
  },
32
32
  "dependencies": {
33
- "@contractspec/lib.lifecycle": "1.56.0",
33
+ "@contractspec/lib.contracts": "1.57.0",
34
+ "@contractspec/lib.lifecycle": "1.57.0",
34
35
  "dayjs": "^1.11.13"
35
36
  },
36
37
  "devDependencies": {
37
- "@contractspec/tool.tsdown": "1.56.0",
38
- "@contractspec/tool.typescript": "1.56.0",
39
- "tsdown": "^0.19.0",
38
+ "@contractspec/tool.tsdown": "1.57.0",
39
+ "@contractspec/tool.typescript": "1.57.0",
40
+ "tsdown": "^0.20.3",
40
41
  "typescript": "^5.9.3"
41
42
  },
42
43
  "exports": {
@@ -52,6 +53,9 @@
52
53
  "./lifecycle": "./dist/lifecycle/index.js",
53
54
  "./lifecycle/metric-collectors": "./dist/lifecycle/metric-collectors.js",
54
55
  "./lifecycle/posthog-bridge": "./dist/lifecycle/posthog-bridge.js",
56
+ "./lifecycle/posthog-metric-source": "./dist/lifecycle/posthog-metric-source.js",
57
+ "./posthog": "./dist/posthog/index.js",
58
+ "./posthog/event-source": "./dist/posthog/event-source.js",
55
59
  "./types": "./dist/types.js",
56
60
  "./*": "./*"
57
61
  },
@@ -70,6 +74,9 @@
70
74
  "./lifecycle": "./dist/lifecycle/index.js",
71
75
  "./lifecycle/metric-collectors": "./dist/lifecycle/metric-collectors.js",
72
76
  "./lifecycle/posthog-bridge": "./dist/lifecycle/posthog-bridge.js",
77
+ "./lifecycle/posthog-metric-source": "./dist/lifecycle/posthog-metric-source.js",
78
+ "./posthog": "./dist/posthog/index.js",
79
+ "./posthog/event-source": "./dist/posthog/event-source.js",
73
80
  "./types": "./dist/types.js",
74
81
  "./*": "./*"
75
82
  },