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,284 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { execSync } from "node:child_process";
4
+ import { randomBytes, scrypt } from "node:crypto";
5
+ import { unlinkSync, writeFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+
8
+ import { createId } from "@paralleldrive/cuid2";
9
+ import { randomName } from "@lib/random_name";
10
+
11
+ type QueryRow = Record<string, unknown>;
12
+
13
+ const args = process.argv.slice(2);
14
+
15
+ const hasFlag = (flag: string) => args.includes(flag);
16
+
17
+ const getOptionalArg = (flag: string, shortFlag?: string): string | undefined => {
18
+ const index = args.indexOf(flag);
19
+ if (index !== -1) {
20
+ const value = args[index + 1];
21
+ if (value && !value.startsWith("-")) return value;
22
+ return undefined;
23
+ }
24
+
25
+ if (!shortFlag) return undefined;
26
+ const shortIndex = args.indexOf(shortFlag);
27
+ if (shortIndex !== -1) {
28
+ const value = args[shortIndex + 1];
29
+ if (value && !value.startsWith("-")) return value;
30
+ }
31
+ return undefined;
32
+ };
33
+
34
+ const getRequiredArg = (flag: string, shortFlag?: string): string => {
35
+ const value = getOptionalArg(flag, shortFlag);
36
+ if (!value) {
37
+ throw new Error(`Missing required argument: ${flag}${shortFlag ? ` / ${shortFlag}` : ""}`);
38
+ }
39
+ return value;
40
+ };
41
+
42
+ if (hasFlag("--help") || hasFlag("-h")) {
43
+ console.log(`
44
+ Usage: bun run cli/bootstrap-admin.ts [options]
45
+
46
+ Options:
47
+ -e, --email <email> Admin email (required)
48
+ -p, --password <password> Password (required only when user does not exist)
49
+ -n, --name <name> Name for new user (default: "Admin User")
50
+ -d, --database <name> D1 database name (default: "lytx_core_db")
51
+ --local Use local D1 (default)
52
+ --remote Use remote D1
53
+ --reset-password Reset credential password if user already exists
54
+ -h, --help Show this help
55
+
56
+ Examples:
57
+ bun run cli/bootstrap-admin.ts --email admin@example.com --password "MyStrongPass123"
58
+ bun run cli/bootstrap-admin.ts --email admin@example.com --remote --reset-password --password "NewPass123"
59
+ `);
60
+ process.exit(0);
61
+ }
62
+
63
+ const email = getRequiredArg("--email", "-e").trim().toLowerCase();
64
+ const password = getOptionalArg("--password", "-p");
65
+ const displayName = getOptionalArg("--name", "-n") ?? "Admin User";
66
+ const database = getOptionalArg("--database", "-d") ?? "lytx_core_db";
67
+ const resetPassword = hasFlag("--reset-password");
68
+ const isRemote = hasFlag("--remote");
69
+ const isLocal = hasFlag("--local") || !isRemote;
70
+
71
+ function escapeSql(value: string): string {
72
+ return value.replace(/'/g, "''");
73
+ }
74
+
75
+ function parseResults(raw: string): QueryRow[] {
76
+ const parsed = JSON.parse(raw);
77
+ if (Array.isArray(parsed) && parsed[0] && Array.isArray(parsed[0].results)) {
78
+ return parsed[0].results as QueryRow[];
79
+ }
80
+ throw new Error("Unexpected wrangler JSON response shape");
81
+ }
82
+
83
+ function runWrangler(sql: string, asJson: boolean): string {
84
+ const tempFile = join(process.cwd(), `temp_bootstrap_${Date.now()}_${Math.random().toString(16).slice(2)}.sql`);
85
+ writeFileSync(tempFile, sql);
86
+
87
+ try {
88
+ const modeFlag = isLocal ? "--local" : "--remote";
89
+ const jsonFlag = asJson ? "--json" : "";
90
+ const command = `bunx wrangler d1 execute ${database} --file ${tempFile} ${modeFlag} ${jsonFlag} --yes`;
91
+ return execSync(command, { encoding: "utf8", stdio: "pipe" });
92
+ } finally {
93
+ try {
94
+ unlinkSync(tempFile);
95
+ } catch {
96
+ // ignore cleanup errors
97
+ }
98
+ }
99
+ }
100
+
101
+ function execute(sql: string, description: string): void {
102
+ console.log(`- ${description}`);
103
+ runWrangler(sql, false);
104
+ }
105
+
106
+ function query(sql: string, description: string): QueryRow[] {
107
+ console.log(`- ${description}`);
108
+ const raw = runWrangler(sql, true);
109
+ return parseResults(raw);
110
+ }
111
+
112
+ async function hashPassword(rawPassword: string): Promise<string> {
113
+ const normalized = rawPassword.normalize("NFKC");
114
+ const salt = randomBytes(16).toString("hex");
115
+
116
+ return await new Promise((resolve, reject) => {
117
+ scrypt(
118
+ normalized,
119
+ salt,
120
+ 64,
121
+ {
122
+ N: 16384,
123
+ r: 16,
124
+ p: 1,
125
+ maxmem: 128 * 16384 * 16 * 2,
126
+ },
127
+ (error, derivedKey) => {
128
+ if (error) {
129
+ reject(error);
130
+ return;
131
+ }
132
+ resolve(`${salt}:${derivedKey.toString("hex")}`);
133
+ },
134
+ );
135
+ });
136
+ }
137
+
138
+ async function main() {
139
+ console.log(`\nBootstrapping admin on ${database} (${isLocal ? "local" : "remote"})`);
140
+
141
+ const now = Math.floor(Date.now() / 1000);
142
+ const escapedEmail = escapeSql(email);
143
+
144
+ const existingUsers = query(
145
+ `SELECT id FROM user WHERE lower(email) = lower('${escapedEmail}') LIMIT 1;`,
146
+ "Checking for existing user",
147
+ );
148
+
149
+ let userId = String(existingUsers[0]?.id ?? "");
150
+
151
+ if (!userId) {
152
+ if (!password) {
153
+ throw new Error("--password is required when creating a new admin user");
154
+ }
155
+
156
+ userId = createId();
157
+ const accountId = createId();
158
+ const hashedPassword = await hashPassword(password);
159
+ const escapedName = escapeSql(displayName);
160
+
161
+ execute(
162
+ `
163
+ INSERT INTO user (id, name, email, email_verified, created_at, updated_at)
164
+ VALUES ('${userId}', '${escapedName}', '${escapedEmail}', 1, ${now}, ${now});
165
+ `,
166
+ "Creating user",
167
+ );
168
+
169
+ execute(
170
+ `
171
+ INSERT INTO account (id, account_id, provider_id, user_id, password, created_at, updated_at)
172
+ VALUES ('${accountId}', '${userId}', 'credential', '${userId}', '${escapeSql(hashedPassword)}', ${now}, ${now});
173
+ `,
174
+ "Creating credential account",
175
+ );
176
+ } else if (resetPassword) {
177
+ if (!password) {
178
+ throw new Error("--password is required when using --reset-password");
179
+ }
180
+
181
+ const hashedPassword = await hashPassword(password);
182
+ const existingCredential = query(
183
+ `
184
+ SELECT id FROM account
185
+ WHERE user_id = '${escapeSql(userId)}' AND provider_id = 'credential'
186
+ LIMIT 1;
187
+ `,
188
+ "Checking credential account",
189
+ );
190
+
191
+ if (existingCredential[0]?.id) {
192
+ execute(
193
+ `
194
+ UPDATE account
195
+ SET password = '${escapeSql(hashedPassword)}', updated_at = ${now}
196
+ WHERE id = '${escapeSql(String(existingCredential[0].id))}';
197
+ `,
198
+ "Resetting existing credential password",
199
+ );
200
+ } else {
201
+ execute(
202
+ `
203
+ INSERT INTO account (id, account_id, provider_id, user_id, password, created_at, updated_at)
204
+ VALUES ('${createId()}', '${escapeSql(userId)}', 'credential', '${escapeSql(userId)}', '${escapeSql(hashedPassword)}', ${now}, ${now});
205
+ `,
206
+ "Creating credential account for existing user",
207
+ );
208
+ }
209
+ }
210
+
211
+ const primaryTeam = query(
212
+ "SELECT id FROM team ORDER BY id ASC LIMIT 1;",
213
+ "Finding primary team",
214
+ );
215
+
216
+ let teamId = Number(primaryTeam[0]?.id ?? 0);
217
+
218
+ if (!teamId) {
219
+ const teamUuid = createId();
220
+ execute(
221
+ `
222
+ INSERT INTO team (created_by, name, uuid, db_adapter, created_at, updated_at)
223
+ VALUES ('${escapeSql(userId)}', '${escapeSql(randomName())}', '${teamUuid}', 'sqlite', ${now}, ${now});
224
+ `,
225
+ "Creating bootstrap team",
226
+ );
227
+
228
+ const createdTeam = query(
229
+ `SELECT id FROM team WHERE uuid = '${teamUuid}' LIMIT 1;`,
230
+ "Resolving bootstrap team id",
231
+ );
232
+ teamId = Number(createdTeam[0]?.id ?? 0);
233
+ if (!teamId) {
234
+ throw new Error("Failed to resolve bootstrap team id");
235
+ }
236
+ }
237
+
238
+ const existingMembership = query(
239
+ `
240
+ SELECT id, role FROM team_member
241
+ WHERE team_id = ${teamId} AND user_id = '${escapeSql(userId)}'
242
+ LIMIT 1;
243
+ `,
244
+ "Checking existing team membership",
245
+ );
246
+
247
+ if (existingMembership[0]?.id) {
248
+ execute(
249
+ `
250
+ UPDATE team_member
251
+ SET role = 'admin', allowed_site_ids = '["all"]', updated_at = ${now}
252
+ WHERE id = ${Number(existingMembership[0].id)};
253
+ `,
254
+ "Promoting existing member to admin",
255
+ );
256
+ } else {
257
+ execute(
258
+ `
259
+ INSERT INTO team_member (team_id, user_id, role, allowed_site_ids, created_at, updated_at)
260
+ VALUES (${teamId}, '${escapeSql(userId)}', 'admin', '["all"]', ${now}, ${now});
261
+ `,
262
+ "Adding admin membership",
263
+ );
264
+ }
265
+
266
+ execute(
267
+ `
268
+ UPDATE user
269
+ SET last_team_id = ${teamId}, updated_at = ${now}
270
+ WHERE id = '${escapeSql(userId)}';
271
+ `,
272
+ "Updating user team preference",
273
+ );
274
+
275
+ console.log("\nDone.");
276
+ console.log(`- Admin email: ${email}`);
277
+ console.log(`- Team id: ${teamId}`);
278
+ console.log("- Role: admin");
279
+ }
280
+
281
+ main().catch((error) => {
282
+ console.error("\nBootstrap admin failed:", error instanceof Error ? error.message : error);
283
+ process.exit(1);
284
+ });