lytx 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/.env.example +37 -0
  2. package/README.md +486 -0
  3. package/alchemy.run.ts +155 -0
  4. package/cli/bootstrap-admin.ts +284 -0
  5. package/cli/deploy-staging.ts +692 -0
  6. package/cli/import-events.ts +628 -0
  7. package/cli/import-sites.ts +518 -0
  8. package/cli/index.ts +609 -0
  9. package/cli/init-db.ts +269 -0
  10. package/cli/migrate-to-durable-objects.ts +564 -0
  11. package/cli/migration-worker.ts +300 -0
  12. package/cli/performance-test.ts +588 -0
  13. package/cli/pg/client.ts +4 -0
  14. package/cli/pg/new-site.ts +153 -0
  15. package/cli/rollback-durable-objects.ts +622 -0
  16. package/cli/seed-data.ts +459 -0
  17. package/cli/setup.js +18 -0
  18. package/cli/setup.ts +463 -0
  19. package/cli/validate-migration.ts +200 -0
  20. package/cli/wrangler-migration.jsonc +28 -0
  21. package/db/adapter.ts +166 -0
  22. package/db/analytics_engine/client.ts +0 -0
  23. package/db/analytics_engine/sites.ts +0 -0
  24. package/db/client.ts +16 -0
  25. package/db/d1/client.ts +8 -0
  26. package/db/d1/drizzle.config.ts +35 -0
  27. package/db/d1/migrations/0000_true_maelstrom.sql +165 -0
  28. package/db/d1/migrations/0001_wonderful_bloodaxe.sql +12 -0
  29. package/db/d1/migrations/0002_late_frightful_four.sql +1 -0
  30. package/db/d1/migrations/0003_cuddly_obadiah_stane.sql +16 -0
  31. package/db/d1/migrations/0004_mute_stardust.sql +1 -0
  32. package/db/d1/migrations/0005_awesome_silvermane.sql +3 -0
  33. package/db/d1/migrations/0006_volatile_shriek.sql +2 -0
  34. package/db/d1/migrations/0007_superb_lila_cheney.sql +1 -0
  35. package/db/d1/migrations/0008_bitter_longshot.sql +17 -0
  36. package/db/d1/migrations/0009_wonderful_madame_masque.sql +28 -0
  37. package/db/d1/migrations/meta/0000_snapshot.json +1112 -0
  38. package/db/d1/migrations/meta/0001_snapshot.json +1187 -0
  39. package/db/d1/migrations/meta/0002_snapshot.json +1194 -0
  40. package/db/d1/migrations/meta/0003_snapshot.json +1296 -0
  41. package/db/d1/migrations/meta/0004_snapshot.json +1303 -0
  42. package/db/d1/migrations/meta/0005_snapshot.json +1325 -0
  43. package/db/d1/migrations/meta/0006_snapshot.json +1339 -0
  44. package/db/d1/migrations/meta/0007_snapshot.json +1347 -0
  45. package/db/d1/migrations/meta/0008_snapshot.json +1464 -0
  46. package/db/d1/migrations/meta/0009_snapshot.json +1648 -0
  47. package/db/d1/migrations/meta/_journal.json +76 -0
  48. package/db/d1/schema.ts +407 -0
  49. package/db/d1/sites.ts +374 -0
  50. package/db/d1/teamAiUsage.ts +101 -0
  51. package/db/d1/teams.ts +127 -0
  52. package/db/durable/drizzle.config.ts +8 -0
  53. package/db/durable/durableObjectClient.ts +480 -0
  54. package/db/durable/events.ts +100 -0
  55. package/db/durable/migrations/0000_fair_bucky.sql +38 -0
  56. package/db/durable/migrations/meta/0000_snapshot.json +278 -0
  57. package/db/durable/migrations/meta/_journal.json +13 -0
  58. package/db/durable/migrations/migrations.js +10 -0
  59. package/db/durable/schema.ts +5 -0
  60. package/db/durable/siteDurableObject.ts +1352 -0
  61. package/db/durable/types.ts +53 -0
  62. package/db/postgres/client.ts +13 -0
  63. package/db/postgres/drizzle.config.ts +12 -0
  64. package/db/postgres/migrations/0000_brainy_sprite.sql +116 -0
  65. package/db/postgres/migrations/meta/0000_snapshot.json +681 -0
  66. package/db/postgres/migrations/meta/_journal.json +13 -0
  67. package/db/postgres/schema.ts +145 -0
  68. package/db/postgres/sites.ts +118 -0
  69. package/db/tranformReports.ts +595 -0
  70. package/db/types.ts +55 -0
  71. package/endpoints/api_worker.tsx +1854 -0
  72. package/endpoints/site_do_worker.ts +11 -0
  73. package/index.d.ts +63 -0
  74. package/index.ts +83 -0
  75. package/lib/auth.ts +279 -0
  76. package/lib/geojson/world_countries.json +45307 -0
  77. package/lib/random_name.ts +41 -0
  78. package/lib/sendMail.ts +252 -0
  79. package/package.json +142 -0
  80. package/public/favicon.ico +0 -0
  81. package/public/images/android-chrome-192x192.png +0 -0
  82. package/public/images/android-chrome-512x512.png +0 -0
  83. package/public/images/apple-touch-icon.png +0 -0
  84. package/public/images/favicon-16x16.png +0 -0
  85. package/public/images/favicon-32x32.png +0 -0
  86. package/public/images/lytx_dark_dashboard.png +0 -0
  87. package/public/images/lytx_light_dashboard.png +0 -0
  88. package/public/images/safari-pinned-tab.svg +4 -0
  89. package/public/logo.png +0 -0
  90. package/public/site.webmanifest +26 -0
  91. package/public/sw.js +107 -0
  92. package/src/Document.tsx +86 -0
  93. package/src/api/ai_api.ts +1156 -0
  94. package/src/api/authMiddleware.ts +45 -0
  95. package/src/api/auth_api.ts +465 -0
  96. package/src/api/event_labels_api.ts +193 -0
  97. package/src/api/events_api.ts +210 -0
  98. package/src/api/queueWorker.ts +303 -0
  99. package/src/api/reports_api.ts +278 -0
  100. package/src/api/seed_api.ts +288 -0
  101. package/src/api/sites_api.ts +904 -0
  102. package/src/api/tag_api.ts +458 -0
  103. package/src/api/tag_api_v2.ts +289 -0
  104. package/src/api/team_api.ts +456 -0
  105. package/src/app/Dashboard.tsx +1339 -0
  106. package/src/app/Events.tsx +974 -0
  107. package/src/app/Explore.tsx +312 -0
  108. package/src/app/Layout.tsx +58 -0
  109. package/src/app/Settings.tsx +1302 -0
  110. package/src/app/components/DashboardCard.tsx +118 -0
  111. package/src/app/components/EditableCell.tsx +123 -0
  112. package/src/app/components/EventForm.tsx +93 -0
  113. package/src/app/components/MarketingFooter.tsx +49 -0
  114. package/src/app/components/MarketingNav.tsx +150 -0
  115. package/src/app/components/Nav.tsx +755 -0
  116. package/src/app/components/NewSiteSetup.tsx +298 -0
  117. package/src/app/components/SQLEditor.tsx +740 -0
  118. package/src/app/components/SiteSelector.tsx +126 -0
  119. package/src/app/components/SiteTag.tsx +42 -0
  120. package/src/app/components/SiteTagInstallCard.tsx +241 -0
  121. package/src/app/components/WorldMapCard.tsx +337 -0
  122. package/src/app/components/charts/ChartComponents.tsx +1481 -0
  123. package/src/app/components/charts/EventFunnel.tsx +45 -0
  124. package/src/app/components/charts/EventSummary.tsx +194 -0
  125. package/src/app/components/charts/SankeyFlows.tsx +72 -0
  126. package/src/app/components/marketing/CheckIcon.tsx +16 -0
  127. package/src/app/components/marketing/MarketingLayout.tsx +23 -0
  128. package/src/app/components/marketing/SectionHeading.tsx +35 -0
  129. package/src/app/components/reports/AskAiWorkspace.tsx +371 -0
  130. package/src/app/components/reports/CreateReportStarter.tsx +74 -0
  131. package/src/app/components/reports/DashboardRouteFiltersContext.tsx +14 -0
  132. package/src/app/components/reports/DashboardToolbar.tsx +154 -0
  133. package/src/app/components/reports/DashboardWorkspaceLayout.tsx +63 -0
  134. package/src/app/components/reports/DashboardWorkspaceShell.tsx +118 -0
  135. package/src/app/components/reports/ReportBuilderWorkspace.tsx +76 -0
  136. package/src/app/components/reports/custom/CustomReportBuilderPage.tsx +1667 -0
  137. package/src/app/components/reports/custom/ReportWidgetChart.tsx +297 -0
  138. package/src/app/components/reports/custom/buildWidgetSql.ts +151 -0
  139. package/src/app/components/reports/custom/chartPalettes.ts +18 -0
  140. package/src/app/components/reports/custom/types.ts +50 -0
  141. package/src/app/components/reports/reportBuilderMenuItems.ts +17 -0
  142. package/src/app/components/reports/useDashboardToolbarControls.tsx +235 -0
  143. package/src/app/components/ui/AlertBanner.tsx +101 -0
  144. package/src/app/components/ui/Button.tsx +55 -0
  145. package/src/app/components/ui/Card.tsx +80 -0
  146. package/src/app/components/ui/Input.tsx +72 -0
  147. package/src/app/components/ui/Link.tsx +23 -0
  148. package/src/app/components/ui/ReportBuilderMenu.tsx +246 -0
  149. package/src/app/components/ui/ThemeToggle.tsx +54 -0
  150. package/src/app/constants.ts +6 -0
  151. package/src/app/headers.ts +33 -0
  152. package/src/app/providers/AuthProvider.tsx +189 -0
  153. package/src/app/providers/ClientProviders.tsx +18 -0
  154. package/src/app/providers/QueryProvider.tsx +23 -0
  155. package/src/app/providers/ThemeProvider.tsx +88 -0
  156. package/src/app/utils/chartThemes.ts +146 -0
  157. package/src/app/utils/keybinds.ts +96 -0
  158. package/src/app/utils/media.tsx +24 -0
  159. package/src/client.tsx +114 -0
  160. package/src/config/createLytxAppConfig.ts +252 -0
  161. package/src/config/resourceNames.ts +88 -0
  162. package/src/db/index.ts +67 -0
  163. package/src/index.css +285 -0
  164. package/src/lib/featureFlags.ts +69 -0
  165. package/src/pages/GetStarted.tsx +290 -0
  166. package/src/pages/Home.tsx +268 -0
  167. package/src/pages/Login.tsx +283 -0
  168. package/src/pages/PrivacyPolicy.tsx +120 -0
  169. package/src/pages/Signup.tsx +267 -0
  170. package/src/pages/TermsOfService.tsx +126 -0
  171. package/src/pages/VerifyEmail.tsx +56 -0
  172. package/src/session/durableObject.ts +7 -0
  173. package/src/session/siteSchema.ts +86 -0
  174. package/src/session/types.ts +36 -0
  175. package/src/templates/README.md +80 -0
  176. package/src/templates/cleanFunctions.js +44 -0
  177. package/src/templates/embedFunctions.js +52 -0
  178. package/src/templates/lytx-shared.ts +662 -0
  179. package/src/templates/lytxpixel-core.ts +144 -0
  180. package/src/templates/lytxpixel.ts +267 -0
  181. package/src/templates/lytxpixelBrowser.js +634 -0
  182. package/src/templates/lytxpixelBrowser.mjs +634 -0
  183. package/src/templates/parseData.js +12 -0
  184. package/src/templates/script.ts +31 -0
  185. package/src/templates/template.tsx +50 -0
  186. package/src/templates/test.js +3 -0
  187. package/src/templates/trackWebEvents.ts +177 -0
  188. package/src/templates/vendors/clickcease.ts +8 -0
  189. package/src/templates/vendors/google.ts +174 -0
  190. package/src/templates/vendors/linkedin.ts +23 -0
  191. package/src/templates/vendors/meta.ts +56 -0
  192. package/src/templates/vendors/quantcast.ts +22 -0
  193. package/src/templates/vendors/simplfi.ts +7 -0
  194. package/src/types/app-context.ts +16 -0
  195. package/src/utilities/dashboardParams.ts +188 -0
  196. package/src/utilities/dashboardQueries.ts +537 -0
  197. package/src/utilities/dashboardTransforms.ts +167 -0
  198. package/src/utilities/dataValidation.ts +414 -0
  199. package/src/utilities/detector.ts +73 -0
  200. package/src/utilities/encrypt.ts +103 -0
  201. package/src/utilities/index.ts +13 -0
  202. package/src/utilities/parser.ts +117 -0
  203. package/src/utilities/performanceMonitoring.ts +570 -0
  204. package/src/utilities/route_interuptors.ts +24 -0
  205. package/src/worker.tsx +675 -0
  206. package/tsconfig.json +78 -0
  207. package/types/env.d.ts +16 -0
  208. package/types/rw.d.ts +7 -0
  209. package/types/shims.d.ts +53 -0
  210. package/types/vite.d.ts +19 -0
  211. package/vite/vite-plugin-pixel-bundle.ts +126 -0
  212. package/vite.config.ts +53 -0
  213. package/worker-configuration.d.ts +8401 -0
