@stacksjs/ts-analytics 0.1.6

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 (237) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +361 -0
  4. package/bin/cli.ts +169 -0
  5. package/dist/Analytics.d.ts +558 -0
  6. package/dist/api.d.ts +109 -0
  7. package/dist/batching.d.ts +93 -0
  8. package/dist/chunk-2mx7fq49.js +4 -0
  9. package/dist/chunk-3z29508k.js +204 -0
  10. package/dist/chunk-deephkz6.js +56 -0
  11. package/dist/chunk-j261vgyp.js +305 -0
  12. package/dist/chunk-xga17tz7.js +207 -0
  13. package/dist/config.d.ts +132 -0
  14. package/dist/dashboard/components/index.d.ts +80 -0
  15. package/dist/dashboard/composables/useAnalytics.d.ts +99 -0
  16. package/dist/dashboard/index.d.ts +110 -0
  17. package/dist/dashboard/types/index.d.ts +144 -0
  18. package/dist/dashboard/utils/index.d.ts +81 -0
  19. package/dist/dynamodb.d.ts +86 -0
  20. package/dist/funnels.d.ts +104 -0
  21. package/dist/geolocation.d.ts +96 -0
  22. package/dist/index.d.ts +371 -0
  23. package/dist/index.js +10211 -0
  24. package/dist/infrastructure/cdk.d.ts +60 -0
  25. package/dist/infrastructure/cloudformation.d.ts +44 -0
  26. package/dist/infrastructure/index.d.ts +45 -0
  27. package/dist/infrastructure/setup.d.ts +133 -0
  28. package/dist/integrations/cloudflare.d.ts +76 -0
  29. package/dist/integrations/hono.d.ts +60 -0
  30. package/dist/integrations/index.d.ts +24 -0
  31. package/dist/integrations/nuxt.d.ts +7 -0
  32. package/dist/integrations/nuxt.js +42 -0
  33. package/dist/integrations/runtime/use-ts-analytics.d.ts +18 -0
  34. package/dist/integrations/runtime/use-ts-analytics.js +17 -0
  35. package/dist/integrations/stx.d.ts +88 -0
  36. package/dist/integrations/stx.js +18 -0
  37. package/dist/lib/crypto-random.d.ts +4 -0
  38. package/dist/lib/salt.d.ts +16 -0
  39. package/dist/local.d.ts +56 -0
  40. package/dist/model-connector.d.ts +145 -0
  41. package/dist/models/AggregatedStats.d.ts +9 -0
  42. package/dist/models/CampaignStats.d.ts +9 -0
  43. package/dist/models/Conversion.d.ts +11 -0
  44. package/dist/models/CustomEvent.d.ts +11 -0
  45. package/dist/models/DeviceStats.d.ts +9 -0
  46. package/dist/models/EventStats.d.ts +9 -0
  47. package/dist/models/GeoStats.d.ts +9 -0
  48. package/dist/models/Goal.d.ts +9 -0
  49. package/dist/models/GoalStats.d.ts +9 -0
  50. package/dist/models/PageStats.d.ts +11 -0
  51. package/dist/models/PageView.d.ts +13 -0
  52. package/dist/models/RealtimeStats.d.ts +9 -0
  53. package/dist/models/ReferrerStats.d.ts +9 -0
  54. package/dist/models/Session.d.ts +11 -0
  55. package/dist/models/Site.d.ts +11 -0
  56. package/dist/models/index.d.ts +28 -0
  57. package/dist/models/types.d.ts +60 -0
  58. package/dist/sqs-buffering.d.ts +243 -0
  59. package/dist/stacks-integration.d.ts +159 -0
  60. package/dist/tracking-script.d.ts +71 -0
  61. package/dist/tracking.d.ts +17 -0
  62. package/dist/tracking.js +26 -0
  63. package/dist/types.d.ts +595 -0
  64. package/dist/utils/geolocation.d.ts +111 -0
  65. package/dist/utils/user-agent.d.ts +17 -0
  66. package/dist/version.d.ts +7 -0
  67. package/package.json +119 -0
  68. package/src/Analytics.ts +3349 -0
  69. package/src/api.ts +1286 -0
  70. package/src/assets/crosswind.css +2220 -0
  71. package/src/batching.ts +452 -0
  72. package/src/components/dashboard/index.ts +18 -0
  73. package/src/config.ts +456 -0
  74. package/src/dashboard/Dashboard.stx +1517 -0
  75. package/src/dashboard/components/AlertCard.stx +177 -0
  76. package/src/dashboard/components/AnalyticsDashboard.stx +354 -0
  77. package/src/dashboard/components/AnimatedNumber.stx +86 -0
  78. package/src/dashboard/components/BarChart.stx +220 -0
  79. package/src/dashboard/components/BrowserBreakdown.stx +98 -0
  80. package/src/dashboard/components/BrowsersTable.stx +125 -0
  81. package/src/dashboard/components/CampaignBreakdown.stx +163 -0
  82. package/src/dashboard/components/CampaignTable.stx +238 -0
  83. package/src/dashboard/components/CountryList.stx +101 -0
  84. package/src/dashboard/components/DataTable.stx +226 -0
  85. package/src/dashboard/components/DateRangePicker.stx +77 -0
  86. package/src/dashboard/components/DeviceBreakdown.stx +94 -0
  87. package/src/dashboard/components/DevicesTable.stx +163 -0
  88. package/src/dashboard/components/DonutChart.stories.ts +55 -0
  89. package/src/dashboard/components/DonutChart.stx +157 -0
  90. package/src/dashboard/components/EmptyState.stx +90 -0
  91. package/src/dashboard/components/EngagementMetrics.stx +183 -0
  92. package/src/dashboard/components/EventsSection.stx +192 -0
  93. package/src/dashboard/components/FilterBar.stx +104 -0
  94. package/src/dashboard/components/FullAnalyticsDashboard.stx +455 -0
  95. package/src/dashboard/components/FunnelChart.stx +142 -0
  96. package/src/dashboard/components/GeoTable.stx +337 -0
  97. package/src/dashboard/components/GoalsPanel.stx +109 -0
  98. package/src/dashboard/components/Header.stx +39 -0
  99. package/src/dashboard/components/HeatmapChart.stx +140 -0
  100. package/src/dashboard/components/LiveActivityFeed.stx +172 -0
  101. package/src/dashboard/components/MetricComparison.stx +106 -0
  102. package/src/dashboard/components/MiniStats.stx +96 -0
  103. package/src/dashboard/components/OSBreakdown.stx +124 -0
  104. package/src/dashboard/components/PageDetailCard.stx +102 -0
  105. package/src/dashboard/components/PagesTable.stx +127 -0
  106. package/src/dashboard/components/ProgressRing.stx +89 -0
  107. package/src/dashboard/components/RealtimeCounter.stories.ts +45 -0
  108. package/src/dashboard/components/RealtimeCounter.stx +46 -0
  109. package/src/dashboard/components/ReferrersTable.stx +180 -0
  110. package/src/dashboard/components/SparklineChart.stx +160 -0
  111. package/src/dashboard/components/StatCard.stories.ts +58 -0
  112. package/src/dashboard/components/StatCard.stx +90 -0
  113. package/src/dashboard/components/SummaryStats.stx +81 -0
  114. package/src/dashboard/components/TabNav.stx +66 -0
  115. package/src/dashboard/components/ThemeSwitcher.stx +124 -0
  116. package/src/dashboard/components/TimeSeriesChart.stx +106 -0
  117. package/src/dashboard/components/TopList.stories.ts +58 -0
  118. package/src/dashboard/components/TopList.stx +74 -0
  119. package/src/dashboard/components/TrendIndicator.stx +84 -0
  120. package/src/dashboard/components/heatmap/ClickHeatmap.stx +264 -0
  121. package/src/dashboard/components/heatmap/ElementClickList.stx +125 -0
  122. package/src/dashboard/components/heatmap/HeatmapControls.stx +125 -0
  123. package/src/dashboard/components/heatmap/HeatmapLegend.stx +69 -0
  124. package/src/dashboard/components/heatmap/PageHeatmap.stx +264 -0
  125. package/src/dashboard/components/heatmap/ScrollHeatmap.stx +127 -0
  126. package/src/dashboard/components/heatmap/index.ts +12 -0
  127. package/src/dashboard/components/index.ts +86 -0
  128. package/src/dashboard/composables/useAnalytics.ts +465 -0
  129. package/src/dashboard/demo/DemoApp.stx +370 -0
  130. package/src/dashboard/demo/index.ts +8 -0
  131. package/src/dashboard/demo/mockData.ts +234 -0
  132. package/src/dashboard/index.ts +117 -0
  133. package/src/dashboard/stx-shim.d.ts +4 -0
  134. package/src/dashboard/types/index.ts +203 -0
  135. package/src/dashboard/utils/index.ts +426 -0
  136. package/src/dynamodb.ts +344 -0
  137. package/src/funnels.ts +534 -0
  138. package/src/geolocation.ts +515 -0
  139. package/src/handlers/alerts.ts +328 -0
  140. package/src/handlers/annotations.ts +128 -0
  141. package/src/handlers/api-keys.ts +240 -0
  142. package/src/handlers/auth.ts +1020 -0
  143. package/src/handlers/authz.ts +137 -0
  144. package/src/handlers/collect.ts +724 -0
  145. package/src/handlers/data.ts +625 -0
  146. package/src/handlers/experiments.ts +138 -0
  147. package/src/handlers/funnels.ts +216 -0
  148. package/src/handlers/goals.ts +218 -0
  149. package/src/handlers/heatmaps.ts +272 -0
  150. package/src/handlers/index.ts +56 -0
  151. package/src/handlers/lib/read-cache.ts +63 -0
  152. package/src/handlers/misc.ts +486 -0
  153. package/src/handlers/oauth.ts +233 -0
  154. package/src/handlers/performance.ts +388 -0
  155. package/src/handlers/sessions.ts +413 -0
  156. package/src/handlers/sharing.ts +198 -0
  157. package/src/handlers/stats.ts +1368 -0
  158. package/src/handlers/team.ts +161 -0
  159. package/src/handlers/uptime.ts +283 -0
  160. package/src/handlers/views.ts +390 -0
  161. package/src/handlers/webhooks.ts +226 -0
  162. package/src/heatmap/index.ts +32 -0
  163. package/src/heatmap/tracking-script.ts +452 -0
  164. package/src/heatmap/types.ts +79 -0
  165. package/src/index.ts +387 -0
  166. package/src/infrastructure/cdk.ts +496 -0
  167. package/src/infrastructure/cloudformation.ts +595 -0
  168. package/src/infrastructure/index.ts +48 -0
  169. package/src/infrastructure/setup.ts +611 -0
  170. package/src/integrations/cloudflare.ts +732 -0
  171. package/src/integrations/hono.ts +589 -0
  172. package/src/integrations/index.ts +27 -0
  173. package/src/integrations/nuxt.ts +78 -0
  174. package/src/integrations/runtime/use-ts-analytics.ts +32 -0
  175. package/src/integrations/stx.ts +138 -0
  176. package/src/jobs/index.ts +127 -0
  177. package/src/lib/crypto-random.ts +41 -0
  178. package/src/lib/ddb-errors.ts +20 -0
  179. package/src/lib/dynamodb.ts +216 -0
  180. package/src/lib/email.ts +38 -0
  181. package/src/lib/ga-import.ts +471 -0
  182. package/src/lib/ga4-api.ts +244 -0
  183. package/src/lib/goals.ts +205 -0
  184. package/src/lib/index.ts +6 -0
  185. package/src/lib/ingest-counters.ts +123 -0
  186. package/src/lib/log.ts +36 -0
  187. package/src/lib/membership.ts +98 -0
  188. package/src/lib/plans.ts +90 -0
  189. package/src/lib/quota.ts +46 -0
  190. package/src/lib/rate-limit.ts +27 -0
  191. package/src/lib/rollups.ts +472 -0
  192. package/src/lib/salt.ts +97 -0
  193. package/src/lib/scheduler.ts +97 -0
  194. package/src/lib/significance.ts +66 -0
  195. package/src/lib/site-retention.ts +56 -0
  196. package/src/local.ts +360 -0
  197. package/src/model-connector.ts +616 -0
  198. package/src/models/AggregatedStats.ts +162 -0
  199. package/src/models/CampaignStats.ts +141 -0
  200. package/src/models/Conversion.ts +135 -0
  201. package/src/models/CustomEvent.ts +123 -0
  202. package/src/models/DeviceStats.ts +105 -0
  203. package/src/models/EventStats.ts +108 -0
  204. package/src/models/GeoStats.ts +112 -0
  205. package/src/models/Goal.ts +105 -0
  206. package/src/models/GoalStats.ts +106 -0
  207. package/src/models/PageStats.ts +152 -0
  208. package/src/models/PageView.ts +277 -0
  209. package/src/models/RealtimeStats.ts +96 -0
  210. package/src/models/ReferrerStats.ts +115 -0
  211. package/src/models/Session.ts +235 -0
  212. package/src/models/Site.ts +115 -0
  213. package/src/models/index.ts +50 -0
  214. package/src/models/orm/index.ts +2043 -0
  215. package/src/models/types.ts +71 -0
  216. package/src/router.ts +521 -0
  217. package/src/sqs-buffering.ts +806 -0
  218. package/src/stacks-integration.ts +527 -0
  219. package/src/tracking-script.ts +944 -0
  220. package/src/tracking.ts +27 -0
  221. package/src/types/analytics.ts +219 -0
  222. package/src/types/api.ts +75 -0
  223. package/src/types/bun-router.d.ts +15 -0
  224. package/src/types/dashboard.ts +62 -0
  225. package/src/types/index.ts +66 -0
  226. package/src/types/stx.d.ts +27 -0
  227. package/src/types/window.d.ts +101 -0
  228. package/src/types.ts +911 -0
  229. package/src/utils/cache.ts +148 -0
  230. package/src/utils/date.ts +118 -0
  231. package/src/utils/filters.ts +71 -0
  232. package/src/utils/geolocation.ts +323 -0
  233. package/src/utils/index.ts +9 -0
  234. package/src/utils/response.ts +180 -0
  235. package/src/utils/timezone-country.ts +242 -0
  236. package/src/utils/user-agent.ts +110 -0
  237. package/src/version.ts +7 -0
