@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,93 @@
1
+ import type { CustomEvent, PageView, Session } from './types';
2
+ /**
3
+ * Create a batch processor for processing events in bulk
4
+ */
5
+ export declare function createBatchProcessor<T>(processor: (batch: T[]) => Promise<void>, options?: { batchSize?: number, delayMs?: number }): {
6
+ add: (item: T) => void
7
+ flush: () => Promise<void>
8
+ size: () => number
9
+ };
10
+ /**
11
+ * Retry helper with exponential backoff
12
+ */
13
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: { maxRetries?: number, baseDelayMs?: number }): Promise<T>;
14
+ /**
15
+ * Chunk an array into smaller arrays
16
+ */
17
+ export declare function chunk<T>(array: T[], size: number): T[][];
18
+ /**
19
+ * Process items in parallel with concurrency limit
20
+ */
21
+ export declare function parallelProcess<T, R>(items: T[], processor: (item: T) => Promise<R>, concurrency?: number): Promise<R[]>;
22
+ // ============================================================================
23
+ // Types
24
+ // ============================================================================
25
+ export declare interface BatchItem {
26
+ type: 'pageview' | 'session' | 'event'
27
+ data: PageView | Session | CustomEvent
28
+ }
29
+ export declare interface BatchWriteResult {
30
+ successful: number
31
+ failed: number
32
+ unprocessedItems: BatchItem[]
33
+ }
34
+ export declare interface BatchQueueOptions {
35
+ maxBatchSize?: number
36
+ flushIntervalMs?: number
37
+ maxQueueSize?: number
38
+ onError?: (error: Error, items: BatchItem[]) => void
39
+ onFlush?: (result: BatchWriteResult) => void
40
+ }
41
+ export declare interface DynamoDBBatchClient {
42
+ batchWriteItem: (input: BatchWriteInput) => Promise<BatchWriteOutput>
43
+ }
44
+ declare interface BatchWriteInput {
45
+ RequestItems: Record<string, WriteRequest[]>
46
+ }
47
+ declare interface BatchWriteOutput {
48
+ UnprocessedItems?: Record<string, WriteRequest[]>
49
+ }
50
+ declare interface WriteRequest {
51
+ PutRequest?: {
52
+ Item: Record<string, AttributeValue>
53
+ }
54
+ }
55
+ declare interface AttributeValue {
56
+ S?: string
57
+ N?: string
58
+ BOOL?: boolean
59
+ L?: AttributeValue[]
60
+ M?: Record<string, AttributeValue>
61
+ }
62
+ /**
63
+ * High-performance event batch queue
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const queue = new EventBatchQueue(dynamoClient, {
68
+ * maxBatchSize: 25,
69
+ * flushIntervalMs: 5000,
70
+ * onFlush: (result) => console.log(`Flushed ${result.successful} items`),
71
+ * })
72
+ *
73
+ * // Add events (auto-batched)
74
+ * queue.addPageView(pageView)
75
+ * queue.addSession(session)
76
+ *
77
+ * // Manual flush if needed
78
+ * await queue.flush()
79
+ *
80
+ * // Shutdown gracefully
81
+ * await queue.close()
82
+ * ```
83
+ */
84
+ export declare class EventBatchQueue {
85
+ constructor(client: DynamoDBBatchClient, options?: BatchQueueOptions);
86
+ addPageView(pageView: PageView): void;
87
+ addSession(session: Session): void;
88
+ addEvent(event: CustomEvent): void;
89
+ add(item: BatchItem): void;
90
+ flush(): Promise<BatchWriteResult>;
91
+ close(): Promise<void>;
92
+ get size(): number;
93
+ }
@@ -0,0 +1,4 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ export { __require };
@@ -0,0 +1,204 @@
1
+ // @bun
2
+ // src/config.ts
3
+ var defaultSingleTableConfig = {
4
+ enabled: true,
5
+ partitionKeyName: "pk",
6
+ sortKeyName: "sk",
7
+ gsi1pkName: "gsi1pk",
8
+ gsi1skName: "gsi1sk",
9
+ gsi2pkName: "gsi2pk",
10
+ gsi2skName: "gsi2sk",
11
+ entityTypeAttribute: "_et",
12
+ pkPrefix: "{ENTITY}#",
13
+ skPrefix: "{ENTITY}#",
14
+ gsiCount: 2,
15
+ keyDelimiter: "#"
16
+ };
17
+ var defaultGSIs = [
18
+ {
19
+ name: "gsi1",
20
+ partitionKey: "gsi1pk",
21
+ sortKey: "gsi1sk",
22
+ projection: { type: "ALL" }
23
+ },
24
+ {
25
+ name: "gsi2",
26
+ partitionKey: "gsi2pk",
27
+ sortKey: "gsi2sk",
28
+ projection: { type: "ALL" }
29
+ }
30
+ ];
31
+ var defaultAnalyticsConfig = {
32
+ tableName: "AnalyticsTable",
33
+ billingMode: "PAY_PER_REQUEST",
34
+ singleTable: defaultSingleTableConfig,
35
+ globalSecondaryIndexes: defaultGSIs,
36
+ localSecondaryIndexes: [],
37
+ ttlAttributeName: "ttl"
38
+ };
39
+ var ONE_DAY = 24 * 60 * 60;
40
+ var THIRTY_DAYS = 30 * ONE_DAY;
41
+ var NINETY_DAYS = 90 * ONE_DAY;
42
+ var TWO_YEARS = 2 * 365 * ONE_DAY;
43
+ var defaultConfig = {
44
+ table: defaultAnalyticsConfig,
45
+ region: "us-east-1",
46
+ endpoint: undefined,
47
+ retention: {
48
+ rawEventTtl: THIRTY_DAYS,
49
+ hourlyAggregateTtl: NINETY_DAYS,
50
+ dailyAggregateTtl: TWO_YEARS,
51
+ monthlyAggregateTtl: undefined
52
+ },
53
+ privacy: {
54
+ hashVisitorIds: true,
55
+ collectGeolocation: false,
56
+ honorDnt: true,
57
+ ipAnonymization: "partial"
58
+ },
59
+ tracking: {
60
+ trackReferrers: true,
61
+ trackUtmParams: true,
62
+ trackDeviceType: true,
63
+ trackHashChanges: false,
64
+ trackOutboundLinks: true,
65
+ excludedIps: [],
66
+ excludedPaths: []
67
+ },
68
+ api: {
69
+ basePath: "/api/analytics",
70
+ corsOrigins: ["*"]
71
+ },
72
+ aggregation: {
73
+ batchSize: 100,
74
+ hourlyEnabled: true,
75
+ dailyEnabled: true,
76
+ monthlyEnabled: true
77
+ },
78
+ scale: {
79
+ sqsBuffering: {
80
+ enabled: false,
81
+ queueUrl: undefined,
82
+ deadLetterQueueUrl: undefined,
83
+ maxRetries: 3
84
+ },
85
+ daxCaching: {
86
+ enabled: false,
87
+ endpoint: undefined,
88
+ ttlSeconds: 300
89
+ },
90
+ partitionSharding: {
91
+ enabled: false,
92
+ shardCount: 10,
93
+ enabledSiteIds: []
94
+ },
95
+ writeCoalescing: {
96
+ enabled: true,
97
+ windowMs: 100
98
+ },
99
+ batchWrites: {
100
+ enabled: true,
101
+ maxBatchSize: 25,
102
+ flushIntervalMs: 1000
103
+ }
104
+ }
105
+ };
106
+ function defineConfig(userConfig = {}) {
107
+ return {
108
+ table: {
109
+ ...defaultConfig.table,
110
+ ...userConfig.table,
111
+ singleTable: {
112
+ ...defaultConfig.table.singleTable,
113
+ ...userConfig.table?.singleTable
114
+ },
115
+ globalSecondaryIndexes: userConfig.table?.globalSecondaryIndexes ?? defaultConfig.table.globalSecondaryIndexes,
116
+ localSecondaryIndexes: userConfig.table?.localSecondaryIndexes ?? defaultConfig.table.localSecondaryIndexes
117
+ },
118
+ region: userConfig.region ?? defaultConfig.region,
119
+ endpoint: userConfig.endpoint ?? defaultConfig.endpoint,
120
+ retention: {
121
+ ...defaultConfig.retention,
122
+ ...userConfig.retention
123
+ },
124
+ privacy: {
125
+ ...defaultConfig.privacy,
126
+ ...userConfig.privacy
127
+ },
128
+ tracking: {
129
+ ...defaultConfig.tracking,
130
+ ...userConfig.tracking
131
+ },
132
+ api: {
133
+ ...defaultConfig.api,
134
+ ...userConfig.api
135
+ },
136
+ aggregation: {
137
+ ...defaultConfig.aggregation,
138
+ ...userConfig.aggregation
139
+ },
140
+ scale: {
141
+ sqsBuffering: {
142
+ ...defaultConfig.scale.sqsBuffering,
143
+ ...userConfig.scale?.sqsBuffering
144
+ },
145
+ daxCaching: {
146
+ ...defaultConfig.scale.daxCaching,
147
+ ...userConfig.scale?.daxCaching
148
+ },
149
+ partitionSharding: {
150
+ ...defaultConfig.scale.partitionSharding,
151
+ ...userConfig.scale?.partitionSharding
152
+ },
153
+ writeCoalescing: {
154
+ ...defaultConfig.scale.writeCoalescing,
155
+ ...userConfig.scale?.writeCoalescing
156
+ },
157
+ batchWrites: {
158
+ ...defaultConfig.scale.batchWrites,
159
+ ...userConfig.scale?.batchWrites
160
+ }
161
+ }
162
+ };
163
+ }
164
+ function getTableConfig(config) {
165
+ return {
166
+ tableName: config.table.tableName,
167
+ billingMode: config.table.billingMode,
168
+ partitionKey: config.table.singleTable.partitionKeyName,
169
+ sortKey: config.table.singleTable.sortKeyName,
170
+ gsis: config.table.globalSecondaryIndexes,
171
+ ttlAttribute: config.table.ttlAttributeName
172
+ };
173
+ }
174
+ function getTtlForEntity(config, entityType) {
175
+ const now = Math.floor(Date.now() / 1000);
176
+ switch (entityType) {
177
+ case "raw":
178
+ return now + config.retention.rawEventTtl;
179
+ case "hourly":
180
+ return now + config.retention.hourlyAggregateTtl;
181
+ case "daily":
182
+ return now + config.retention.dailyAggregateTtl;
183
+ case "monthly":
184
+ return config.retention.monthlyAggregateTtl ? now + config.retention.monthlyAggregateTtl : undefined;
185
+ default:
186
+ return;
187
+ }
188
+ }
189
+ var _config = null;
190
+ function setConfig(config) {
191
+ _config = defineConfig(config);
192
+ return _config;
193
+ }
194
+ function getConfig() {
195
+ if (!_config) {
196
+ _config = defaultConfig;
197
+ }
198
+ return _config;
199
+ }
200
+ function resetConfig() {
201
+ _config = null;
202
+ }
203
+
204
+ export { defaultSingleTableConfig, defaultGSIs, defaultAnalyticsConfig, defaultConfig, defineConfig, getTableConfig, getTtlForEntity, setConfig, getConfig, resetConfig };
@@ -0,0 +1,56 @@
1
+ // @bun
2
+ // src/lib/crypto-random.ts
3
+ var DEFAULT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4
+ function randomToken(length, chars = DEFAULT_CHARS) {
5
+ if (length <= 0)
6
+ return "";
7
+ const limit = Math.floor(256 / chars.length) * chars.length;
8
+ let out = "";
9
+ const buf = new Uint8Array(length * 2);
10
+ while (out.length < length) {
11
+ crypto.getRandomValues(buf);
12
+ for (let i = 0;i < buf.length && out.length < length; i++) {
13
+ if (buf[i] < limit)
14
+ out += chars[buf[i] % chars.length];
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+ var APP_ID_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
20
+ function generateAppId(length = 8) {
21
+ return randomToken(length, APP_ID_CHARS);
22
+ }
23
+
24
+ // src/integrations/stx.ts
25
+ var DEFAULT_API_ENDPOINT = "http://localhost:2027";
26
+ function resolveApiEndpoint(explicit) {
27
+ const raw = (explicit ?? process.env.TS_ANALYTICS_ENDPOINT ?? DEFAULT_API_ENDPOINT).trim();
28
+ return raw.replace(/\/+$/, "");
29
+ }
30
+ function tsAnalytics(options) {
31
+ const appId = options?.appId?.trim();
32
+ if (!appId)
33
+ return [];
34
+ const origin = resolveApiEndpoint(options?.apiEndpoint);
35
+ if (!origin)
36
+ return [];
37
+ const rawPath = options.scriptPath ?? "/script.js";
38
+ const path = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
39
+ const src = `${origin}${path}${options.stealth ? "?stealth=true" : ""}`;
40
+ return [
41
+ {
42
+ src,
43
+ defer: true,
44
+ "data-site": appId
45
+ }
46
+ ];
47
+ }
48
+ function tsAnalyticsTag(options) {
49
+ const [entry] = tsAnalytics(options);
50
+ if (!entry)
51
+ return "";
52
+ return `<script defer data-site="${String(entry["data-site"])}" src="${String(entry.src)}"></script>`;
53
+ }
54
+ var stx_default = tsAnalytics;
55
+
56
+ export { randomToken, generateAppId, DEFAULT_API_ENDPOINT, resolveApiEndpoint, tsAnalytics, tsAnalyticsTag, stx_default };
@@ -0,0 +1,305 @@
1
+ // @bun
2
+ // src/utils/user-agent.ts
3
+ function parseUserAgent(ua) {
4
+ if (!ua || ua === "unknown") {
5
+ return { deviceType: "desktop", browser: "Unknown", os: "Unknown" };
6
+ }
7
+ let deviceType = "desktop";
8
+ if (/mobile|android|iphone|ipod|blackberry|iemobile|opera mini/i.test(ua)) {
9
+ deviceType = "mobile";
10
+ } else if (/ipad|tablet|playbook|silk/i.test(ua)) {
11
+ deviceType = "tablet";
12
+ }
13
+ let browser = "Unknown";
14
+ if (/\bdia\b|diahq|diabrowser/i.test(ua))
15
+ browser = "Dia";
16
+ else if (/arc\//i.test(ua))
17
+ browser = "Arc";
18
+ else if (/edg/i.test(ua))
19
+ browser = "Edge";
20
+ else if (/opr\b|opera/i.test(ua))
21
+ browser = "Opera";
22
+ else if (/brave/i.test(ua))
23
+ browser = "Brave";
24
+ else if (/vivaldi/i.test(ua))
25
+ browser = "Vivaldi";
26
+ else if (/yabrowser/i.test(ua))
27
+ browser = "Yandex";
28
+ else if (/whale/i.test(ua))
29
+ browser = "Whale";
30
+ else if (/puffin/i.test(ua))
31
+ browser = "Puffin";
32
+ else if (/qqbrowser/i.test(ua))
33
+ browser = "QQ Browser";
34
+ else if (/ucbrowser/i.test(ua))
35
+ browser = "UC Browser";
36
+ else if (/samsungbrowser/i.test(ua))
37
+ browser = "Samsung Internet";
38
+ else if (/silk/i.test(ua))
39
+ browser = "Amazon Silk";
40
+ else if (/duckduckgo/i.test(ua))
41
+ browser = "DuckDuckGo";
42
+ else if (/firefox|fxios/i.test(ua))
43
+ browser = "Firefox";
44
+ else if (/chrome|chromium|crios/i.test(ua))
45
+ browser = "Chrome";
46
+ else if (/safari/i.test(ua) && !/chrome|chromium/i.test(ua))
47
+ browser = "Safari";
48
+ else if (/trident|msie/i.test(ua))
49
+ browser = "IE";
50
+ else if (/bot|crawl|spider|slurp|bingpreview/i.test(ua))
51
+ browser = "Bot";
52
+ let os = "Unknown";
53
+ if (/windows nt 10/i.test(ua))
54
+ os = "Windows 10";
55
+ else if (/windows nt 11/i.test(ua))
56
+ os = "Windows 11";
57
+ else if (/windows/i.test(ua))
58
+ os = "Windows";
59
+ else if (/mac os x|macintosh/i.test(ua))
60
+ os = "macOS";
61
+ else if (/iphone|ipad|ipod/i.test(ua))
62
+ os = "iOS";
63
+ else if (/android/i.test(ua))
64
+ os = "Android";
65
+ else if (/linux/i.test(ua))
66
+ os = "Linux";
67
+ else if (/cros/i.test(ua))
68
+ os = "Chrome OS";
69
+ return { deviceType, browser, os };
70
+ }
71
+ var BOT_UA_PATTERN = new RegExp([
72
+ "bot|crawl|spider|slurp|preview|scan|probe|monitor(?:ing)?|checker|validator",
73
+ "headless|phantomjs|puppeteer|playwright|selenium|webdriver|electron",
74
+ "curl|wget|python-requests|python-urllib|aiohttp|httpx|go-http-client|okhttp",
75
+ "java/|libwww|lwp::|php/|guzzle|axios|node-fetch|undici|bun/",
76
+ "facebookexternalhit|whatsapp|telegrambot|discordbot|slackbot|twitterbot",
77
+ "pingdom|uptimerobot|statuscake|newrelic|datadog|site24x7",
78
+ "gptbot|claudebot|ccbot|bytespider|amazonbot|anthropic|perplexity"
79
+ ].join("|"), "i");
80
+ function isBot(ua) {
81
+ if (!ua || ua === "unknown" || ua.length < 12)
82
+ return true;
83
+ return BOT_UA_PATTERN.test(ua);
84
+ }
85
+ function getBrowserFamily(browser) {
86
+ const chromiumBased = ["Chrome", "Edge", "Opera", "Brave", "Vivaldi", "Arc", "Dia", "Whale", "Samsung Internet"];
87
+ if (chromiumBased.includes(browser))
88
+ return "Chromium";
89
+ if (browser === "Firefox")
90
+ return "Firefox";
91
+ if (browser === "Safari")
92
+ return "Safari";
93
+ return "Other";
94
+ }
95
+
96
+ // src/utils/geolocation.ts
97
+ var COUNTRY_NAMES = {
98
+ US: "United States",
99
+ GB: "United Kingdom",
100
+ CA: "Canada",
101
+ AU: "Australia",
102
+ DE: "Germany",
103
+ FR: "France",
104
+ JP: "Japan",
105
+ CN: "China",
106
+ IN: "India",
107
+ BR: "Brazil",
108
+ MX: "Mexico",
109
+ ES: "Spain",
110
+ IT: "Italy",
111
+ NL: "Netherlands",
112
+ SE: "Sweden",
113
+ NO: "Norway",
114
+ DK: "Denmark",
115
+ FI: "Finland",
116
+ CH: "Switzerland",
117
+ AT: "Austria",
118
+ BE: "Belgium",
119
+ PL: "Poland",
120
+ RU: "Russia",
121
+ KR: "South Korea",
122
+ SG: "Singapore",
123
+ HK: "Hong Kong",
124
+ TW: "Taiwan",
125
+ NZ: "New Zealand",
126
+ IE: "Ireland",
127
+ PT: "Portugal",
128
+ CZ: "Czech Republic",
129
+ GR: "Greece",
130
+ IL: "Israel",
131
+ ZA: "South Africa",
132
+ AR: "Argentina",
133
+ CL: "Chile",
134
+ CO: "Colombia",
135
+ PH: "Philippines",
136
+ TH: "Thailand",
137
+ MY: "Malaysia",
138
+ ID: "Indonesia",
139
+ VN: "Vietnam",
140
+ AE: "UAE",
141
+ SA: "Saudi Arabia",
142
+ TR: "Turkey",
143
+ UA: "Ukraine",
144
+ RO: "Romania",
145
+ HU: "Hungary"
146
+ };
147
+ var NAME_TO_CODE = Object.fromEntries(Object.entries(COUNTRY_NAMES).map(([code, name]) => [name, code]));
148
+ function countryCodeOf(nameOrCode) {
149
+ if (/^[A-Z]{2}$/i.test(nameOrCode))
150
+ return nameOrCode.toUpperCase();
151
+ return NAME_TO_CODE[nameOrCode];
152
+ }
153
+ function countryFlagEmoji(code) {
154
+ if (!/^[A-Z]{2}$/i.test(code))
155
+ return "";
156
+ const cc = code.toUpperCase();
157
+ return String.fromCodePoint(127462 + cc.charCodeAt(0) - 65, 127462 + cc.charCodeAt(1) - 65);
158
+ }
159
+ var ipGeoCache = new Map;
160
+ function getCountryFromHeaders(headers) {
161
+ if (!headers)
162
+ return;
163
+ const countryCode = headers["cloudfront-viewer-country"] || headers["CloudFront-Viewer-Country"] || headers["x-country-code"] || headers["cf-ipcountry"];
164
+ if (countryCode && countryCode !== "XX") {
165
+ return COUNTRY_NAMES[countryCode.toUpperCase()] || countryCode.toUpperCase();
166
+ }
167
+ return;
168
+ }
169
+ function anonymizeIp(ip, mode = "partial") {
170
+ if (!ip || mode === "none")
171
+ return ip;
172
+ if (mode === "full")
173
+ return "";
174
+ if (ip.includes(":")) {
175
+ const parts = ip.split(":");
176
+ return `${parts.slice(0, 3).join(":")}::`;
177
+ }
178
+ const octets = ip.split(".");
179
+ if (octets.length === 4) {
180
+ octets[3] = "0";
181
+ return octets.join(".");
182
+ }
183
+ return ip;
184
+ }
185
+ var REFERRER_MAP = [
186
+ { match: "mail.google", name: "Gmail", channel: "Email" },
187
+ { match: "mail.yahoo", name: "Yahoo Mail", channel: "Email" },
188
+ { match: "outlook.", name: "Outlook", channel: "Email" },
189
+ { match: "mail.proton", name: "Proton Mail", channel: "Email" },
190
+ { match: "chatgpt.", name: "ChatGPT", channel: "AI" },
191
+ { match: "chat.openai", name: "ChatGPT", channel: "AI" },
192
+ { match: "openai.", name: "ChatGPT", channel: "AI" },
193
+ { match: "perplexity", name: "Perplexity", channel: "AI" },
194
+ { match: "gemini.google", name: "Gemini", channel: "AI" },
195
+ { match: "bard.google", name: "Gemini", channel: "AI" },
196
+ { match: "claude.ai", name: "Claude", channel: "AI" },
197
+ { match: "copilot.microsoft", name: "Copilot", channel: "AI" },
198
+ { match: /(^|\.)you\.com$/, name: "You.com", channel: "AI" },
199
+ { match: "phind.", name: "Phind", channel: "AI" },
200
+ { match: "google", name: "Google", channel: "Search" },
201
+ { match: "bing", name: "Bing", channel: "Search" },
202
+ { match: "duckduckgo", name: "DuckDuckGo", channel: "Search" },
203
+ { match: "yahoo", name: "Yahoo", channel: "Search" },
204
+ { match: "yandex", name: "Yandex", channel: "Search" },
205
+ { match: "baidu", name: "Baidu", channel: "Search" },
206
+ { match: "ecosia", name: "Ecosia", channel: "Search" },
207
+ { match: "startpage", name: "Startpage", channel: "Search" },
208
+ { match: "search.brave", name: "Brave Search", channel: "Search" },
209
+ { match: "qwant", name: "Qwant", channel: "Search" },
210
+ { match: "search.marginalia", name: "Marginalia", channel: "Search" },
211
+ { match: /(^|\.)ask\.com$/, name: "Ask", channel: "Search" },
212
+ { match: "reddit", name: "Reddit", channel: "Social" },
213
+ { match: "facebook", name: "Facebook", channel: "Social" },
214
+ { match: /(^|\.)fb\.com$/, name: "Facebook", channel: "Social" },
215
+ { match: /(^|\.)fb\.me$/, name: "Facebook", channel: "Social" },
216
+ { match: "instagram", name: "Instagram", channel: "Social" },
217
+ { match: /^t\.co$/, name: "Twitter", channel: "Social" },
218
+ { match: "twitter", name: "Twitter", channel: "Social" },
219
+ { match: /(^|\.)x\.com$/, name: "Twitter", channel: "Social" },
220
+ { match: "linkedin", name: "LinkedIn", channel: "Social" },
221
+ { match: /(^|\.)lnkd\.in$/, name: "LinkedIn", channel: "Social" },
222
+ { match: "youtube", name: "YouTube", channel: "Social" },
223
+ { match: /(^|\.)youtu\.be$/, name: "YouTube", channel: "Social" },
224
+ { match: "pinterest", name: "Pinterest", channel: "Social" },
225
+ { match: "tiktok", name: "TikTok", channel: "Social" },
226
+ { match: "threads.net", name: "Threads", channel: "Social" },
227
+ { match: "mastodon", name: "Mastodon", channel: "Social" },
228
+ { match: "quora", name: "Quora", channel: "Social" },
229
+ { match: "snapchat", name: "Snapchat", channel: "Social" },
230
+ { match: "tumblr", name: "Tumblr", channel: "Social" },
231
+ { match: "telegram", name: "Telegram", channel: "Social" },
232
+ { match: /^t\.me$/, name: "Telegram", channel: "Social" },
233
+ { match: "news.ycombinator", name: "Hacker News", channel: "Social" },
234
+ { match: "ycombinator", name: "Hacker News", channel: "Social" },
235
+ { match: "github", name: "GitHub", channel: "Referral" },
236
+ { match: "gitlab", name: "GitLab", channel: "Referral" },
237
+ { match: "stackoverflow", name: "Stack Overflow", channel: "Referral" },
238
+ { match: "medium.com", name: "Medium", channel: "Referral" },
239
+ { match: "dev.to", name: "DEV", channel: "Referral" }
240
+ ];
241
+ function matchReferrer(host) {
242
+ return REFERRER_MAP.find((e) => typeof e.match === "string" ? host.includes(e.match) : e.match.test(host));
243
+ }
244
+ function parseReferrerSource(referrer) {
245
+ if (!referrer)
246
+ return "Direct";
247
+ try {
248
+ const host = new URL(referrer).hostname.toLowerCase();
249
+ const entry = matchReferrer(host);
250
+ if (entry)
251
+ return entry.name;
252
+ return host.startsWith("www.") ? host.slice(4) : host;
253
+ } catch {
254
+ return "Unknown";
255
+ }
256
+ }
257
+ var SPAM_REFERRERS = [
258
+ "semalt.com",
259
+ "buttons-for-website.com",
260
+ "buttons-for-your-website.com",
261
+ "darodar.com",
262
+ "best-seo-offer.com",
263
+ "best-seo-solution.com",
264
+ "free-share-buttons.com",
265
+ "free-social-buttons.com",
266
+ "get-free-traffic-now.com",
267
+ "social-buttons.com",
268
+ "success-seo.com",
269
+ "trafficmonetizer.org",
270
+ "simple-share-buttons.com",
271
+ "4webmasters.org",
272
+ "ilovevitaly.com",
273
+ "priceg.com",
274
+ "blackhatworth.com",
275
+ "econom.co",
276
+ "savetubevideo.com",
277
+ "kambasoft.com",
278
+ "voucherssite.com",
279
+ "sharebutton.net",
280
+ "sitevaluation.org",
281
+ "dailyrank.net",
282
+ "lifehac\u0138er.com",
283
+ "o-o-6-o-o.com",
284
+ "humanorightswatch.org",
285
+ "guardlink.org",
286
+ "cenoval.ru",
287
+ "descargar-musica-gratis.net"
288
+ ];
289
+ function isSpamReferrer(referrer) {
290
+ if (!referrer)
291
+ return false;
292
+ try {
293
+ const host = new URL(referrer).hostname.toLowerCase().replace(/^www\./, "");
294
+ return SPAM_REFERRERS.some((d) => host === d || host.endsWith(`.${d}`));
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+ function getRegionFromHeaders(headers) {
300
+ if (!headers)
301
+ return;
302
+ return headers["cloudfront-viewer-country-region-name"] || headers["CloudFront-Viewer-Country-Region-Name"] || headers["cf-region"];
303
+ }
304
+
305
+ export { parseUserAgent, isBot, getBrowserFamily, countryCodeOf, countryFlagEmoji, getCountryFromHeaders, anonymizeIp, parseReferrerSource, isSpamReferrer, getRegionFromHeaders };