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,76 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1769225895532,
9
+ "tag": "0000_true_maelstrom",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "6",
15
+ "when": 1769286353159,
16
+ "tag": "0001_wonderful_bloodaxe",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "6",
22
+ "when": 1769293072777,
23
+ "tag": "0002_late_frightful_four",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1769738376089,
30
+ "tag": "0003_cuddly_obadiah_stane",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1769979378718,
37
+ "tag": "0004_mute_stardust",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1770005862385,
44
+ "tag": "0005_awesome_silvermane",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "6",
50
+ "when": 1770349249555,
51
+ "tag": "0006_volatile_shriek",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "6",
57
+ "when": 1770361400868,
58
+ "tag": "0007_superb_lila_cheney",
59
+ "breakpoints": true
60
+ },
61
+ {
62
+ "idx": 8,
63
+ "version": "6",
64
+ "when": 1770706668549,
65
+ "tag": "0008_bitter_longshot",
66
+ "breakpoints": true
67
+ },
68
+ {
69
+ "idx": 9,
70
+ "version": "6",
71
+ "when": 1770961353995,
72
+ "tag": "0009_wonderful_madame_masque",
73
+ "breakpoints": true
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,407 @@
1
+ import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
2
+ import { randomName } from "@lib/random_name";
3
+ import { createId } from "@paralleldrive/cuid2";
4
+ import { events } from "@db/postgres/schema";
5
+ const enumRoles = ["viewer", "editor", "admin"] as const;
6
+ export type UserRole = typeof enumRoles[number];
7
+
8
+ export const enumAdapters = [
9
+ "postgres",
10
+ "sqlite",
11
+ "singlestore",
12
+ "analytics_engine",
13
+ ] as const;
14
+
15
+ const defaultThirtyDays = () => {
16
+ const date = new Date();
17
+ date.setDate(date.getDate() + 30); // 30 days from now
18
+ return date;
19
+ };
20
+
21
+ export const user = sqliteTable("user", {
22
+ id: text("id").primaryKey(),
23
+ name: text("name").notNull(),
24
+ email: text("email").notNull().unique(),
25
+ emailVerified: integer("email_verified", { mode: "boolean" })
26
+ .$defaultFn(() => false)
27
+ .notNull(),
28
+ image: text("image"),
29
+ timezone: text("timezone"),
30
+ last_site_id: integer("last_site_id"),
31
+ last_team_id: integer("last_team_id"),
32
+ createdAt: integer("created_at", { mode: "timestamp" })
33
+ .$defaultFn(() => /* @__PURE__ */ new Date())
34
+ .notNull(),
35
+ updatedAt: integer("updated_at", { mode: "timestamp" })
36
+ .$defaultFn(() => /* @__PURE__ */ new Date())
37
+ .notNull()
38
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
39
+ });
40
+
41
+ export const invited_user = sqliteTable(
42
+ "invited_user",
43
+ {
44
+ id: integer("id").primaryKey({ autoIncrement: true }),
45
+ team_id: integer("team_id").notNull(),
46
+ accepted: integer("accepted", { mode: "boolean" })
47
+ .$defaultFn(() => false)
48
+ .notNull(),
49
+ email: text("email").notNull().unique(),
50
+ role: text({ enum: enumRoles }).default("editor").notNull(),
51
+ name: text("name"),
52
+ createdAt: integer("created_at", { mode: "timestamp" })
53
+ .$defaultFn(() => /* @__PURE__ */ new Date())
54
+ .notNull(),
55
+ updatedAt: integer("updated_at", { mode: "timestamp" })
56
+ .$defaultFn(() => /* @__PURE__ */ new Date())
57
+ .notNull()
58
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
59
+ },
60
+ (table) => [
61
+ index("invited_user_team_id_idx").on(table.team_id),
62
+ index("invited_user_email_idx").on(table.email),
63
+ ],
64
+ );
65
+
66
+ export const session = sqliteTable(
67
+ "session",
68
+ {
69
+ id: text("id").primaryKey(),
70
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
71
+ token: text("token").notNull().unique(),
72
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
73
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
74
+ ipAddress: text("ip_address"),
75
+ userAgent: text("user_agent"),
76
+ userId: text("user_id")
77
+ .notNull()
78
+ .references(() => user.id, { onDelete: "cascade" }),
79
+ },
80
+ (table) => [
81
+ index("session_user_id_idx").on(table.userId),
82
+ index("session_token_idx").on(table.token),
83
+ ],
84
+ );
85
+
86
+ export const account = sqliteTable(
87
+ "account",
88
+ {
89
+ id: text("id").primaryKey(),
90
+ accountId: text("account_id").notNull(),
91
+ providerId: text("provider_id").notNull(),
92
+ userId: text("user_id")
93
+ .notNull()
94
+ .references(() => user.id, { onDelete: "cascade" }),
95
+ accessToken: text("access_token"),
96
+ refreshToken: text("refresh_token"),
97
+ idToken: text("id_token"),
98
+ accessTokenExpiresAt: integer("access_token_expires_at", {
99
+ mode: "timestamp",
100
+ }),
101
+ refreshTokenExpiresAt: integer("refresh_token_expires_at", {
102
+ mode: "timestamp",
103
+ }),
104
+ scope: text("scope"),
105
+ password: text("password"),
106
+ createdAt: integer("created_at", { mode: "timestamp" })
107
+ .$defaultFn(() => /* @__PURE__ */ new Date())
108
+ .notNull(),
109
+ updatedAt: integer("updated_at", { mode: "timestamp" })
110
+ .$defaultFn(() => /* @__PURE__ */ new Date())
111
+ .notNull()
112
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
113
+ },
114
+ (table) => [index("account_user_id_idx").on(table.userId)],
115
+ );
116
+
117
+ export const verification = sqliteTable("verification", {
118
+ id: text("id").primaryKey(),
119
+ identifier: text("identifier").notNull(),
120
+ value: text("value").notNull(),
121
+ expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
122
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
123
+ () => /* @__PURE__ */ new Date(),
124
+ ),
125
+ updatedAt: integer("updated_at", { mode: "timestamp" })
126
+ .$defaultFn(() => /* @__PURE__ */ new Date())
127
+ .notNull()
128
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
129
+ });
130
+
131
+ export const team = sqliteTable("team", {
132
+ id: integer("id").primaryKey({ autoIncrement: true }),
133
+ //NOTE: only used for third party integrations
134
+ external_id: integer("external_id").default(0).notNull(),
135
+ // api_key: text('api_key'),
136
+ createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(
137
+ () => /* @__PURE__ */ new Date(),
138
+ ),
139
+ updatedAt: integer("updated_at", { mode: "timestamp" })
140
+ .$defaultFn(() => /* @__PURE__ */ new Date())
141
+ .notNull()
142
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
143
+ /**User who created the team**/
144
+ created_by: text("created_by").notNull(),
145
+ name: text("name").$defaultFn(() => randomName()),
146
+ uuid: text("uuid").$defaultFn(() => createId()),
147
+ db_adapter: text({ enum: enumAdapters }).default("sqlite").notNull(),
148
+ });
149
+
150
+ export type AllowedSiteIds = Array<number | "all">;
151
+
152
+ export const team_member = sqliteTable(
153
+ "team_member",
154
+ {
155
+ id: integer("id").primaryKey({ autoIncrement: true }),
156
+ team_id: integer("team_id").notNull(),
157
+ role: text({ enum: ["viewer", "editor", "admin"] })
158
+ .default("editor")
159
+ .notNull(),
160
+ //Tied to user table
161
+ user_id: text("user_id").notNull(),
162
+ allowed_site_ids: text("allowed_site_ids", { mode: "json" })
163
+ .$type<AllowedSiteIds>()
164
+ .default(["all"]),
165
+ createdAt: integer("created_at", { mode: "timestamp" })
166
+ .$defaultFn(() => /* @__PURE__ */ new Date())
167
+ .notNull(),
168
+ updatedAt: integer("updated_at", { mode: "timestamp" })
169
+ .$defaultFn(() => /* @__PURE__ */ new Date())
170
+ .notNull()
171
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
172
+ },
173
+ (table) => [
174
+ index("team_member_team_id_idx").on(table.team_id),
175
+ index("team_member_user_id_idx").on(table.user_id),
176
+ index("team_member_team_user_idx").on(table.team_id, table.user_id),
177
+ ],
178
+ );
179
+ export type AllowedMembers = Array<string>;
180
+ export type Permissions = {
181
+ read: boolean,
182
+ write: boolean,
183
+ };
184
+ export const api_key = sqliteTable(
185
+ "api_key",
186
+ {
187
+ id: integer("id").primaryKey({ autoIncrement: true }),
188
+ key: text("key").$defaultFn(() => createId()),
189
+ team_id: integer("team_id").notNull(),
190
+ site_id: integer("site_id"),
191
+ enabled: integer("enabled", { mode: "boolean" }).default(true),
192
+ permissions: text("permissions", { mode: "json" }).$type<Permissions>().default({ read: true, write: true }).notNull(),
193
+ allowed_team_members: text("allowed_team_members", { mode: "json" }).$type<AllowedMembers>().default([]),
194
+ createdAt: integer("created_at", { mode: "timestamp" })
195
+ .$defaultFn(() => /* @__PURE__ */ new Date())
196
+ .notNull(),
197
+ updatedAt: integer("updated_at", { mode: "timestamp" })
198
+ .$defaultFn(() => /* @__PURE__ */ new Date())
199
+ .notNull()
200
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
201
+ },
202
+ (table) => [
203
+ index("api_key_team_id_idx").on(table.team_id),
204
+ index("api_key_site_id_idx").on(table.site_id),
205
+ ],
206
+ );
207
+
208
+ export const team_ai_usage = sqliteTable(
209
+ "team_ai_usage",
210
+ {
211
+ id: integer("id").primaryKey({ autoIncrement: true }),
212
+ team_id: integer("team_id").notNull(),
213
+ user_id: text("user_id"),
214
+ site_id: integer("site_id"),
215
+ request_id: text("request_id"),
216
+ request_type: text({ enum: ["chat", "site_tag_suggest"] }).notNull(),
217
+ provider: text("provider"),
218
+ model: text("model"),
219
+ status: text({ enum: ["success", "error"] })
220
+ .default("success")
221
+ .notNull(),
222
+ error_code: text("error_code"),
223
+ error_message: text("error_message"),
224
+ input_tokens: integer("input_tokens"),
225
+ output_tokens: integer("output_tokens"),
226
+ total_tokens: integer("total_tokens"),
227
+ tool_calls: integer("tool_calls"),
228
+ message_count: integer("message_count"),
229
+ prompt_chars: integer("prompt_chars"),
230
+ completion_chars: integer("completion_chars"),
231
+ duration_ms: integer("duration_ms"),
232
+ createdAt: integer("created_at", { mode: "timestamp" })
233
+ .$defaultFn(() => /* @__PURE__ */ new Date())
234
+ .notNull(),
235
+ },
236
+ (table) => [
237
+ index("team_ai_usage_team_id_idx").on(table.team_id),
238
+ index("team_ai_usage_team_created_idx").on(table.team_id, table.createdAt),
239
+ index("team_ai_usage_team_type_created_idx").on(table.team_id, table.request_type, table.createdAt),
240
+ index("team_ai_usage_request_id_idx").on(table.request_id),
241
+ index("team_ai_usage_user_created_idx").on(table.user_id, table.createdAt),
242
+ ],
243
+ );
244
+ export const sites = sqliteTable(
245
+ "sites",
246
+ {
247
+ site_id: integer("site_id").primaryKey({ autoIncrement: true }),
248
+ uuid: text("uuid").$defaultFn(() => createId()).notNull(),
249
+ tag_id: text("tag_id")
250
+ .$defaultFn(() => createId())
251
+ .notNull(),
252
+ track_web_events: integer("track_web_events", { mode: "boolean" })
253
+ .notNull()
254
+ .default(false),
255
+ gdpr: integer("gdpr", { mode: "boolean" }).default(false),
256
+ event_load_strategy: text("event_load_strategy")
257
+ .notNull()
258
+ .default("sdk"),
259
+ //NOTE: only used for third party integrations
260
+ external_id: integer("external_id").default(0).notNull(),
261
+ //NOTE: if this is set this overrides the team overall db adapter
262
+ site_db_adapter: text({ enum: enumAdapters }).default("sqlite").notNull(),
263
+ team_id: integer("team_id").notNull(),
264
+ name: text("name"),
265
+ createdAt: integer("created_at", { mode: "timestamp" })
266
+ .$defaultFn(() => /* @__PURE__ */ new Date())
267
+ .notNull(),
268
+ updatedAt: integer("updated_at", { mode: "timestamp" })
269
+ .$defaultFn(() => /* @__PURE__ */ new Date())
270
+ .notNull()
271
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
272
+ domain: text("domain"),
273
+ //Might not need
274
+ tag_manager: integer("tag_manager", { mode: "boolean" }).default(false),
275
+ // Autocapture: automatically track clicks on links, buttons, and form submissions
276
+ autocapture: integer("autocapture", { mode: "boolean" }).default(true),
277
+ rid_salt: text("rid_salt").$defaultFn(() => createId()),
278
+ rid_salt_expire: integer("rid_salt_expire", {
279
+ mode: "timestamp",
280
+ }).$defaultFn(() => defaultThirtyDays()),
281
+ /**@deprecated**/
282
+ tag_id_override: text("tag_id_override"),
283
+ },
284
+ (table) => [
285
+ index("sites_team_id_idx").on(table.team_id),
286
+ index("sites_tag_id_idx").on(table.tag_id),
287
+ index("sites_domain_idx").on(table.domain),
288
+ ],
289
+ );
290
+
291
+ //CONSIDER: Breaking this up into multiple tables
292
+ export const siteEvents = sqliteTable(
293
+ "site_events",
294
+ {
295
+ id: integer("id").primaryKey({ autoIncrement: true }),
296
+ team_id: integer("team_id"),
297
+ bot_data: text("bot_data", { mode: "json" }).$type<Record<string, string>>(),
298
+ browser: text("browser"),
299
+ city: text("city"),
300
+ client_page_url: text("client_page_url"),
301
+ country: text("country"),
302
+ createdAt: integer("created_at", { mode: "timestamp" })
303
+ .$defaultFn(() => /* @__PURE__ */ new Date())
304
+ .notNull(),
305
+ updatedAt: integer("updated_at", { mode: "timestamp" })
306
+ .$defaultFn(() => /* @__PURE__ */ new Date())
307
+ .notNull()
308
+ .$onUpdateFn(() => /* @__PURE__ */ new Date()),
309
+ custom_data: text("custom_data", { mode: "json" }).$type<Record<string, string>>(),
310
+ device_type: text("device_type"),
311
+ event: text("event").notNull(),
312
+ operating_system: text("operating_system"),
313
+ page_url: text("page_url"),
314
+ postal: text("postal"),
315
+ query_params: text("query_params", { mode: "json" }).$type<Record<string, string>>(),
316
+ referer: text("referer"),
317
+ region: text("region"),
318
+ rid: text("rid"),
319
+ screen_height: integer("screen_height"),
320
+ screen_width: integer("screen_width"),
321
+ site_id: integer("site_id").notNull(),
322
+ tag_id: text("tag_id").notNull(),
323
+ },
324
+ (table) => [
325
+ // Primary query patterns from dashboard API
326
+ index("site_events_team_id_idx").on(table.team_id),
327
+ index("site_events_site_id_idx").on(table.site_id),
328
+ index("site_events_tag_id_idx").on(table.tag_id),
329
+ index("site_events_created_at_idx").on(table.createdAt),
330
+
331
+ // Composite indexes for common query patterns
332
+ index("site_events_team_site_idx").on(table.team_id, table.site_id),
333
+ index("site_events_team_tag_idx").on(table.team_id, table.tag_id),
334
+ index("site_events_site_created_idx").on(table.site_id, table.createdAt),
335
+ index("site_events_team_created_idx").on(table.team_id, table.createdAt),
336
+
337
+ // Analytics filtering indexes
338
+ index("site_events_country_idx").on(table.country),
339
+ index("site_events_device_type_idx").on(table.device_type),
340
+ index("site_events_event_idx").on(table.event),
341
+ index("site_events_referer_idx").on(table.referer),
342
+ ],
343
+ );
344
+
345
+ // Event labels for renaming/labeling autocaptured events
346
+ export const eventLabels = sqliteTable(
347
+ "event_labels",
348
+ {
349
+ id: integer("id").primaryKey({ autoIncrement: true }),
350
+ site_id: integer("site_id").notNull(),
351
+ event_name: text("event_name").notNull(), // Original event name (e.g., "$ac_link_SignUp_btn1")
352
+ label: text("label").notNull(), // User-defined label (e.g., "Sign Up Button")
353
+ description: text("description"), // Optional description
354
+ createdAt: integer("created_at", { mode: "timestamp" })
355
+ .$defaultFn(() => new Date())
356
+ .notNull(),
357
+ updatedAt: integer("updated_at", { mode: "timestamp" })
358
+ .$defaultFn(() => new Date())
359
+ .notNull()
360
+ .$onUpdateFn(() => new Date()),
361
+ },
362
+ (table) => [
363
+ index("event_labels_site_id_idx").on(table.site_id),
364
+ index("event_labels_site_event_idx").on(table.site_id, table.event_name),
365
+ ],
366
+ );
367
+
368
+ export const customReports = sqliteTable(
369
+ "custom_reports",
370
+ {
371
+ id: integer("id").primaryKey({ autoIncrement: true }),
372
+ uuid: text("uuid").notNull().unique(),
373
+ team_id: integer("team_id").notNull(),
374
+ site_id: integer("site_id").notNull(),
375
+ name: text("name").notNull(),
376
+ description: text("description"),
377
+ config: text("config", { mode: "json" })
378
+ .$type<Record<string, unknown>>()
379
+ .notNull(),
380
+ created_by: text("created_by").notNull(),
381
+ createdAt: integer("created_at", { mode: "timestamp" })
382
+ .$defaultFn(() => new Date())
383
+ .notNull(),
384
+ updatedAt: integer("updated_at", { mode: "timestamp" })
385
+ .$defaultFn(() => new Date())
386
+ .notNull()
387
+ .$onUpdateFn(() => new Date()),
388
+ },
389
+ (table) => [
390
+ index("custom_reports_team_id_idx").on(table.team_id),
391
+ index("custom_reports_site_id_idx").on(table.site_id),
392
+ index("custom_reports_team_site_idx").on(table.team_id, table.site_id),
393
+ ],
394
+ );
395
+
396
+ export type EventLabelInsert = typeof eventLabels.$inferInsert;
397
+ export type EventLabelSelect = typeof eventLabels.$inferSelect;
398
+ export type CustomReportInsert = typeof customReports.$inferInsert;
399
+ export type CustomReportSelect = typeof customReports.$inferSelect;
400
+ export type TeamAiUsageInsert = typeof team_ai_usage.$inferInsert;
401
+ export type TeamAiUsageSelect = typeof team_ai_usage.$inferSelect;
402
+
403
+ export type SiteInsert = typeof sites.$inferInsert;
404
+ export type TeamInsert = typeof team.$inferInsert;
405
+ export type ApiKeyInsert = typeof api_key.$inferInsert;
406
+ export type DBAdapter = NonNullable<TeamInsert["db_adapter"]>;
407
+ export type EventSelect = typeof siteEvents.$inferSelect;