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,459 @@
1
+ #!/usr/bin/env bun
2
+ import { createId } from "@paralleldrive/cuid2";
3
+
4
+ // Parse CLI arguments
5
+ const args = process.argv.slice(2);
6
+ const getArg = (flag: string, defaultValue?: string): string => {
7
+ const index = args.indexOf(flag);
8
+ if (index === -1) {
9
+ if (defaultValue !== undefined) return defaultValue;
10
+ throw new Error(`Missing required argument: ${flag}`);
11
+ }
12
+ const value = args[index + 1];
13
+ if (!value || value.startsWith("-")) {
14
+ throw new Error(`Invalid value for ${flag}`);
15
+ }
16
+ return value;
17
+ };
18
+
19
+ const hasFlag = (flag: string): boolean => args.includes(flag);
20
+
21
+ // Check for help flag first
22
+ if (hasFlag("--help") || hasFlag("-h")) {
23
+ console.log(`
24
+ Usage: bun run cli/seed-data.ts [options]
25
+
26
+ Requires the dev server to be running (bun run dev).
27
+
28
+ Options:
29
+ -t, --team-id <id> Team ID to create sites for (required)
30
+ -s, --sites <number> Number of sites to create (default: 3)
31
+ --site-id <id> Populate existing site ID with events (skips site creation)
32
+ -e, --events <number> Number of events per site (default: 100)
33
+ --days <number> Number of days back to generate events (default: 30)
34
+ --dev-url <url> Base URL for dev server (default: "http://localhost:6123")
35
+ --seed-secret <value> SEED_DATA_SECRET value for dev bypass (required)
36
+ -h, --help Show this help message
37
+
38
+ Example:
39
+ bun run cli/seed-data.ts --team-id 1 --sites 2 --events 50 --seed-secret "$SEED_DATA_SECRET"
40
+ bun run cli/seed-data.ts --team-id 1 --site-id 3 --events 100 --seed-secret "$SEED_DATA_SECRET"
41
+ `);
42
+ process.exit(0);
43
+ }
44
+
45
+ // CLI argument parsing
46
+ const getTeamIdArg = () => {
47
+ try {
48
+ return parseInt(getArg("--team-id"));
49
+ } catch {
50
+ return parseInt(getArg("-t"));
51
+ }
52
+ };
53
+
54
+ const getSitesArg = () => {
55
+ try {
56
+ return parseInt(getArg("--sites"));
57
+ } catch {
58
+ try {
59
+ return parseInt(getArg("-s"));
60
+ } catch {
61
+ return 3;
62
+ }
63
+ }
64
+ };
65
+
66
+ const getEventsArg = () => {
67
+ try {
68
+ return parseInt(getArg("--events"));
69
+ } catch {
70
+ try {
71
+ return parseInt(getArg("-e"));
72
+ } catch {
73
+ return 100;
74
+ }
75
+ }
76
+ };
77
+
78
+ const getDaysArg = () => {
79
+ try {
80
+ return parseInt(getArg("--days"));
81
+ } catch {
82
+ return 30;
83
+ }
84
+ };
85
+
86
+ const getSiteIdArg = () => {
87
+ try {
88
+ return parseInt(getArg("--site-id"));
89
+ } catch {
90
+ return null; // Optional parameter
91
+ }
92
+ };
93
+
94
+ const teamId = getTeamIdArg();
95
+ const numSites = getSitesArg();
96
+ const numEvents = getEventsArg();
97
+ const numDays = getDaysArg();
98
+ const siteId = getSiteIdArg();
99
+ const devUrl = hasFlag("--dev-url") ? getArg("--dev-url") : "http://localhost:6123";
100
+ const seedSecret = hasFlag("--seed-secret") ? getArg("--seed-secret") : "";
101
+
102
+ if (!seedSecret) {
103
+ console.error("āŒ Error: --seed-secret is required.");
104
+ process.exit(1);
105
+ }
106
+
107
+ // Sample data arrays
108
+ const sampleDomains = [
109
+ "example.com",
110
+ "mystore.com",
111
+ "techblog.io",
112
+ "portfolio.dev",
113
+ "startup.co",
114
+ "agency.design",
115
+ "ecommerce.shop",
116
+ "news.today",
117
+ ];
118
+
119
+ const sampleSiteNames = [
120
+ "Example Website",
121
+ "My Online Store",
122
+ "Tech Blog",
123
+ "Portfolio Site",
124
+ "Startup Landing",
125
+ "Design Agency",
126
+ "E-commerce Shop",
127
+ "News Portal",
128
+ ];
129
+
130
+ const samplePages = [
131
+ "/",
132
+ "/about",
133
+ "/contact",
134
+ "/products",
135
+ "/services",
136
+ "/blog",
137
+ "/pricing",
138
+ "/features",
139
+ "/team",
140
+ "/careers",
141
+ "/support",
142
+ "/login",
143
+ "/signup",
144
+ "/checkout",
145
+ "/cart",
146
+ ];
147
+
148
+ const sampleReferrers = [
149
+ "https://google.com",
150
+ "https://facebook.com",
151
+ "https://twitter.com",
152
+ "https://linkedin.com",
153
+ "https://reddit.com",
154
+ "https://github.com",
155
+ "https://stackoverflow.com",
156
+ "https://medium.com",
157
+ "https://dev.to",
158
+ "https://hackernews.com",
159
+ "",
160
+ "direct",
161
+ ];
162
+
163
+ const sampleBrowsers = [
164
+ "Chrome 120.0.0",
165
+ "Firefox 121.0.0",
166
+ "Safari 17.2.0",
167
+ "Edge 120.0.0",
168
+ "Opera 105.0.0",
169
+ ];
170
+
171
+ const sampleOS = [
172
+ "Windows 11",
173
+ "macOS 14.2",
174
+ "Ubuntu 22.04",
175
+ "iOS 17.2",
176
+ "Android 14",
177
+ ];
178
+
179
+ const sampleDeviceTypes = ["desktop", "mobile", "tablet"];
180
+
181
+ const sampleGeo = [
182
+ { country: "US", region: "California", city: "San Francisco" },
183
+ { country: "US", region: "Texas", city: "Austin" },
184
+ { country: "US", region: "New York", city: "New York" },
185
+ { country: "GB", region: "London", city: "London" },
186
+ { country: "CA", region: "Ontario", city: "Toronto" },
187
+ { country: "DE", region: "Bavaria", city: "Munich" },
188
+ { country: "FR", region: "Ile-de-France", city: "Paris" },
189
+ { country: "AU", region: "New South Wales", city: "Sydney" },
190
+ { country: "JP", region: "Tokyo", city: "Tokyo" },
191
+ { country: "BR", region: "Sao Paulo", city: "Sao Paulo" },
192
+ ];
193
+
194
+ const sampleEvents = ["page_view", "form_fill", "phone_call"];
195
+
196
+ // Helper function to get random item from array
197
+ function randomItem<T>(array: T[]): T {
198
+ return array[Math.floor(Math.random() * array.length)];
199
+ }
200
+
201
+ // Helper function to generate random timestamp within last N days (returns Unix timestamp in ms)
202
+ function randomTimestamp(daysBack: number): number {
203
+ const now = Date.now();
204
+ const daysInMs = daysBack * 24 * 60 * 60 * 1000;
205
+ return now - Math.floor(Math.random() * daysInMs);
206
+ }
207
+
208
+ function normalizeTimestampMs(timestamp: number): number {
209
+ return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp;
210
+ }
211
+
212
+ // Helper function to generate screen dimensions
213
+ function randomScreenDimensions(): { width: number; height: number } {
214
+ const commonResolutions = [
215
+ { width: 1920, height: 1080 },
216
+ { width: 1366, height: 768 },
217
+ { width: 1440, height: 900 },
218
+ { width: 1536, height: 864 },
219
+ { width: 375, height: 667 }, // iPhone
220
+ { width: 414, height: 896 }, // iPhone
221
+ { width: 768, height: 1024 }, // iPad
222
+ ];
223
+ return randomItem(commonResolutions);
224
+ }
225
+
226
+ // API helper functions
227
+ async function apiRequest(path: string, options: RequestInit = {}) {
228
+ const url = `${devUrl}${path}`;
229
+ const response = await fetch(url, {
230
+ ...options,
231
+ headers: {
232
+ "Content-Type": "application/json",
233
+ "x-seed-secret": seedSecret,
234
+ ...options.headers,
235
+ },
236
+ });
237
+
238
+ if (!response.ok) {
239
+ const text = await response.text();
240
+ throw new Error(`API request failed (${response.status}): ${text}`);
241
+ }
242
+
243
+ return response;
244
+ }
245
+
246
+ async function checkTeamExists(teamId: number): Promise<boolean> {
247
+ try {
248
+ await apiRequest(`/api/seed/team/${teamId}`);
249
+ return true;
250
+ } catch (error: any) {
251
+ if (error.message.includes("404")) {
252
+ return false;
253
+ }
254
+ throw error;
255
+ }
256
+ }
257
+
258
+ type SiteResponse = {
259
+ site_id: number;
260
+ uuid: string;
261
+ tag_id: string;
262
+ name: string | null;
263
+ domain: string | null;
264
+ site_db_adapter: string | null;
265
+ };
266
+
267
+ type SeedEventsResponse = {
268
+ success: boolean;
269
+ inserted?: number;
270
+ error?: string;
271
+ };
272
+
273
+ async function getSiteById(siteId: number, teamId: number): Promise<SiteResponse> {
274
+ const response = await apiRequest(`/api/seed/site/${siteId}?teamId=${teamId}`);
275
+ return response.json() as Promise<SiteResponse>;
276
+ }
277
+
278
+ async function createSite(teamId: number, name: string, domain: string): Promise<SiteResponse> {
279
+ const response = await apiRequest("/api/seed/site", {
280
+ method: "POST",
281
+ body: JSON.stringify({ teamId, name, domain }),
282
+ });
283
+ return response.json() as Promise<SiteResponse>;
284
+ }
285
+
286
+ async function seedEvents(siteId: number, teamId: number, events: Array<Record<string, unknown>>): Promise<SeedEventsResponse> {
287
+ const response = await apiRequest(`/api/seed/events/${siteId}?teamId=${teamId}`, {
288
+ method: "POST",
289
+ body: JSON.stringify(events),
290
+ });
291
+ return response.json() as Promise<SeedEventsResponse>;
292
+ }
293
+
294
+ function buildEventPayload(site: { tag_id: string; domain: string }, timestampSeconds: number) {
295
+ const page = randomItem(samplePages);
296
+ const referrer = randomItem(sampleReferrers);
297
+ const browser = randomItem(sampleBrowsers);
298
+ const os = randomItem(sampleOS);
299
+ const deviceType = randomItem(sampleDeviceTypes);
300
+ const geo = randomItem(sampleGeo);
301
+ const event = randomItem(sampleEvents);
302
+ const { width, height } = randomScreenDimensions();
303
+ const rid = createId();
304
+
305
+ return {
306
+ tag_id: site.tag_id,
307
+ event,
308
+ client_page_url: page,
309
+ page_url: `${site.domain}${page}`,
310
+ referer: referrer,
311
+ browser,
312
+ operating_system: os,
313
+ device_type: deviceType,
314
+ country: geo.country,
315
+ region: geo.region,
316
+ city: geo.city,
317
+ screen_width: width,
318
+ screen_height: height,
319
+ rid,
320
+ createdAt: new Date(timestampSeconds * 1000).toISOString(),
321
+ updatedAt: new Date(timestampSeconds * 1000).toISOString(),
322
+ };
323
+ }
324
+
325
+ async function seedData() {
326
+ try {
327
+ console.log("🌱 Seeding database with sample data...");
328
+ console.log(`🌐 Dev server: ${devUrl}`);
329
+ console.log(`šŸ¢ Team ID: ${teamId}`);
330
+
331
+ // Verify team exists
332
+ console.log("šŸ“‹ Verifying team exists...");
333
+ const teamExists = await checkTeamExists(teamId);
334
+ if (!teamExists) {
335
+ console.error(`āŒ Team ${teamId} not found. Please create the team first using init-db.ts`);
336
+ process.exit(1);
337
+ }
338
+ console.log("āœ… Team verified");
339
+
340
+ if (siteId) {
341
+ console.log(`šŸŽÆ Using existing site ID: ${siteId}`);
342
+ console.log(`šŸ“ˆ Events to generate: ${numEvents}`);
343
+ } else {
344
+ console.log(`🌐 Sites to create: ${numSites}`);
345
+ console.log(`šŸ“ˆ Events per site: ${numEvents}`);
346
+ }
347
+ console.log(`šŸ“… Days back: ${numDays}`);
348
+
349
+ const createdSites: Array<{
350
+ site_id: number;
351
+ tag_id: string;
352
+ name: string;
353
+ domain: string;
354
+ }> = [];
355
+
356
+ if (siteId) {
357
+ // Use existing site - fetch its details
358
+ console.log(`šŸ“” Fetching site ${siteId}...`);
359
+ const site = await getSiteById(siteId, teamId);
360
+ createdSites.push({
361
+ site_id: site.site_id,
362
+ tag_id: site.tag_id,
363
+ name: site.name || "Unknown Site",
364
+ domain: site.domain || "unknown.com",
365
+ });
366
+ console.log(`āœ… Found existing site: ${site.name} (${site.domain})`);
367
+ } else {
368
+ // Create sample sites
369
+ for (let i = 0; i < numSites; i++) {
370
+ const name = sampleSiteNames[i % sampleSiteNames.length];
371
+ const domain = sampleDomains[i % sampleDomains.length];
372
+
373
+ console.log(`šŸ“” Creating site: ${name}...`);
374
+ const site = await createSite(teamId, name, domain);
375
+
376
+ createdSites.push({
377
+ site_id: site.site_id,
378
+ tag_id: site.tag_id,
379
+ name: site.name || name,
380
+ domain: site.domain || domain,
381
+ });
382
+ console.log(`āœ… Created site ID: ${site.site_id} for ${name}`);
383
+ }
384
+ }
385
+
386
+ // Generate sample events for each site
387
+ for (const site of createdSites) {
388
+ console.log(`šŸ“Š Generating ${numEvents} events for ${site.name}...`);
389
+
390
+ // SQLite has a ~999 variable limit per query, each event has ~16 fields
391
+ // So batch size of 25 = ~400 variables, safely under the limit
392
+ const batchSize = 25;
393
+ let eventBatch: Array<Record<string, unknown>> = [];
394
+
395
+ for (let i = 0; i < numEvents; i++) {
396
+ const timestamp = normalizeTimestampMs(randomTimestamp(numDays));
397
+ const timestampSeconds = Math.floor(timestamp / 1000);
398
+
399
+ eventBatch.push(buildEventPayload(site, timestampSeconds));
400
+
401
+ if (eventBatch.length === batchSize || i === numEvents - 1) {
402
+ const result = await seedEvents(site.site_id, teamId, eventBatch);
403
+ console.log(` āœ… Inserted batch of ${eventBatch.length} events (total: ${result.inserted || eventBatch.length})`);
404
+ eventBatch = [];
405
+ }
406
+ }
407
+ }
408
+
409
+ console.log("\nāœ… Data seeding complete!");
410
+ console.log(`
411
+ šŸ“Š Summary:
412
+ Sites: ${createdSites.length}
413
+ Total events generated: ${createdSites.length * numEvents}
414
+ Team ID: ${teamId}
415
+ Dev server: ${devUrl}
416
+ `);
417
+
418
+ console.log(`
419
+ 🌐 Created/seeded sites:
420
+ ${createdSites.map((site) => ` - ${site.name} (${site.domain}) - Tag ID: ${site.tag_id}`).join("\n")}
421
+ `);
422
+
423
+ console.log(`
424
+ šŸš€ Next steps:
425
+ 1. Login with your user credentials
426
+ 2. View the analytics dashboard with sample data
427
+ 3. Test the tracking script on your sites
428
+ `);
429
+ } catch (error) {
430
+ console.error("āŒ Error seeding data:", error);
431
+ process.exit(1);
432
+ }
433
+ }
434
+
435
+ // Validate required arguments
436
+ if (!teamId || isNaN(teamId)) {
437
+ console.error("āŒ Error: --team-id is required and must be a number");
438
+ console.log("Use --help for usage information");
439
+ process.exit(1);
440
+ }
441
+
442
+ if (siteId && (isNaN(siteId) || siteId <= 0)) {
443
+ console.error("āŒ Error: --site-id must be a positive number");
444
+ process.exit(1);
445
+ }
446
+
447
+ if ((!siteId && numSites <= 0) || numEvents <= 0 || numDays <= 0) {
448
+ console.error(
449
+ "āŒ Error: --sites (when not using --site-id), --events, and --days must be positive numbers",
450
+ );
451
+ process.exit(1);
452
+ }
453
+
454
+ if (siteId && numSites !== 3) {
455
+ console.log("ā„¹ļø Note: --sites parameter is ignored when using --site-id");
456
+ }
457
+
458
+ // Run the seeding
459
+ seedData();
package/cli/setup.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { resolve, dirname } from "node:path";
5
+
6
+ const cliPath = resolve(dirname(fileURLToPath(import.meta.url)), "./setup.ts");
7
+ const result = spawnSync("bun", [cliPath, ...process.argv.slice(2)], {
8
+ stdio: "inherit",
9
+ env: process.env,
10
+ });
11
+
12
+ if (result.error) {
13
+ console.error("lytx-setup requires Bun to run: npm i -g bun or install via https://bun.sh");
14
+ console.error(result.error.message);
15
+ process.exit(1);
16
+ }
17
+
18
+ process.exit(result.status ?? 0);