@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,277 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Page view event (raw event - stored temporarily for aggregation)
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: PV#{timestamp}#{pageViewId}
9
+ * - GSI1PK: SITE#{siteId}#DATE#{date}
10
+ * - GSI1SK: PATH#{path}#{pageViewId}
11
+ * - GSI2PK: SITE#{siteId}#VISITOR#{visitorId}
12
+ * - GSI2SK: PV#{timestamp}
13
+ */
14
+ const PageViewModel: Model = {
15
+ name: 'PageView',
16
+ table: 'analytics', // Single-table design
17
+ primaryKey: 'id',
18
+ autoIncrement: false,
19
+
20
+ traits: {
21
+ useUuid: true,
22
+ useTtl: true, // Auto-delete after aggregation
23
+ },
24
+
25
+ belongsTo: ['Site', 'Session'],
26
+
27
+ attributes: {
28
+ id: {
29
+ required: true,
30
+ unique: true,
31
+ validation: {
32
+ rule: 'string',
33
+ },
34
+ },
35
+
36
+ siteId: {
37
+ required: true,
38
+ fillable: true,
39
+ validation: {
40
+ rule: 'string',
41
+ },
42
+ },
43
+
44
+ visitorId: {
45
+ required: true,
46
+ fillable: true,
47
+ validation: {
48
+ rule: 'string',
49
+ },
50
+ comment: 'Hashed, anonymous visitor identifier',
51
+ },
52
+
53
+ sessionId: {
54
+ required: true,
55
+ fillable: true,
56
+ validation: {
57
+ rule: 'string',
58
+ },
59
+ },
60
+
61
+ path: {
62
+ required: true,
63
+ fillable: true,
64
+ validation: {
65
+ rule: 'string',
66
+ },
67
+ },
68
+
69
+ hostname: {
70
+ required: true,
71
+ fillable: true,
72
+ validation: {
73
+ rule: 'string',
74
+ },
75
+ },
76
+
77
+ title: {
78
+ fillable: true,
79
+ validation: {
80
+ rule: 'string',
81
+ },
82
+ },
83
+
84
+ referrer: {
85
+ fillable: true,
86
+ validation: {
87
+ rule: 'string',
88
+ },
89
+ },
90
+
91
+ referrerSource: {
92
+ fillable: true,
93
+ validation: {
94
+ rule: 'string',
95
+ },
96
+ comment: 'e.g., google, twitter, direct',
97
+ },
98
+
99
+ // UTM parameters
100
+ utmSource: {
101
+ fillable: true,
102
+ validation: {
103
+ rule: 'string',
104
+ },
105
+ },
106
+
107
+ utmMedium: {
108
+ fillable: true,
109
+ validation: {
110
+ rule: 'string',
111
+ },
112
+ },
113
+
114
+ utmCampaign: {
115
+ fillable: true,
116
+ validation: {
117
+ rule: 'string',
118
+ },
119
+ },
120
+
121
+ utmContent: {
122
+ fillable: true,
123
+ validation: {
124
+ rule: 'string',
125
+ },
126
+ },
127
+
128
+ utmTerm: {
129
+ fillable: true,
130
+ validation: {
131
+ rule: 'string',
132
+ },
133
+ },
134
+
135
+ // Geolocation (privacy-sensitive)
136
+ country: {
137
+ fillable: true,
138
+ validation: {
139
+ rule: 'string',
140
+ },
141
+ comment: 'ISO 3166-1 alpha-2 country code',
142
+ },
143
+
144
+ region: {
145
+ fillable: true,
146
+ validation: {
147
+ rule: 'string',
148
+ },
149
+ },
150
+
151
+ city: {
152
+ fillable: true,
153
+ validation: {
154
+ rule: 'string',
155
+ },
156
+ },
157
+
158
+ // Device information
159
+ deviceType: {
160
+ fillable: true,
161
+ validation: {
162
+ rule: 'enum:desktop,mobile,tablet,unknown',
163
+ },
164
+ },
165
+
166
+ browser: {
167
+ fillable: true,
168
+ validation: {
169
+ rule: 'string',
170
+ },
171
+ },
172
+
173
+ browserVersion: {
174
+ fillable: true,
175
+ validation: {
176
+ rule: 'string',
177
+ },
178
+ },
179
+
180
+ os: {
181
+ fillable: true,
182
+ validation: {
183
+ rule: 'string',
184
+ },
185
+ },
186
+
187
+ osVersion: {
188
+ fillable: true,
189
+ validation: {
190
+ rule: 'string',
191
+ },
192
+ },
193
+
194
+ screenWidth: {
195
+ fillable: true,
196
+ validation: {
197
+ rule: 'number',
198
+ },
199
+ cast: 'number',
200
+ },
201
+
202
+ screenHeight: {
203
+ fillable: true,
204
+ validation: {
205
+ rule: 'number',
206
+ },
207
+ cast: 'number',
208
+ },
209
+
210
+ // Session metrics
211
+ isUnique: {
212
+ required: true,
213
+ fillable: true,
214
+ default: false,
215
+ validation: {
216
+ rule: 'boolean',
217
+ },
218
+ cast: 'boolean',
219
+ comment: 'First page view in session',
220
+ },
221
+
222
+ isBounce: {
223
+ required: true,
224
+ fillable: true,
225
+ default: true,
226
+ validation: {
227
+ rule: 'boolean',
228
+ },
229
+ cast: 'boolean',
230
+ comment: 'Only page view in session',
231
+ },
232
+
233
+ timeOnPage: {
234
+ fillable: true,
235
+ validation: {
236
+ rule: 'number',
237
+ },
238
+ cast: 'number',
239
+ comment: 'Milliseconds - updated on next page view or session end',
240
+ },
241
+
242
+ timestamp: {
243
+ required: true,
244
+ fillable: true,
245
+ validation: {
246
+ rule: 'date',
247
+ },
248
+ cast: 'datetime',
249
+ },
250
+
251
+ ttl: {
252
+ fillable: true,
253
+ validation: {
254
+ rule: 'number',
255
+ },
256
+ cast: 'number',
257
+ comment: 'Unix timestamp for DynamoDB TTL auto-deletion',
258
+ },
259
+ },
260
+
261
+ // DynamoDB single-table key patterns
262
+ dynamodb: {
263
+ pk: (pv: { siteId: string }): string => `SITE#${pv.siteId}`,
264
+ sk: (pv: { timestamp: Date, id: string }): string =>
265
+ `PV#${pv.timestamp.toISOString()}#${pv.id}`,
266
+ gsi1pk: (pv: { siteId: string, timestamp: Date }): string =>
267
+ `SITE#${pv.siteId}#DATE#${pv.timestamp.toISOString().split('T')[0]}`,
268
+ gsi1sk: (pv: { path: string, id: string }): string =>
269
+ `PATH#${pv.path}#${pv.id}`,
270
+ gsi2pk: (pv: { siteId: string, visitorId: string }): string =>
271
+ `SITE#${pv.siteId}#VISITOR#${pv.visitorId}`,
272
+ gsi2sk: (pv: { timestamp: Date }): string =>
273
+ `PV#${pv.timestamp.toISOString()}`,
274
+ },
275
+ }
276
+
277
+ export default PageViewModel
@@ -0,0 +1,96 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Real-time stats (last 5 minutes, stored with short TTL)
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: REALTIME#{minute}
9
+ */
10
+ const RealtimeStatsModel: Model = {
11
+ name: 'RealtimeStats',
12
+ table: 'analytics', // Single-table design
13
+ primaryKey: ['siteId', 'minute'], // 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
+ minute: {
32
+ required: true,
33
+ fillable: true,
34
+ validation: {
35
+ rule: 'string',
36
+ },
37
+ comment: 'ISO timestamp truncated to minute',
38
+ },
39
+
40
+ currentVisitors: {
41
+ required: true,
42
+ fillable: true,
43
+ default: 0,
44
+ validation: {
45
+ rule: 'number',
46
+ },
47
+ cast: 'number',
48
+ comment: 'Active visitors in this minute',
49
+ },
50
+
51
+ pageViews: {
52
+ required: true,
53
+ fillable: true,
54
+ default: 0,
55
+ validation: {
56
+ rule: 'number',
57
+ },
58
+ cast: 'number',
59
+ comment: 'Page views in this minute',
60
+ },
61
+
62
+ activePages: {
63
+ required: true,
64
+ fillable: true,
65
+ cast: 'json',
66
+ default: {},
67
+ comment: 'Map of path -> visitor count',
68
+ },
69
+
70
+ visitorIds: {
71
+ required: false,
72
+ fillable: true,
73
+ cast: 'json',
74
+ default: [],
75
+ comment: 'Set of unique visitor IDs for this minute (for counting)',
76
+ },
77
+
78
+ ttl: {
79
+ required: true,
80
+ fillable: true,
81
+ validation: {
82
+ rule: 'number',
83
+ },
84
+ cast: 'number',
85
+ comment: 'Auto-delete after 10 minutes',
86
+ },
87
+ },
88
+
89
+ // DynamoDB single-table key patterns
90
+ dynamodb: {
91
+ pk: (stats: { siteId: string }): string => `SITE#${stats.siteId}`,
92
+ sk: (stats: { minute: string }): string => `REALTIME#${stats.minute}`,
93
+ },
94
+ }
95
+
96
+ export default RealtimeStatsModel
@@ -0,0 +1,115 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Referrer stats
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: REFSTATS#{PERIOD}#{periodStart}#{source}
9
+ */
10
+ const ReferrerStatsModel: Model = {
11
+ name: 'ReferrerStats',
12
+ table: 'analytics', // Single-table design
13
+ primaryKey: ['siteId', 'period', 'periodStart', 'source'], // 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
+ source: {
48
+ required: true,
49
+ fillable: true,
50
+ validation: {
51
+ rule: 'string',
52
+ },
53
+ comment: 'Referrer source (e.g., google, twitter, direct)',
54
+ },
55
+
56
+ referrer: {
57
+ fillable: true,
58
+ validation: {
59
+ rule: 'string',
60
+ },
61
+ comment: 'Full referrer URL (for non-grouped view)',
62
+ },
63
+
64
+ visitors: {
65
+ required: true,
66
+ fillable: true,
67
+ default: 0,
68
+ validation: {
69
+ rule: 'number',
70
+ },
71
+ cast: 'number',
72
+ },
73
+
74
+ pageViews: {
75
+ required: true,
76
+ fillable: true,
77
+ default: 0,
78
+ validation: {
79
+ rule: 'number',
80
+ },
81
+ cast: 'number',
82
+ },
83
+
84
+ bounceRate: {
85
+ required: true,
86
+ fillable: true,
87
+ default: 0,
88
+ validation: {
89
+ rule: 'number',
90
+ },
91
+ cast: 'number',
92
+ comment: '0-1 decimal',
93
+ },
94
+
95
+ avgSessionDuration: {
96
+ required: true,
97
+ fillable: true,
98
+ default: 0,
99
+ validation: {
100
+ rule: 'number',
101
+ },
102
+ cast: 'number',
103
+ comment: 'Milliseconds',
104
+ },
105
+ },
106
+
107
+ // DynamoDB single-table key patterns
108
+ dynamodb: {
109
+ pk: (stats: { siteId: string }): string => `SITE#${stats.siteId}`,
110
+ sk: (stats: { period: string, periodStart: string, source: string }): string =>
111
+ `REFSTATS#${stats.period.toUpperCase()}#${stats.periodStart}#${stats.source}`,
112
+ },
113
+ }
114
+
115
+ export default ReferrerStatsModel
@@ -0,0 +1,235 @@
1
+ import type { Model } from './types'
2
+
3
+ /**
4
+ * Visitor session
5
+ *
6
+ * DynamoDB Keys:
7
+ * - PK: SITE#{siteId}
8
+ * - SK: SESSION#{sessionId}
9
+ * - GSI1PK: SITE#{siteId}#SESSIONS#{date}
10
+ * - GSI1SK: SESSION#{sessionId}
11
+ */
12
+ const SessionModel: Model = {
13
+ name: 'Session',
14
+ table: 'analytics', // Single-table design
15
+ primaryKey: 'id',
16
+ autoIncrement: false,
17
+
18
+ traits: {
19
+ useUuid: true,
20
+ useTtl: true,
21
+ },
22
+
23
+ belongsTo: ['Site'],
24
+
25
+ hasMany: ['PageView', 'CustomEvent'],
26
+
27
+ attributes: {
28
+ id: {
29
+ required: true,
30
+ unique: true,
31
+ validation: {
32
+ rule: 'string',
33
+ },
34
+ },
35
+
36
+ siteId: {
37
+ required: true,
38
+ fillable: true,
39
+ validation: {
40
+ rule: 'string',
41
+ },
42
+ },
43
+
44
+ visitorId: {
45
+ required: true,
46
+ fillable: true,
47
+ validation: {
48
+ rule: 'string',
49
+ },
50
+ comment: 'Hashed, anonymous visitor identifier',
51
+ },
52
+
53
+ entryPath: {
54
+ required: true,
55
+ fillable: true,
56
+ validation: {
57
+ rule: 'string',
58
+ },
59
+ comment: 'First page in session',
60
+ },
61
+
62
+ exitPath: {
63
+ required: true,
64
+ fillable: true,
65
+ validation: {
66
+ rule: 'string',
67
+ },
68
+ comment: 'Last page in session',
69
+ },
70
+
71
+ referrer: {
72
+ fillable: true,
73
+ validation: {
74
+ rule: 'string',
75
+ },
76
+ },
77
+
78
+ referrerSource: {
79
+ fillable: true,
80
+ validation: {
81
+ rule: 'string',
82
+ },
83
+ },
84
+
85
+ // UTM parameters
86
+ utmSource: {
87
+ fillable: true,
88
+ validation: {
89
+ rule: 'string',
90
+ },
91
+ },
92
+
93
+ utmMedium: {
94
+ fillable: true,
95
+ validation: {
96
+ rule: 'string',
97
+ },
98
+ },
99
+
100
+ utmCampaign: {
101
+ fillable: true,
102
+ validation: {
103
+ rule: 'string',
104
+ },
105
+ },
106
+
107
+ // Geolocation
108
+ country: {
109
+ fillable: true,
110
+ validation: {
111
+ rule: 'string',
112
+ },
113
+ },
114
+
115
+ region: {
116
+ fillable: true,
117
+ validation: {
118
+ rule: 'string',
119
+ },
120
+ comment: 'Region/state code or name',
121
+ },
122
+
123
+ city: {
124
+ fillable: true,
125
+ validation: {
126
+ rule: 'string',
127
+ },
128
+ comment: 'City name',
129
+ },
130
+
131
+ // Device information
132
+ deviceType: {
133
+ fillable: true,
134
+ validation: {
135
+ rule: 'enum:desktop,mobile,tablet,unknown',
136
+ },
137
+ },
138
+
139
+ browser: {
140
+ fillable: true,
141
+ validation: {
142
+ rule: 'string',
143
+ },
144
+ },
145
+
146
+ os: {
147
+ fillable: true,
148
+ validation: {
149
+ rule: 'string',
150
+ },
151
+ },
152
+
153
+ // Session metrics
154
+ pageViewCount: {
155
+ required: true,
156
+ fillable: true,
157
+ default: 0,
158
+ validation: {
159
+ rule: 'number',
160
+ },
161
+ cast: 'number',
162
+ },
163
+
164
+ eventCount: {
165
+ required: true,
166
+ fillable: true,
167
+ default: 0,
168
+ validation: {
169
+ rule: 'number',
170
+ },
171
+ cast: 'number',
172
+ },
173
+
174
+ isBounce: {
175
+ required: true,
176
+ fillable: true,
177
+ default: true,
178
+ validation: {
179
+ rule: 'boolean',
180
+ },
181
+ cast: 'boolean',
182
+ comment: 'Single page session',
183
+ },
184
+
185
+ duration: {
186
+ required: true,
187
+ fillable: true,
188
+ default: 0,
189
+ validation: {
190
+ rule: 'number',
191
+ },
192
+ cast: 'number',
193
+ comment: 'Session duration in milliseconds',
194
+ },
195
+
196
+ startedAt: {
197
+ required: true,
198
+ fillable: true,
199
+ validation: {
200
+ rule: 'date',
201
+ },
202
+ cast: 'datetime',
203
+ },
204
+
205
+ endedAt: {
206
+ required: true,
207
+ fillable: true,
208
+ validation: {
209
+ rule: 'date',
210
+ },
211
+ cast: 'datetime',
212
+ comment: 'Last activity timestamp',
213
+ },
214
+
215
+ ttl: {
216
+ fillable: true,
217
+ validation: {
218
+ rule: 'number',
219
+ },
220
+ cast: 'number',
221
+ comment: 'Unix timestamp for DynamoDB TTL auto-deletion',
222
+ },
223
+ },
224
+
225
+ // DynamoDB single-table key patterns
226
+ dynamodb: {
227
+ pk: (session: { siteId: string }): string => `SITE#${session.siteId}`,
228
+ sk: (session: { id: string }): string => `SESSION#${session.id}`,
229
+ gsi1pk: (session: { siteId: string, startedAt: Date }): string =>
230
+ `SITE#${session.siteId}#SESSIONS#${session.startedAt.toISOString().split('T')[0]}`,
231
+ gsi1sk: (session: { id: string }): string => `SESSION#${session.id}`,
232
+ },
233
+ }
234
+
235
+ export default SessionModel