@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,595 @@
1
+ /**
2
+ * CloudFormation Template Generator for Analytics DynamoDB Table
3
+ *
4
+ * Generates CloudFormation JSON/YAML for deploying the analytics
5
+ * single-table design to AWS.
6
+ */
7
+
8
+ export interface CloudFormationConfig {
9
+ /** Stack name */
10
+ stackName?: string
11
+ /** Table name */
12
+ tableName?: string
13
+ /** Billing mode */
14
+ billingMode?: 'PAY_PER_REQUEST' | 'PROVISIONED'
15
+ /** Provisioned capacity (if billingMode is PROVISIONED) */
16
+ provisionedCapacity?: {
17
+ readCapacityUnits: number
18
+ writeCapacityUnits: number
19
+ }
20
+ /** Enable Point-in-Time Recovery */
21
+ enablePitr?: boolean
22
+ /** Enable DynamoDB Streams */
23
+ enableStreams?: boolean
24
+ /** Stream view type */
25
+ streamViewType?: 'KEYS_ONLY' | 'NEW_IMAGE' | 'OLD_IMAGE' | 'NEW_AND_OLD_IMAGES'
26
+ /** TTL attribute name */
27
+ ttlAttributeName?: string
28
+ /** Enable server-side encryption */
29
+ enableEncryption?: boolean
30
+ /** KMS key ARN for encryption (optional, uses AWS managed key if not specified) */
31
+ kmsKeyArn?: string
32
+ /** Tags to apply to resources */
33
+ tags?: Record<string, string>
34
+ /** Enable deletion protection */
35
+ deletionProtection?: boolean
36
+ }
37
+
38
+ const defaultConfig: Required<CloudFormationConfig> = {
39
+ stackName: 'analytics-dynamodb',
40
+ tableName: 'analytics',
41
+ billingMode: 'PAY_PER_REQUEST',
42
+ provisionedCapacity: {
43
+ readCapacityUnits: 5,
44
+ writeCapacityUnits: 5,
45
+ },
46
+ enablePitr: true,
47
+ enableStreams: false,
48
+ streamViewType: 'NEW_AND_OLD_IMAGES',
49
+ ttlAttributeName: 'ttl',
50
+ enableEncryption: true,
51
+ kmsKeyArn: '',
52
+ tags: {},
53
+ deletionProtection: false,
54
+ }
55
+
56
+ /**
57
+ * Analytics table attribute definitions
58
+ */
59
+ const attributeDefinitions = [
60
+ { AttributeName: 'pk', AttributeType: 'S' },
61
+ { AttributeName: 'sk', AttributeType: 'S' },
62
+ { AttributeName: 'gsi1pk', AttributeType: 'S' },
63
+ { AttributeName: 'gsi1sk', AttributeType: 'S' },
64
+ { AttributeName: 'gsi2pk', AttributeType: 'S' },
65
+ { AttributeName: 'gsi2sk', AttributeType: 'S' },
66
+ ]
67
+
68
+ /**
69
+ * Analytics table key schema
70
+ */
71
+ const keySchema = [
72
+ { AttributeName: 'pk', KeyType: 'HASH' },
73
+ { AttributeName: 'sk', KeyType: 'RANGE' },
74
+ ]
75
+
76
+ /**
77
+ * Global Secondary Index definitions for analytics
78
+ *
79
+ * GSI1: Owner lookups, date-based queries
80
+ * - Sites by owner: gsi1pk=OWNER#{ownerId}, gsi1sk=SITE#{siteId}
81
+ * - PageViews by date: gsi1pk=SITE#{id}#DATE#{date}, gsi1sk=PATH#{path}
82
+ * - Sessions by date: gsi1pk=SITE#{id}#SESSIONS#{date}
83
+ * - Events by name: gsi1pk=SITE#{id}#EVENTNAME#{name}
84
+ * - Goals by site: gsi1pk=SITE#{id}#GOAL#{goalId}
85
+ * - Page stats by period: gsi1pk=SITE#{id}#PAGESTATS#{period}#{start}
86
+ *
87
+ * GSI2: Visitor lookups
88
+ * - PageViews by visitor: gsi2pk=SITE#{id}#VISITOR#{visitorId}
89
+ */
90
+ function getGsiDefinitions(config: Required<CloudFormationConfig>) {
91
+ const projection = { ProjectionType: 'ALL' }
92
+
93
+ const gsis = [
94
+ {
95
+ IndexName: 'GSI1',
96
+ KeySchema: [
97
+ { AttributeName: 'gsi1pk', KeyType: 'HASH' },
98
+ { AttributeName: 'gsi1sk', KeyType: 'RANGE' },
99
+ ],
100
+ Projection: projection,
101
+ },
102
+ {
103
+ IndexName: 'GSI2',
104
+ KeySchema: [
105
+ { AttributeName: 'gsi2pk', KeyType: 'HASH' },
106
+ { AttributeName: 'gsi2sk', KeyType: 'RANGE' },
107
+ ],
108
+ Projection: projection,
109
+ },
110
+ ]
111
+
112
+ // Add provisioned capacity if not on-demand
113
+ if (config.billingMode === 'PROVISIONED') {
114
+ return gsis.map(gsi => ({
115
+ ...gsi,
116
+ ProvisionedThroughput: {
117
+ ReadCapacityUnits: config.provisionedCapacity.readCapacityUnits,
118
+ WriteCapacityUnits: config.provisionedCapacity.writeCapacityUnits,
119
+ },
120
+ }))
121
+ }
122
+
123
+ return gsis
124
+ }
125
+
126
+ /**
127
+ * Generate CloudFormation template for analytics table
128
+ */
129
+ export function generateCloudFormationTemplate(
130
+ userConfig: CloudFormationConfig = {},
131
+ ): Record<string, unknown> {
132
+ const config = { ...defaultConfig, ...userConfig }
133
+
134
+ const tableProperties: Record<string, unknown> = {
135
+ TableName: config.tableName,
136
+ AttributeDefinitions: attributeDefinitions,
137
+ KeySchema: keySchema,
138
+ GlobalSecondaryIndexes: getGsiDefinitions(config),
139
+ BillingMode: config.billingMode,
140
+ }
141
+
142
+ // Add provisioned capacity if not on-demand
143
+ if (config.billingMode === 'PROVISIONED') {
144
+ tableProperties.ProvisionedThroughput = {
145
+ ReadCapacityUnits: config.provisionedCapacity.readCapacityUnits,
146
+ WriteCapacityUnits: config.provisionedCapacity.writeCapacityUnits,
147
+ }
148
+ }
149
+
150
+ // TTL configuration
151
+ if (config.ttlAttributeName) {
152
+ tableProperties.TimeToLiveSpecification = {
153
+ AttributeName: config.ttlAttributeName,
154
+ Enabled: true,
155
+ }
156
+ }
157
+
158
+ // Stream configuration
159
+ if (config.enableStreams) {
160
+ tableProperties.StreamSpecification = {
161
+ StreamViewType: config.streamViewType,
162
+ }
163
+ }
164
+
165
+ // Server-side encryption
166
+ if (config.enableEncryption) {
167
+ tableProperties.SSESpecification = {
168
+ SSEEnabled: true,
169
+ ...(config.kmsKeyArn && {
170
+ SSEType: 'KMS',
171
+ KMSMasterKeyId: config.kmsKeyArn,
172
+ }),
173
+ }
174
+ }
175
+
176
+ // Point-in-time recovery
177
+ if (config.enablePitr) {
178
+ tableProperties.PointInTimeRecoverySpecification = {
179
+ PointInTimeRecoveryEnabled: true,
180
+ }
181
+ }
182
+
183
+ // Deletion protection
184
+ if (config.deletionProtection) {
185
+ tableProperties.DeletionProtectionEnabled = true
186
+ }
187
+
188
+ // Tags
189
+ if (Object.keys(config.tags).length > 0) {
190
+ tableProperties.Tags = Object.entries(config.tags).map(([Key, Value]) => ({
191
+ Key,
192
+ Value,
193
+ }))
194
+ }
195
+
196
+ return {
197
+ AWSTemplateFormatVersion: '2010-09-09',
198
+ Description: 'DynamoDB table for privacy-focused web analytics (single-table design)',
199
+
200
+ Parameters: {
201
+ Environment: {
202
+ Type: 'String',
203
+ Default: 'production',
204
+ AllowedValues: ['development', 'staging', 'production'],
205
+ Description: 'Deployment environment',
206
+ },
207
+ },
208
+
209
+ Resources: {
210
+ AnalyticsTable: {
211
+ Type: 'AWS::DynamoDB::Table',
212
+ Properties: tableProperties,
213
+ },
214
+ },
215
+
216
+ Outputs: {
217
+ TableName: {
218
+ Description: 'Analytics DynamoDB table name',
219
+ Value: { Ref: 'AnalyticsTable' },
220
+ Export: {
221
+ Name: { 'Fn::Sub': '${AWS::StackName}-TableName' },
222
+ },
223
+ },
224
+ TableArn: {
225
+ Description: 'Analytics DynamoDB table ARN',
226
+ Value: { 'Fn::GetAtt': ['AnalyticsTable', 'Arn'] },
227
+ Export: {
228
+ Name: { 'Fn::Sub': '${AWS::StackName}-TableArn' },
229
+ },
230
+ },
231
+ ...(config.enableStreams && {
232
+ StreamArn: {
233
+ Description: 'DynamoDB Stream ARN',
234
+ Value: { 'Fn::GetAtt': ['AnalyticsTable', 'StreamArn'] },
235
+ Export: {
236
+ Name: { 'Fn::Sub': '${AWS::StackName}-StreamArn' },
237
+ },
238
+ },
239
+ }),
240
+ },
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Generate CloudFormation template as JSON string
246
+ */
247
+ export function generateCloudFormationJson(
248
+ config: CloudFormationConfig = {},
249
+ pretty = true,
250
+ ): string {
251
+ const template = generateCloudFormationTemplate(config)
252
+ return pretty ? JSON.stringify(template, null, 2) : JSON.stringify(template)
253
+ }
254
+
255
+ /**
256
+ * Generate CloudFormation template as YAML string
257
+ */
258
+ export function generateCloudFormationYaml(
259
+ config: CloudFormationConfig = {},
260
+ ): string {
261
+ const template = generateCloudFormationTemplate(config)
262
+ return jsonToYaml(template)
263
+ }
264
+
265
+ /**
266
+ * Simple JSON to YAML converter (handles CloudFormation intrinsic functions)
267
+ */
268
+ // eslint-disable-next-line pickier/no-unused-vars
269
+ function jsonToYaml(obj: unknown, indent = 0): string {
270
+ const spaces = ' '.repeat(indent)
271
+
272
+ if (obj === null || obj === undefined) {
273
+ return 'null'
274
+ }
275
+
276
+ if (typeof obj === 'string') {
277
+ // Handle multi-line strings or strings with special characters
278
+ if (obj.includes('\n') || obj.includes(':') || obj.includes('#')) {
279
+ return `"${obj.replace(/"/g, '\\"')}"`
280
+ }
281
+ return obj
282
+ }
283
+
284
+ if (typeof obj === 'number' || typeof obj === 'boolean') {
285
+ return String(obj)
286
+ }
287
+
288
+ if (Array.isArray(obj)) {
289
+ if (obj.length === 0)
290
+ return '[]'
291
+ return obj
292
+ .map((item) => {
293
+ const value = jsonToYaml(item, indent + 1)
294
+ if (typeof item === 'object' && item !== null) {
295
+ return `${spaces}- ${value.trim().replace(/^\s+/gm, match => `${spaces} ${match.slice(spaces.length + 2)}`)}`
296
+ }
297
+ return `${spaces}- ${value}`
298
+ })
299
+ .join('\n')
300
+ }
301
+
302
+ if (typeof obj === 'object') {
303
+ const entries = Object.entries(obj as Record<string, unknown>)
304
+ if (entries.length === 0)
305
+ return '{}'
306
+
307
+ // Handle CloudFormation intrinsic functions
308
+ if (entries.length === 1) {
309
+ const [key, value] = entries[0]
310
+ if (key === 'Ref') {
311
+ return `!Ref ${value}`
312
+ }
313
+ if (key === 'Fn::Sub') {
314
+ return `!Sub "${value}"`
315
+ }
316
+ if (key === 'Fn::GetAtt') {
317
+ if (Array.isArray(value)) {
318
+ return `!GetAtt ${(value as string[]).join('.')}`
319
+ }
320
+ return `!GetAtt ${value}`
321
+ }
322
+ if (key === 'Fn::Join') {
323
+ const [delimiter, parts] = value as [string, unknown[]]
324
+ return `!Join\n${spaces} - "${delimiter}"\n${spaces} - ${jsonToYaml(parts, indent + 2)}`
325
+ }
326
+ }
327
+
328
+ return entries
329
+ .map(([key, value]) => {
330
+ const yamlValue = jsonToYaml(value, indent + 1)
331
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
332
+ return `${spaces}${key}:\n${yamlValue}`
333
+ }
334
+ if (Array.isArray(value)) {
335
+ return `${spaces}${key}:\n${yamlValue}`
336
+ }
337
+ return `${spaces}${key}: ${yamlValue}`
338
+ })
339
+ .join('\n')
340
+ }
341
+
342
+ return String(obj)
343
+ }
344
+
345
+ /**
346
+ * Generate SAM (Serverless Application Model) template
347
+ * Includes Lambda functions for API endpoints
348
+ */
349
+ export function generateSamTemplate(config: CloudFormationConfig = {}): Record<string, unknown> {
350
+ const cfnTemplate = generateCloudFormationTemplate(config)
351
+ const _mergedConfig = { ...defaultConfig, ...config }
352
+
353
+ return {
354
+ AWSTemplateFormatVersion: '2010-09-09',
355
+ Transform: 'AWS::Serverless-2016-10-31',
356
+ Description: 'Privacy-focused web analytics with DynamoDB and Lambda',
357
+
358
+ Globals: {
359
+ Function: {
360
+ Runtime: 'nodejs20.x',
361
+ Timeout: 30,
362
+ MemorySize: 256,
363
+ Environment: {
364
+ Variables: {
365
+ ANALYTICS_TABLE: { Ref: 'AnalyticsTable' },
366
+ NODE_ENV: { Ref: 'Environment' },
367
+ },
368
+ },
369
+ Architectures: ['arm64'],
370
+ },
371
+ },
372
+
373
+ Parameters: {
374
+ ...((cfnTemplate as Record<string, unknown>).Parameters as Record<string, unknown> || {}),
375
+ CorsOrigins: {
376
+ Type: 'String',
377
+ Default: '*',
378
+ Description: 'CORS allowed origins (comma-separated)',
379
+ },
380
+ },
381
+
382
+ Resources: {
383
+ // DynamoDB Table
384
+ AnalyticsTable: (cfnTemplate as Record<string, unknown>).Resources
385
+ ? ((cfnTemplate as Record<string, unknown>).Resources as Record<string, unknown>).AnalyticsTable
386
+ : {},
387
+
388
+ // API Gateway
389
+ AnalyticsApi: {
390
+ Type: 'AWS::Serverless::HttpApi',
391
+ Properties: {
392
+ StageName: { Ref: 'Environment' },
393
+ CorsConfiguration: {
394
+ AllowOrigins: [{ Ref: 'CorsOrigins' }],
395
+ AllowMethods: ['GET', 'POST', 'OPTIONS'],
396
+ AllowHeaders: ['Content-Type', 'Authorization'],
397
+ },
398
+ },
399
+ },
400
+
401
+ // Lambda Functions
402
+ CollectFunction: {
403
+ Type: 'AWS::Serverless::Function',
404
+ Properties: {
405
+ Handler: 'index.collectHandler',
406
+ CodeUri: './dist/lambda',
407
+ Description: 'Collect analytics events',
408
+ Events: {
409
+ Api: {
410
+ Type: 'HttpApi',
411
+ Properties: {
412
+ ApiId: { Ref: 'AnalyticsApi' },
413
+ Path: '/api/analytics/collect',
414
+ Method: 'POST',
415
+ },
416
+ },
417
+ },
418
+ Policies: [
419
+ {
420
+ DynamoDBCrudPolicy: {
421
+ TableName: { Ref: 'AnalyticsTable' },
422
+ },
423
+ },
424
+ ],
425
+ },
426
+ },
427
+
428
+ StatsFunction: {
429
+ Type: 'AWS::Serverless::Function',
430
+ Properties: {
431
+ Handler: 'index.statsHandler',
432
+ CodeUri: './dist/lambda',
433
+ Description: 'Get analytics statistics',
434
+ Events: {
435
+ Api: {
436
+ Type: 'HttpApi',
437
+ Properties: {
438
+ ApiId: { Ref: 'AnalyticsApi' },
439
+ Path: '/api/analytics/sites/{siteId}/stats',
440
+ Method: 'GET',
441
+ },
442
+ },
443
+ },
444
+ Policies: [
445
+ {
446
+ DynamoDBReadPolicy: {
447
+ TableName: { Ref: 'AnalyticsTable' },
448
+ },
449
+ },
450
+ ],
451
+ },
452
+ },
453
+
454
+ RealtimeFunction: {
455
+ Type: 'AWS::Serverless::Function',
456
+ Properties: {
457
+ Handler: 'index.realtimeHandler',
458
+ CodeUri: './dist/lambda',
459
+ Description: 'Get realtime analytics',
460
+ Events: {
461
+ Api: {
462
+ Type: 'HttpApi',
463
+ Properties: {
464
+ ApiId: { Ref: 'AnalyticsApi' },
465
+ Path: '/api/analytics/sites/{siteId}/realtime',
466
+ Method: 'GET',
467
+ },
468
+ },
469
+ },
470
+ Policies: [
471
+ {
472
+ DynamoDBReadPolicy: {
473
+ TableName: { Ref: 'AnalyticsTable' },
474
+ },
475
+ },
476
+ ],
477
+ },
478
+ },
479
+
480
+ SitesFunction: {
481
+ Type: 'AWS::Serverless::Function',
482
+ Properties: {
483
+ Handler: 'index.sitesHandler',
484
+ CodeUri: './dist/lambda',
485
+ Description: 'Manage analytics sites',
486
+ Events: {
487
+ List: {
488
+ Type: 'HttpApi',
489
+ Properties: {
490
+ ApiId: { Ref: 'AnalyticsApi' },
491
+ Path: '/api/analytics/sites',
492
+ Method: 'GET',
493
+ },
494
+ },
495
+ Create: {
496
+ Type: 'HttpApi',
497
+ Properties: {
498
+ ApiId: { Ref: 'AnalyticsApi' },
499
+ Path: '/api/analytics/sites',
500
+ Method: 'POST',
501
+ },
502
+ },
503
+ Get: {
504
+ Type: 'HttpApi',
505
+ Properties: {
506
+ ApiId: { Ref: 'AnalyticsApi' },
507
+ Path: '/api/analytics/sites/{siteId}',
508
+ Method: 'GET',
509
+ },
510
+ },
511
+ },
512
+ Policies: [
513
+ {
514
+ DynamoDBCrudPolicy: {
515
+ TableName: { Ref: 'AnalyticsTable' },
516
+ },
517
+ },
518
+ ],
519
+ },
520
+ },
521
+
522
+ GoalsFunction: {
523
+ Type: 'AWS::Serverless::Function',
524
+ Properties: {
525
+ Handler: 'index.goalsHandler',
526
+ CodeUri: './dist/lambda',
527
+ Description: 'Manage analytics goals',
528
+ Events: {
529
+ List: {
530
+ Type: 'HttpApi',
531
+ Properties: {
532
+ ApiId: { Ref: 'AnalyticsApi' },
533
+ Path: '/api/analytics/sites/{siteId}/goals',
534
+ Method: 'GET',
535
+ },
536
+ },
537
+ Create: {
538
+ Type: 'HttpApi',
539
+ Properties: {
540
+ ApiId: { Ref: 'AnalyticsApi' },
541
+ Path: '/api/analytics/sites/{siteId}/goals',
542
+ Method: 'POST',
543
+ },
544
+ },
545
+ },
546
+ Policies: [
547
+ {
548
+ DynamoDBCrudPolicy: {
549
+ TableName: { Ref: 'AnalyticsTable' },
550
+ },
551
+ },
552
+ ],
553
+ },
554
+ },
555
+
556
+ ScriptFunction: {
557
+ Type: 'AWS::Serverless::Function',
558
+ Properties: {
559
+ Handler: 'index.scriptHandler',
560
+ CodeUri: './dist/lambda',
561
+ Description: 'Generate tracking script',
562
+ Events: {
563
+ Api: {
564
+ Type: 'HttpApi',
565
+ Properties: {
566
+ ApiId: { Ref: 'AnalyticsApi' },
567
+ Path: '/api/analytics/sites/{siteId}/script',
568
+ Method: 'GET',
569
+ },
570
+ },
571
+ },
572
+ },
573
+ },
574
+
575
+ },
576
+
577
+ Outputs: {
578
+ ...((cfnTemplate as Record<string, unknown>).Outputs || {}),
579
+ ApiEndpoint: {
580
+ Description: 'Analytics API endpoint',
581
+ Value: {
582
+ 'Fn::Sub': 'https://${AnalyticsApi}.execute-api.${AWS::Region}.amazonaws.com/${Environment}',
583
+ },
584
+ },
585
+ },
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Generate SAM template as YAML string
591
+ */
592
+ export function generateSamYaml(config: CloudFormationConfig = {}): string {
593
+ const template = generateSamTemplate(config)
594
+ return jsonToYaml(template)
595
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Analytics Infrastructure Module
3
+ *
4
+ * Provides tools for deploying the analytics DynamoDB table:
5
+ * - CloudFormation templates (JSON/YAML)
6
+ * - SAM templates for serverless deployment
7
+ * - CDK code generation
8
+ * - AWS SDK CreateTable input
9
+ * - Migration utilities
10
+ * - Setup scripts
11
+ */
12
+
13
+ // CDK
14
+ export {
15
+ type AnalyticsApiProps,
16
+ type AnalyticsTableProps,
17
+ generateCdkCode,
18
+ generateCdkTableCode,
19
+ generateCreateTableInput,
20
+ } from './cdk'
21
+
22
+ // CloudFormation
23
+ export {
24
+ type CloudFormationConfig,
25
+ generateCloudFormationJson,
26
+ generateCloudFormationTemplate,
27
+ generateCloudFormationYaml,
28
+ generateSamTemplate,
29
+ generateSamYaml,
30
+ } from './cloudformation'
31
+
32
+ // Setup & Migrations
33
+ export {
34
+ checkTableStatus,
35
+ createAnalyticsTable,
36
+ createPitrMigration,
37
+ createStreamsMigration,
38
+ createTimeBasedGsiMigration,
39
+ type DynamoDBClientLike,
40
+ enableTtl,
41
+ generateAwsCliCommands,
42
+ type MigrationResult,
43
+ type MigrationStep,
44
+ printSetupInstructions,
45
+ runMigrations,
46
+ type SetupConfig,
47
+ type SetupResult,
48
+ } from './setup'