@@ -0,0 +1,108 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Custom event stats
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: EVENTSTATS#{PERIOD}#{periodStart}#{eventName}
9
+ */
10
+ const EventStatsModel: Model = {
11
+ name: 'EventStats',
12
+ table: 'analytics', // Single-table design
13
+ primaryKey: ['siteId', 'period', 'periodStart', 'eventName'], // Composite key
14
+ autoIncrement: false,
15
+
16
+ traits: {
17
+ useTtl: true,
18
+ },
19
+
20
+ belongsTo: ['Site'],
21
+
22
+ attributes: {
23
+ siteId: {
24
+ required: true,
25
+ fillable: true,
26
+ validation: {
27
+ rule: 'string',
28
+ },
29
+ },
30
+
31
+ period: {
32
+ required: true,
33
+ fillable: true,
34
+ validation: {
35
+ rule: 'enum:hour,day,month',
36
+ },
37
+ },
38
+
39
+ periodStart: {
40
+ required: true,
41
+ fillable: true,
42
+ validation: {
43
+ rule: 'string',
44
+ },
45
+ },
46
+
47
+ eventName: {
48
+ required: true,
49
+ fillable: true,
50
+ validation: {
51
+ rule: 'string',
52
+ },
53
+ },
54
+
55
+ count: {
56
+ required: true,
57
+ fillable: true,
58
+ default: 0,
59
+ validation: {
60
+ rule: 'number',
61
+ },
62
+ cast: 'number',
63
+ comment: 'Total occurrences',
64
+ },
65
+
66
+ uniqueVisitors: {
67
+ required: true,
68
+ fillable: true,
69
+ default: 0,
70
+ validation: {
71
+ rule: 'number',
72
+ },
73
+ cast: 'number',
74
+ comment: 'Unique visitors who triggered event',
75
+ },
76
+
77
+ totalValue: {
78
+ required: true,
79
+ fillable: true,
80
+ default: 0,
81
+ validation: {
82
+ rule: 'number',
83
+ },
84
+ cast: 'number',
85
+ comment: 'For revenue tracking',
86
+ },
87
+
88
+ avgValue: {
89
+ required: true,
90
+ fillable: true,
91
+ default: 0,
92
+ validation: {
93
+ rule: 'number',
94
+ },
95
+ cast: 'number',
96
+ comment: 'Average value per event',
97
+ },
98
+ },
99
+
100
+ // DynamoDB single-table key patterns
101
+ dynamodb: {
102
+ pk: (stats: { siteId: string }): string => `SITE#${stats.siteId}`,
103
+ sk: (stats: { period: string, periodStart: string, eventName: string }): string =>
104
+ `EVENTSTATS#${stats.period.toUpperCase()}#${stats.periodStart}#${stats.eventName}`,
105
+ },
106
+ }
107
+
108
+ export default EventStatsModel
@@ -0,0 +1,112 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Geographic stats
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: GEOSTATS#{PERIOD}#{periodStart}#{country}[#{region}]
9
+ */
10
+ const GeoStatsModel: Model = {
11
+ name: 'GeoStats',
12
+ table: 'analytics', // Single-table design
13
+ primaryKey: ['siteId', 'period', 'periodStart', 'country'], // Composite key
14
+ autoIncrement: false,
15
+
16
+ traits: {
17
+ useTtl: true,
18
+ },
19
+
20
+ belongsTo: ['Site'],
21
+
22
+ attributes: {
23
+ siteId: {
24
+ required: true,
25
+ fillable: true,
26
+ validation: {
27
+ rule: 'string',
28
+ },
29
+ },
30
+
31
+ period: {
32
+ required: true,
33
+ fillable: true,
34
+ validation: {
35
+ rule: 'enum:hour,day,month',
36
+ },
37
+ },
38
+
39
+ periodStart: {
40
+ required: true,
41
+ fillable: true,
42
+ validation: {
43
+ rule: 'string',
44
+ },
45
+ },
46
+
47
+ country: {
48
+ required: true,
49
+ fillable: true,
50
+ validation: {
51
+ rule: 'string',
52
+ },
53
+ comment: 'ISO 3166-1 alpha-2 country code',
54
+ },
55
+
56
+ region: {
57
+ fillable: true,
58
+ validation: {
59
+ rule: 'string',
60
+ },
61
+ comment: 'For country drilldown',
62
+ },
63
+
64
+ city: {
65
+ fillable: true,
66
+ validation: {
67
+ rule: 'string',
68
+ },
69
+ comment: 'For region drilldown',
70
+ },
71
+
72
+ visitors: {
73
+ required: true,
74
+ fillable: true,
75
+ default: 0,
76
+ validation: {
77
+ rule: 'number',
78
+ },
79
+ cast: 'number',
80
+ },
81
+
82
+ pageViews: {
83
+ required: true,
84
+ fillable: true,
85
+ default: 0,
86
+ validation: {
87
+ rule: 'number',
88
+ },
89
+ cast: 'number',
90
+ },
91
+
92
+ bounceRate: {
93
+ required: true,
94
+ fillable: true,
95
+ default: 0,
96
+ validation: {
97
+ rule: 'number',
98
+ },
99
+ cast: 'number',
100
+ comment: '0-1 decimal',
101
+ },
102
+ },
103
+
104
+ // DynamoDB single-table key patterns
105
+ dynamodb: {
106
+ pk: (stats: { siteId: string }): string => `SITE#${stats.siteId}`,
107
+ sk: (stats: { period: string, periodStart: string, country: string, region?: string }): string =>
108
+ `GEOSTATS#${stats.period.toUpperCase()}#${stats.periodStart}#${stats.country}${stats.region ? `#${stats.region}` : ''}`,
109
+ },
110
+ }
111
+
112
+ export default GeoStatsModel
@@ -0,0 +1,105 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Goal/Conversion definition
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: GOAL#{goalId}
9
+ */
10
+ const GoalModel: Model = {
11
+ name: 'Goal',
12
+ table: 'analytics', // Single-table design
13
+ primaryKey: 'id',
14
+ autoIncrement: false,
15
+
16
+ traits: {
17
+ useUuid: true,
18
+ useTimestamps: true,
19
+ },
20
+
21
+ belongsTo: ['Site'],
22
+
23
+ hasMany: ['Conversion'],
24
+
25
+ attributes: {
26
+ id: {
27
+ required: true,
28
+ unique: true,
29
+ validation: {
30
+ rule: 'string',
31
+ },
32
+ },
33
+
34
+ siteId: {
35
+ required: true,
36
+ fillable: true,
37
+ validation: {
38
+ rule: 'string',
39
+ },
40
+ },
41
+
42
+ name: {
43
+ required: true,
44
+ fillable: true,
45
+ validation: {
46
+ rule: 'string',
47
+ },
48
+ comment: 'Human-readable goal name',
49
+ },
50
+
51
+ type: {
52
+ required: true,
53
+ fillable: true,
54
+ validation: {
55
+ rule: 'enum:pageview,event',
56
+ },
57
+ comment: 'Goal type - pageview or event based',
58
+ },
59
+
60
+ pattern: {
61
+ required: true,
62
+ fillable: true,
63
+ validation: {
64
+ rule: 'string',
65
+ },
66
+ comment: 'Match pattern (path pattern or event name)',
67
+ },
68
+
69
+ matchType: {
70
+ required: true,
71
+ fillable: true,
72
+ default: 'exact',
73
+ validation: {
74
+ rule: 'enum:exact,contains,regex',
75
+ },
76
+ },
77
+
78
+ value: {
79
+ fillable: true,
80
+ validation: {
81
+ rule: 'number',
82
+ },
83
+ cast: 'number',
84
+ comment: 'Revenue value per conversion',
85
+ },
86
+
87
+ isActive: {
88
+ required: true,
89
+ fillable: true,
90
+ default: true,
91
+ validation: {
92
+ rule: 'boolean',
93
+ },
94
+ cast: 'boolean',
95
+ },
96
+ },
97
+
98
+ // DynamoDB single-table key patterns
99
+ dynamodb: {
100
+ pk: (goal: { siteId: string }): string => `SITE#${goal.siteId}`,
101
+ sk: (goal: { id: string }): string => `GOAL#${goal.id}`,
102
+ },
103
+ }
104
+
105
+ export default GoalModel
@@ -0,0 +1,106 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Goal stats (conversions)
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: GOALSTATS#{goalId}#{PERIOD}#{periodStart}
9
+ */
10
+ const GoalStatsModel: Model = {
11
+ name: 'GoalStats',
12
+ table: 'analytics', // Single-table design
13
+ primaryKey: ['siteId', 'goalId', 'period', 'periodStart'], // Composite key
14
+ autoIncrement: false,
15
+
16
+ traits: {
17
+ useTtl: true,
18
+ },
19
+
20
+ belongsTo: ['Site', 'Goal'],
21
+
22
+ attributes: {
23
+ siteId: {
24
+ required: true,
25
+ fillable: true,
26
+ validation: {
27
+ rule: 'string',
28
+ },
29
+ },
30
+
31
+ goalId: {
32
+ required: true,
33
+ fillable: true,
34
+ validation: {
35
+ rule: 'string',
36
+ },
37
+ },
38
+
39
+ period: {
40
+ required: true,
41
+ fillable: true,
42
+ validation: {
43
+ rule: 'enum:hour,day,month',
44
+ },
45
+ },
46
+
47
+ periodStart: {
48
+ required: true,
49
+ fillable: true,
50
+ validation: {
51
+ rule: 'string',
52
+ },
53
+ },
54
+
55
+ conversions: {
56
+ required: true,
57
+ fillable: true,
58
+ default: 0,
59
+ validation: {
60
+ rule: 'number',
61
+ },
62
+ cast: 'number',
63
+ },
64
+
65
+ uniqueConversions: {
66
+ required: true,
67
+ fillable: true,
68
+ default: 0,
69
+ validation: {
70
+ rule: 'number',
71
+ },
72
+ cast: 'number',
73
+ comment: 'Unique visitors who converted',
74
+ },
75
+
76
+ conversionRate: {
77
+ required: true,
78
+ fillable: true,
79
+ default: 0,
80
+ validation: {
81
+ rule: 'number',
82
+ },
83
+ cast: 'number',
84
+ comment: '0-1 decimal',
85
+ },
86
+
87
+ revenue: {
88
+ required: true,
89
+ fillable: true,
90
+ default: 0,
91
+ validation: {
92
+ rule: 'number',
93
+ },
94
+ cast: 'number',
95
+ },
96
+ },
97
+
98
+ // DynamoDB single-table key patterns
99
+ dynamodb: {
100
+ pk: (stats: { siteId: string }): string => `SITE#${stats.siteId}`,
101
+ sk: (stats: { goalId: string, period: string, periodStart: string }): string =>
102
+ `GOALSTATS#${stats.goalId}#${stats.period.toUpperCase()}#${stats.periodStart}`,
103
+ },
104
+ }
105
+
106
+ export default GoalStatsModel
@@ -0,0 +1,152 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Page-level aggregated stats
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: PAGESTATS#{PERIOD}#{periodStart}#{path}
9
+ * - GSI1PK: SITE#{siteId}#PAGESTATS#{PERIOD}#{periodStart}
10
+ * - GSI1SK: PV#{pageViews}#{path} (for sorting by page views)
11
+ */
12
+ const PageStatsModel: Model = {
13
+ name: 'PageStats',
14
+ table: 'analytics', // Single-table design
15
+ primaryKey: ['siteId', 'period', 'periodStart', 'path'], // Composite key
16
+ autoIncrement: false,
17
+
18
+ traits: {
19
+ useTtl: true,
20
+ },
21
+
22
+ belongsTo: ['Site'],
23
+
24
+ attributes: {
25
+ siteId: {
26
+ required: true,
27
+ fillable: true,
28
+ validation: {
29
+ rule: 'string',
30
+ },
31
+ },
32
+
33
+ period: {
34
+ required: true,
35
+ fillable: true,
36
+ validation: {
37
+ rule: 'enum:hour,day,month',
38
+ },
39
+ },
40
+
41
+ periodStart: {
42
+ required: true,
43
+ fillable: true,
44
+ validation: {
45
+ rule: 'string',
46
+ },
47
+ },
48
+
49
+ path: {
50
+ required: true,
51
+ fillable: true,
52
+ validation: {
53
+ rule: 'string',
54
+ },
55
+ },
56
+
57
+ title: {
58
+ fillable: true,
59
+ validation: {
60
+ rule: 'string',
61
+ },
62
+ comment: 'Most recent page title',
63
+ },
64
+
65
+ pageViews: {
66
+ required: true,
67
+ fillable: true,
68
+ default: 0,
69
+ validation: {
70
+ rule: 'number',
71
+ },
72
+ cast: 'number',
73
+ },
74
+
75
+ uniqueVisitors: {
76
+ required: true,
77
+ fillable: true,
78
+ default: 0,
79
+ validation: {
80
+ rule: 'number',
81
+ },
82
+ cast: 'number',
83
+ },
84
+
85
+ entries: {
86
+ required: true,
87
+ fillable: true,
88
+ default: 0,
89
+ validation: {
90
+ rule: 'number',
91
+ },
92
+ cast: 'number',
93
+ comment: 'Sessions starting on this page',
94
+ },
95
+
96
+ exits: {
97
+ required: true,
98
+ fillable: true,
99
+ default: 0,
100
+ validation: {
101
+ rule: 'number',
102
+ },
103
+ cast: 'number',
104
+ comment: 'Sessions ending on this page',
105
+ },
106
+
107
+ bounces: {
108
+ required: true,
109
+ fillable: true,
110
+ default: 0,
111
+ validation: {
112
+ rule: 'number',
113
+ },
114
+ cast: 'number',
115
+ },
116
+
117
+ avgTimeOnPage: {
118
+ required: true,
119
+ fillable: true,
120
+ default: 0,
121
+ validation: {
122
+ rule: 'number',
123
+ },
124
+ cast: 'number',
125
+ comment: 'Milliseconds',
126
+ },
127
+
128
+ exitRate: {
129
+ required: true,
130
+ fillable: true,
131
+ default: 0,
132
+ validation: {
133
+ rule: 'number',
134
+ },
135
+ cast: 'number',
136
+ comment: '0-1 decimal',
137
+ },
138
+ },
139
+
140
+ // DynamoDB single-table key patterns
141
+ dynamodb: {
142
+ pk: (stats: { siteId: string }): string => `SITE#${stats.siteId}`,
143
+ sk: (stats: { period: string, periodStart: string, path: string }): string =>
144
+ `PAGESTATS#${stats.period.toUpperCase()}#${stats.periodStart}#${encodeURIComponent(stats.path)}`,
145
+ gsi1pk: (stats: { siteId: string, period: string, periodStart: string }): string =>
146
+ `SITE#${stats.siteId}#PAGESTATS#${stats.period.toUpperCase()}#${stats.periodStart}`,
147
+ gsi1sk: (stats: { pageViews: number, path: string }): string =>
148
+ `PV#${String(stats.pageViews).padStart(10, '0')}#${encodeURIComponent(stats.path)}`,
149
+ },
150
+ }
151
+
152
+ export default PageStatsModel