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,300 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Migration Worker Script
4
+ *
5
+ * This script runs inside the Cloudflare Workers environment to perform
6
+ * data migration from D1/Postgres to Durable Objects using the existing
7
+ * infrastructure (writeToDurableObject, insertSiteEvents, etc.)
8
+ *
9
+ * Usage:
10
+ * 1. Deploy this as a temporary worker or run with wrangler dev
11
+ * 2. Call the migration endpoints to trigger migration
12
+ * 3. Remove after migration is complete
13
+ */
14
+
15
+ import { writeToDurableObject } from '@db/durable/durableObjectClient';
16
+ import { drizzle } from 'drizzle-orm/d1';
17
+ import { siteEvents as d1SiteEvents, sites as d1Sites } from '@db/d1/schema';
18
+ import { eq } from 'drizzle-orm';
19
+ import type { SiteEventInput } from '@/session/siteSchema';
20
+
21
+ interface MigrationRequest {
22
+ siteId: number;
23
+ batchSize?: number;
24
+ dryRun?: boolean;
25
+ verify?: boolean;
26
+ }
27
+
28
+ interface MigrationResponse {
29
+ success: boolean;
30
+ siteId: number;
31
+ totalEvents: number;
32
+ migratedEvents: number;
33
+ batches: number;
34
+ errors?: string[];
35
+ dryRun?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Transform D1 events to durable object format
40
+ */
41
+ function transformEventsForDurableObject(events: any[]): SiteEventInput[] {
42
+ return events.map(event => ({
43
+ bot_data: event.bot_data,
44
+ browser: event.browser,
45
+ city: event.city,
46
+ client_page_url: event.client_page_url,
47
+ country: event.country,
48
+ createdAt: event.createdAt ? new Date(event.createdAt) : new Date(),
49
+ updatedAt: event.updatedAt ? new Date(event.updatedAt) : new Date(),
50
+ custom_data: event.custom_data,
51
+ device_type: event.device_type,
52
+ event: event.event,
53
+ operating_system: event.operating_system,
54
+ page_url: event.page_url,
55
+ postal: event.postal,
56
+ query_params: event.query_params,
57
+ referer: event.referer,
58
+ region: event.region,
59
+ rid: event.rid,
60
+ screen_height: event.screen_height,
61
+ screen_width: event.screen_width,
62
+ tag_id: event.tag_id,
63
+ }));
64
+ }
65
+
66
+ /**
67
+ * Migrate a single site's events from D1 to Durable Object
68
+ */
69
+ async function migrateSiteEvents(
70
+ siteId: number,
71
+ env: Env,
72
+ batchSize: number = 50,
73
+ dryRun: boolean = false
74
+ ): Promise<MigrationResponse> {
75
+ const db = drizzle(env.lytx_core_db);
76
+ const errors: string[] = [];
77
+
78
+ try {
79
+ const siteRecord = await db
80
+ .select({ uuid: d1Sites.uuid })
81
+ .from(d1Sites)
82
+ .where(eq(d1Sites.site_id, siteId))
83
+ .limit(1);
84
+
85
+ const siteUuid = siteRecord[0]?.uuid;
86
+ if (!siteUuid) {
87
+ return {
88
+ success: false,
89
+ siteId,
90
+ totalEvents: 0,
91
+ migratedEvents: 0,
92
+ batches: 0,
93
+ errors: [`Site ${siteId} not found`],
94
+ };
95
+ }
96
+
97
+ // Get all events for this site from D1
98
+ const events = await db
99
+ .select()
100
+ .from(d1SiteEvents)
101
+ .where(eq(d1SiteEvents.site_id, siteId))
102
+ .orderBy(d1SiteEvents.createdAt);
103
+
104
+ console.log(`Found ${events.length} events for site ${siteId}`);
105
+
106
+ if (events.length === 0) {
107
+ return {
108
+ success: true,
109
+ siteId,
110
+ totalEvents: 0,
111
+ migratedEvents: 0,
112
+ batches: 0,
113
+ dryRun
114
+ };
115
+ }
116
+
117
+ // Transform events for durable object
118
+ const transformedEvents = transformEventsForDurableObject(events);
119
+
120
+ if (dryRun) {
121
+ return {
122
+ success: true,
123
+ siteId,
124
+ totalEvents: events.length,
125
+ migratedEvents: 0,
126
+ batches: Math.ceil(events.length / batchSize),
127
+ dryRun: true
128
+ };
129
+ }
130
+
131
+ // Process in batches
132
+ let migratedCount = 0;
133
+ const totalBatches = Math.ceil(transformedEvents.length / batchSize);
134
+
135
+ for (let i = 0; i < transformedEvents.length; i += batchSize) {
136
+ const batch = transformedEvents.slice(i, i + batchSize);
137
+ const batchNumber = Math.floor(i / batchSize) + 1;
138
+
139
+ console.log(`Processing batch ${batchNumber}/${totalBatches} (${batch.length} events) for site ${siteId}`);
140
+
141
+ try {
142
+ const result = await writeToDurableObject(siteId, siteUuid, batch);
143
+
144
+ if (result.success) {
145
+ migratedCount += result.inserted || batch.length;
146
+ } else {
147
+ errors.push(`Batch ${batchNumber}: ${result.error}`);
148
+ }
149
+ } catch (error) {
150
+ const errorMsg = `Batch ${batchNumber} failed: ${error instanceof Error ? error.message : String(error)}`;
151
+ errors.push(errorMsg);
152
+ console.error(errorMsg);
153
+ }
154
+ }
155
+
156
+ return {
157
+ success: errors.length === 0,
158
+ siteId,
159
+ totalEvents: events.length,
160
+ migratedEvents: migratedCount,
161
+ batches: totalBatches,
162
+ errors: errors.length > 0 ? errors : undefined
163
+ };
164
+
165
+ } catch (error) {
166
+ const errorMsg = `Migration failed for site ${siteId}: ${error instanceof Error ? error.message : String(error)}`;
167
+ console.error(errorMsg);
168
+
169
+ return {
170
+ success: false,
171
+ siteId,
172
+ totalEvents: 0,
173
+ migratedEvents: 0,
174
+ batches: 0,
175
+ errors: [errorMsg]
176
+ };
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Verify migration by checking durable object has events
182
+ */
183
+ async function verifyMigration(siteId: number, expectedCount: number, env: Env): Promise<{ success: boolean; actualCount: number; expectedCount: number }> {
184
+ try {
185
+ // Get the durable object for this site
186
+ const durableObjectId = env.SITE_DURABLE_OBJECT.idFromName(`Site-${siteId}`);
187
+ const durableObject = env.SITE_DURABLE_OBJECT.get(durableObjectId);
188
+
189
+ // Get health check which includes total events
190
+ const response = await durableObject.fetch('https://durable-object/health');
191
+
192
+ if (!response.ok) {
193
+ throw new Error(`Health check failed: ${response.status}`);
194
+ }
195
+
196
+ const health = await response.json() as { totalEvents: number };
197
+
198
+ return {
199
+ success: health.totalEvents === expectedCount,
200
+ actualCount: health.totalEvents,
201
+ expectedCount
202
+ };
203
+ } catch (error) {
204
+ console.error(`Verification failed for site ${siteId}:`, error);
205
+ return {
206
+ success: false,
207
+ actualCount: -1,
208
+ expectedCount
209
+ };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Worker fetch handler for migration endpoints
215
+ */
216
+ export default {
217
+ async fetch(request: Request, env: Env): Promise<Response> {
218
+ const url = new URL(request.url);
219
+
220
+ // CORS headers for development
221
+ const corsHeaders = {
222
+ 'Access-Control-Allow-Origin': '*',
223
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
224
+ 'Access-Control-Allow-Headers': 'Content-Type',
225
+ };
226
+
227
+ if (request.method === 'OPTIONS') {
228
+ return new Response(null, { headers: corsHeaders });
229
+ }
230
+
231
+ try {
232
+ // POST /migrate-site - Migrate a single site
233
+ if (url.pathname === '/migrate-site' && request.method === 'POST') {
234
+ const body = await request.json() as MigrationRequest;
235
+ const { siteId, batchSize = 50, dryRun = false, verify = false } = body;
236
+
237
+ if (!siteId) {
238
+ return new Response(JSON.stringify({ error: 'siteId is required' }), {
239
+ status: 400,
240
+ headers: { 'Content-Type': 'application/json', ...corsHeaders }
241
+ });
242
+ }
243
+
244
+ // Perform migration
245
+ const migrationResult = await migrateSiteEvents(siteId, env, batchSize, dryRun);
246
+
247
+ // Perform verification if requested and migration was successful
248
+ let verificationResult;
249
+ if (verify && migrationResult.success && !dryRun) {
250
+ verificationResult = await verifyMigration(siteId, migrationResult.totalEvents, env);
251
+ }
252
+
253
+ return new Response(JSON.stringify({
254
+ migration: migrationResult,
255
+ verification: verificationResult
256
+ }), {
257
+ headers: { 'Content-Type': 'application/json', ...corsHeaders }
258
+ });
259
+ }
260
+
261
+ // GET /health - Health check
262
+ if (url.pathname === '/health' && request.method === 'GET') {
263
+ return new Response(JSON.stringify({
264
+ status: 'ok',
265
+ service: 'migration-worker',
266
+ timestamp: new Date().toISOString()
267
+ }), {
268
+ headers: { 'Content-Type': 'application/json', ...corsHeaders }
269
+ });
270
+ }
271
+
272
+ // GET /verify-site/:siteId - Verify a site's migration
273
+ const verifyMatch = url.pathname.match(/^\/verify-site\/(\d+)$/);
274
+ if (verifyMatch && request.method === 'GET') {
275
+ const siteId = parseInt(verifyMatch[1]);
276
+ const expectedCount = parseInt(url.searchParams.get('expectedCount') || '0');
277
+
278
+ const verificationResult = await verifyMigration(siteId, expectedCount, env);
279
+
280
+ return new Response(JSON.stringify(verificationResult), {
281
+ headers: { 'Content-Type': 'application/json', ...corsHeaders }
282
+ });
283
+ }
284
+
285
+ return new Response('Not Found', {
286
+ status: 404,
287
+ headers: corsHeaders
288
+ });
289
+
290
+ } catch (error) {
291
+ console.error('Migration worker error:', error);
292
+ return new Response(JSON.stringify({
293
+ error: error instanceof Error ? error.message : 'Unknown error'
294
+ }), {
295
+ status: 500,
296
+ headers: { 'Content-Type': 'application/json', ...corsHeaders }
297
+ });
298
+ }
299
+ }
300
+ };