@@ -0,0 +1,588 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ /**
4
+ * Performance Testing Suite for Durable Objects
5
+ *
6
+ * This script conducts comprehensive performance testing of the new durable object system,
7
+ * measuring dashboard load times, concurrent user capacity, and event ingestion rates.
8
+ *
9
+ * Usage:
10
+ * npx tsx cli/performance-test.ts --dashboard-load
11
+ * npx tsx cli/performance-test.ts --event-ingestion
12
+ * npx tsx cli/performance-test.ts --concurrent-users
13
+ * npx tsx cli/performance-test.ts --all
14
+ */
15
+
16
+ import { performance } from 'perf_hooks';
17
+
18
+ /**
19
+ * Performance test configuration
20
+ */
21
+ interface PerformanceConfig {
22
+ // Dashboard load testing
23
+ dashboardConcurrency: number;
24
+ dashboardIterations: number;
25
+ dashboardTimeout: number;
26
+
27
+ // Event ingestion testing
28
+ eventBatchSizes: number[];
29
+ eventConcurrency: number;
30
+ eventIterations: number;
31
+
32
+ // Concurrent user testing
33
+ maxConcurrentUsers: number;
34
+ userRampUpTime: number;
35
+ testDuration: number;
36
+
37
+ // Target performance metrics
38
+ targets: {
39
+ dashboardLoadTime: number; // ms
40
+ eventIngestionRate: number; // events/second
41
+ concurrentUsers: number;
42
+ errorRate: number; // percentage
43
+ };
44
+ }
45
+
46
+ const DEFAULT_CONFIG: PerformanceConfig = {
47
+ dashboardConcurrency: 10,
48
+ dashboardIterations: 100,
49
+ dashboardTimeout: 5000,
50
+
51
+ eventBatchSizes: [1, 10, 50, 100, 500],
52
+ eventConcurrency: 5,
53
+ eventIterations: 50,
54
+
55
+ maxConcurrentUsers: 100,
56
+ userRampUpTime: 30000, // 30 seconds
57
+ testDuration: 120000, // 2 minutes
58
+
59
+ targets: {
60
+ dashboardLoadTime: 100, // Sub-100ms target
61
+ eventIngestionRate: 1000, // 1000 events/second
62
+ concurrentUsers: 50,
63
+ errorRate: 1 // Less than 1% errors
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Performance test results
69
+ */
70
+ interface TestResults {
71
+ testName: string;
72
+ duration: number;
73
+ iterations: number;
74
+ successCount: number;
75
+ errorCount: number;
76
+ averageTime: number;
77
+ medianTime: number;
78
+ p95Time: number;
79
+ p99Time: number;
80
+ minTime: number;
81
+ maxTime: number;
82
+ throughput: number;
83
+ errorRate: number;
84
+ passed: boolean;
85
+ details?: any;
86
+ }
87
+
88
+ /**
89
+ * Mock HTTP client for testing (replace with actual fetch in real environment)
90
+ */
91
+ class MockHttpClient {
92
+ private baseUrl: string;
93
+ private latencyRange: [number, number];
94
+ private errorRate: number;
95
+
96
+ constructor(baseUrl: string = 'http://localhost:8787', latencyRange: [number, number] = [10, 200], errorRate: number = 0.01) {
97
+ this.baseUrl = baseUrl;
98
+ this.latencyRange = latencyRange;
99
+ this.errorRate = errorRate;
100
+ }
101
+
102
+ async request(path: string, options: RequestInit = {}): Promise<{ status: number; data: any; time: number }> {
103
+ const startTime = performance.now();
104
+
105
+ // Simulate network latency
106
+ const latency = Math.random() * (this.latencyRange[1] - this.latencyRange[0]) + this.latencyRange[0];
107
+ await new Promise(resolve => setTimeout(resolve, latency));
108
+
109
+ // Simulate occasional errors
110
+ if (Math.random() < this.errorRate) {
111
+ throw new Error(`HTTP ${Math.random() > 0.5 ? '500' : '503'} - Simulated error`);
112
+ }
113
+
114
+ const endTime = performance.now();
115
+
116
+ // Mock response based on path
117
+ let mockData = {};
118
+ if (path.includes('/events/stats')) {
119
+ mockData = {
120
+ totalEvents: Math.floor(Math.random() * 10000),
121
+ uniqueVisitors: Math.floor(Math.random() * 1000),
122
+ topEventTypes: [
123
+ { label: 'page_view', value: Math.floor(Math.random() * 5000), percentage: 50 },
124
+ { label: 'click', value: Math.floor(Math.random() * 2000), percentage: 20 }
125
+ ]
126
+ };
127
+ } else if (path.includes('/events/timeseries')) {
128
+ mockData = {
129
+ data: Array.from({ length: 30 }, (_, i) => ({
130
+ date: `2024-01-${i + 1}`,
131
+ count: Math.floor(Math.random() * 1000)
132
+ }))
133
+ };
134
+ } else if (path.includes('/events') && options.method === 'POST') {
135
+ mockData = { success: true, inserted: 100 };
136
+ }
137
+
138
+ return {
139
+ status: 200,
140
+ data: mockData,
141
+ time: endTime - startTime
142
+ };
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Dashboard load performance test
148
+ */
149
+ async function testDashboardLoad(config: PerformanceConfig): Promise<TestResults> {
150
+ console.log('🔄 Running Dashboard Load Performance Test...');
151
+
152
+ const client = new MockHttpClient();
153
+ const results: number[] = [];
154
+ const errors: string[] = [];
155
+ const startTime = performance.now();
156
+
157
+ // Test different dashboard endpoints
158
+ const endpoints = [
159
+ '/api/events/123/stats',
160
+ '/api/events/123/timeseries?granularity=day',
161
+ '/api/events/123/metrics?type=events&limit=10',
162
+ '/api/events/123/metrics?type=countries&limit=20'
163
+ ];
164
+
165
+ const promises = [];
166
+
167
+ for (let i = 0; i < config.dashboardIterations; i++) {
168
+ const promise = (async () => {
169
+ try {
170
+ const endpoint = endpoints[i % endpoints.length];
171
+ const result = await client.request(endpoint);
172
+ results.push(result.time);
173
+ } catch (error) {
174
+ errors.push(error instanceof Error ? error.message : String(error));
175
+ }
176
+ })();
177
+
178
+ promises.push(promise);
179
+
180
+ // Control concurrency
181
+ if (promises.length >= config.dashboardConcurrency) {
182
+ await Promise.all(promises);
183
+ promises.length = 0;
184
+ }
185
+ }
186
+
187
+ // Wait for remaining promises
188
+ if (promises.length > 0) {
189
+ await Promise.all(promises);
190
+ }
191
+
192
+ const endTime = performance.now();
193
+ const duration = endTime - startTime;
194
+
195
+ // Calculate statistics
196
+ results.sort((a, b) => a - b);
197
+ const successCount = results.length;
198
+ const errorCount = errors.length;
199
+ const averageTime = results.reduce((sum, time) => sum + time, 0) / results.length || 0;
200
+ const medianTime = results[Math.floor(results.length / 2)] || 0;
201
+ const p95Time = results[Math.floor(results.length * 0.95)] || 0;
202
+ const p99Time = results[Math.floor(results.length * 0.99)] || 0;
203
+ const minTime = results[0] || 0;
204
+ const maxTime = results[results.length - 1] || 0;
205
+ const throughput = (successCount / duration) * 1000; // requests per second
206
+ const errorRate = (errorCount / (successCount + errorCount)) * 100;
207
+
208
+ const passed = averageTime <= config.targets.dashboardLoadTime && errorRate <= config.targets.errorRate;
209
+
210
+ return {
211
+ testName: 'Dashboard Load Performance',
212
+ duration,
213
+ iterations: config.dashboardIterations,
214
+ successCount,
215
+ errorCount,
216
+ averageTime,
217
+ medianTime,
218
+ p95Time,
219
+ p99Time,
220
+ minTime,
221
+ maxTime,
222
+ throughput,
223
+ errorRate,
224
+ passed,
225
+ details: {
226
+ target: `${config.targets.dashboardLoadTime}ms`,
227
+ endpoints: endpoints.length,
228
+ concurrency: config.dashboardConcurrency
229
+ }
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Event ingestion performance test
235
+ */
236
+ async function testEventIngestion(config: PerformanceConfig): Promise<TestResults> {
237
+ console.log('🔄 Running Event Ingestion Performance Test...');
238
+
239
+ const client = new MockHttpClient();
240
+ const results: number[] = [];
241
+ const errors: string[] = [];
242
+ const startTime = performance.now();
243
+ let totalEventsProcessed = 0;
244
+
245
+ const promises = [];
246
+
247
+ for (const batchSize of config.eventBatchSizes) {
248
+ console.log(` Testing batch size: ${batchSize} events`);
249
+
250
+ for (let i = 0; i < config.eventIterations; i++) {
251
+ const promise = (async () => {
252
+ try {
253
+ // Generate mock event batch
254
+ const events = Array.from({ length: batchSize }, (_, index) => ({
255
+ event: 'page_view',
256
+ tag_id: 'test-tag-123',
257
+ browser: 'Chrome',
258
+ country: 'US',
259
+ device_type: 'desktop',
260
+ createdAt: new Date().toISOString()
261
+ }));
262
+
263
+ const result = await client.request('/api/events/123', {
264
+ method: 'POST',
265
+ headers: { 'Content-Type': 'application/json' },
266
+ body: JSON.stringify(events)
267
+ });
268
+
269
+ results.push(result.time);
270
+ totalEventsProcessed += batchSize;
271
+ } catch (error) {
272
+ errors.push(error instanceof Error ? error.message : String(error));
273
+ }
274
+ })();
275
+
276
+ promises.push(promise);
277
+
278
+ // Control concurrency
279
+ if (promises.length >= config.eventConcurrency) {
280
+ await Promise.all(promises);
281
+ promises.length = 0;
282
+ }
283
+ }
284
+ }
285
+
286
+ // Wait for remaining promises
287
+ if (promises.length > 0) {
288
+ await Promise.all(promises);
289
+ }
290
+
291
+ const endTime = performance.now();
292
+ const duration = endTime - startTime;
293
+
294
+ // Calculate statistics
295
+ results.sort((a, b) => a - b);
296
+ const successCount = results.length;
297
+ const errorCount = errors.length;
298
+ const averageTime = results.reduce((sum, time) => sum + time, 0) / results.length || 0;
299
+ const medianTime = results[Math.floor(results.length / 2)] || 0;
300
+ const p95Time = results[Math.floor(results.length * 0.95)] || 0;
301
+ const p99Time = results[Math.floor(results.length * 0.99)] || 0;
302
+ const minTime = results[0] || 0;
303
+ const maxTime = results[results.length - 1] || 0;
304
+ const throughput = (totalEventsProcessed / duration) * 1000; // events per second
305
+ const errorRate = (errorCount / (successCount + errorCount)) * 100;
306
+
307
+ const passed = throughput >= config.targets.eventIngestionRate && errorRate <= config.targets.errorRate;
308
+
309
+ return {
310
+ testName: 'Event Ingestion Performance',
311
+ duration,
312
+ iterations: successCount + errorCount,
313
+ successCount,
314
+ errorCount,
315
+ averageTime,
316
+ medianTime,
317
+ p95Time,
318
+ p99Time,
319
+ minTime,
320
+ maxTime,
321
+ throughput,
322
+ errorRate,
323
+ passed,
324
+ details: {
325
+ target: `${config.targets.eventIngestionRate} events/sec`,
326
+ totalEventsProcessed,
327
+ batchSizes: config.eventBatchSizes,
328
+ concurrency: config.eventConcurrency
329
+ }
330
+ };
331
+ }
332
+
333
+ /**
334
+ * Concurrent users performance test
335
+ */
336
+ async function testConcurrentUsers(config: PerformanceConfig): Promise<TestResults> {
337
+ console.log('🔄 Running Concurrent Users Performance Test...');
338
+
339
+ const client = new MockHttpClient();
340
+ const results: number[] = [];
341
+ const errors: string[] = [];
342
+ const startTime = performance.now();
343
+
344
+ // Simulate gradual user ramp-up
345
+ const userPromises: Promise<void>[] = [];
346
+ const rampUpInterval = config.userRampUpTime / config.maxConcurrentUsers;
347
+
348
+ for (let userId = 0; userId < config.maxConcurrentUsers; userId++) {
349
+ const userPromise = (async () => {
350
+ // Wait for ramp-up time
351
+ await new Promise(resolve => setTimeout(resolve, userId * rampUpInterval));
352
+
353
+ const userStartTime = performance.now();
354
+ const userEndTime = userStartTime + config.testDuration;
355
+
356
+ // Simulate user activity
357
+ while (performance.now() < userEndTime) {
358
+ try {
359
+ // Random dashboard activity
360
+ const endpoints = [
361
+ '/api/events/123/stats',
362
+ '/api/events/123/timeseries?granularity=day',
363
+ '/api/events/123/metrics?type=events'
364
+ ];
365
+
366
+ const endpoint = endpoints[Math.floor(Math.random() * endpoints.length)];
367
+ const result = await client.request(endpoint);
368
+ results.push(result.time);
369
+
370
+ // Wait between requests (simulate user think time)
371
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 2000 + 1000));
372
+ } catch (error) {
373
+ errors.push(error instanceof Error ? error.message : String(error));
374
+ }
375
+ }
376
+ })();
377
+
378
+ userPromises.push(userPromise);
379
+ }
380
+
381
+ // Wait for all users to complete
382
+ await Promise.all(userPromises);
383
+
384
+ const endTime = performance.now();
385
+ const duration = endTime - startTime;
386
+
387
+ // Calculate statistics
388
+ results.sort((a, b) => a - b);
389
+ const successCount = results.length;
390
+ const errorCount = errors.length;
391
+ const averageTime = results.reduce((sum, time) => sum + time, 0) / results.length || 0;
392
+ const medianTime = results[Math.floor(results.length / 2)] || 0;
393
+ const p95Time = results[Math.floor(results.length * 0.95)] || 0;
394
+ const p99Time = results[Math.floor(results.length * 0.99)] || 0;
395
+ const minTime = results[0] || 0;
396
+ const maxTime = results[results.length - 1] || 0;
397
+ const throughput = (successCount / duration) * 1000; // requests per second
398
+ const errorRate = (errorCount / (successCount + errorCount)) * 100;
399
+
400
+ const passed = config.maxConcurrentUsers >= config.targets.concurrentUsers && errorRate <= config.targets.errorRate;
401
+
402
+ return {
403
+ testName: 'Concurrent Users Performance',
404
+ duration,
405
+ iterations: successCount + errorCount,
406
+ successCount,
407
+ errorCount,
408
+ averageTime,
409
+ medianTime,
410
+ p95Time,
411
+ p99Time,
412
+ minTime,
413
+ maxTime,
414
+ throughput,
415
+ errorRate,
416
+ passed,
417
+ details: {
418
+ target: `${config.targets.concurrentUsers} concurrent users`,
419
+ maxConcurrentUsers: config.maxConcurrentUsers,
420
+ rampUpTime: config.userRampUpTime,
421
+ testDuration: config.testDuration
422
+ }
423
+ };
424
+ }
425
+
426
+ /**
427
+ * Generate performance report
428
+ */
429
+ function generateReport(results: TestResults[]): string {
430
+ const lines: string[] = [];
431
+
432
+ lines.push('🚀 Performance Test Report');
433
+ lines.push('=' .repeat(50));
434
+ lines.push('');
435
+
436
+ let allPassed = true;
437
+
438
+ for (const result of results) {
439
+ const status = result.passed ? '✅ PASSED' : '❌ FAILED';
440
+ allPassed = allPassed && result.passed;
441
+
442
+ lines.push(`${status} ${result.testName}`);
443
+ lines.push(`Duration: ${(result.duration / 1000).toFixed(2)}s`);
444
+ lines.push(`Iterations: ${result.iterations}`);
445
+ lines.push(`Success Rate: ${((result.successCount / result.iterations) * 100).toFixed(1)}%`);
446
+ lines.push(`Error Rate: ${result.errorRate.toFixed(2)}%`);
447
+ lines.push(`Average Time: ${result.averageTime.toFixed(2)}ms`);
448
+ lines.push(`Median Time: ${result.medianTime.toFixed(2)}ms`);
449
+ lines.push(`95th Percentile: ${result.p95Time.toFixed(2)}ms`);
450
+ lines.push(`99th Percentile: ${result.p99Time.toFixed(2)}ms`);
451
+ lines.push(`Throughput: ${result.throughput.toFixed(2)} req/sec`);
452
+
453
+ if (result.details) {
454
+ lines.push(`Target: ${result.details.target}`);
455
+ if (result.details.totalEventsProcessed) {
456
+ lines.push(`Events Processed: ${result.details.totalEventsProcessed}`);
457
+ }
458
+ }
459
+
460
+ lines.push('');
461
+ }
462
+
463
+ lines.push('=' .repeat(50));
464
+ lines.push(`Overall Status: ${allPassed ? '✅ ALL TESTS PASSED' : '❌ SOME TESTS FAILED'}`);
465
+ lines.push('');
466
+
467
+ // Performance recommendations
468
+ lines.push('📊 Performance Analysis:');
469
+ lines.push('');
470
+
471
+ const dashboardTest = results.find(r => r.testName.includes('Dashboard'));
472
+ if (dashboardTest) {
473
+ if (dashboardTest.averageTime <= 100) {
474
+ lines.push('✅ Dashboard load times meet sub-100ms target');
475
+ } else {
476
+ lines.push('⚠️ Dashboard load times exceed target - consider caching optimizations');
477
+ }
478
+ }
479
+
480
+ const ingestionTest = results.find(r => r.testName.includes('Ingestion'));
481
+ if (ingestionTest) {
482
+ if (ingestionTest.throughput >= 1000) {
483
+ lines.push('✅ Event ingestion meets 1000+ events/sec target');
484
+ } else {
485
+ lines.push('⚠️ Event ingestion below target - consider batch size optimization');
486
+ }
487
+ }
488
+
489
+ const concurrentTest = results.find(r => r.testName.includes('Concurrent'));
490
+ if (concurrentTest) {
491
+ if (concurrentTest.passed) {
492
+ lines.push('✅ Concurrent user capacity meets requirements');
493
+ } else {
494
+ lines.push('⚠️ Concurrent user capacity needs improvement - check resource limits');
495
+ }
496
+ }
497
+
498
+ return lines.join('\n');
499
+ }
500
+
501
+ /**
502
+ * Main performance testing function
503
+ */
504
+ async function main() {
505
+ const args = process.argv.slice(2);
506
+
507
+ let runDashboard = false;
508
+ let runIngestion = false;
509
+ let runConcurrent = false;
510
+
511
+ // Parse command line arguments
512
+ for (const arg of args) {
513
+ if (arg === '--dashboard-load') runDashboard = true;
514
+ else if (arg === '--event-ingestion') runIngestion = true;
515
+ else if (arg === '--concurrent-users') runConcurrent = true;
516
+ else if (arg === '--all') {
517
+ runDashboard = runIngestion = runConcurrent = true;
518
+ } else if (arg === '--help') {
519
+ console.log(`
520
+ Performance Testing Suite
521
+
522
+ Usage:
523
+ npx tsx cli/performance-test.ts --dashboard-load # Test dashboard load performance
524
+ npx tsx cli/performance-test.ts --event-ingestion # Test event ingestion performance
525
+ npx tsx cli/performance-test.ts --concurrent-users # Test concurrent user capacity
526
+ npx tsx cli/performance-test.ts --all # Run all performance tests
527
+ npx tsx cli/performance-test.ts --help # Show this help message
528
+
529
+ Target Metrics:
530
+ - Dashboard Load Time: < 100ms average
531
+ - Event Ingestion Rate: > 1000 events/second
532
+ - Concurrent Users: 50+ simultaneous users
533
+ - Error Rate: < 1%
534
+ `);
535
+ process.exit(0);
536
+ }
537
+ }
538
+
539
+ if (!runDashboard && !runIngestion && !runConcurrent) {
540
+ console.error('Error: Must specify at least one test type. Use --help for usage information.');
541
+ process.exit(1);
542
+ }
543
+
544
+ console.log('🚀 Starting Performance Test Suite...');
545
+ console.log('');
546
+
547
+ const config = DEFAULT_CONFIG;
548
+ const results: TestResults[] = [];
549
+
550
+ try {
551
+ if (runDashboard) {
552
+ const dashboardResult = await testDashboardLoad(config);
553
+ results.push(dashboardResult);
554
+ }
555
+
556
+ if (runIngestion) {
557
+ const ingestionResult = await testEventIngestion(config);
558
+ results.push(ingestionResult);
559
+ }
560
+
561
+ if (runConcurrent) {
562
+ const concurrentResult = await testConcurrentUsers(config);
563
+ results.push(concurrentResult);
564
+ }
565
+
566
+ // Generate and display report
567
+ const report = generateReport(results);
568
+ console.log('\n' + report);
569
+
570
+ // Exit with appropriate code
571
+ const allPassed = results.every(r => r.passed);
572
+ process.exit(allPassed ? 0 : 1);
573
+
574
+ } catch (error) {
575
+ console.error('Fatal error during performance testing:', error);
576
+ process.exit(1);
577
+ }
578
+ }
579
+
580
+ // Run the performance tests
581
+ if (require.main === module) {
582
+ main().catch(error => {
583
+ console.error('Unhandled error:', error);
584
+ process.exit(1);
585
+ });
586
+ }
587
+
588
+ export { testDashboardLoad, testEventIngestion, testConcurrentUsers, generateReport };
@@ -0,0 +1,4 @@
1
+ import { drizzle } from 'drizzle-orm/postgres-js';
2
+ export const pg_client = (connextionString = process.env.DATABASE_URL) => {
3
+ return drizzle(connextionString, { logger: true });
4
+ }