lytx 0.3.11 → 0.3.15

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.
package/README.md CHANGED
@@ -9,6 +9,7 @@ The supported public API surface for `lytx` is documented in `core/docs/oss-cont
9
9
  - Contract doc: [`docs/oss-contract.md`](./docs/oss-contract.md)
10
10
  - Self-host quickstart: [`docs/self-host-quickstart.md`](./docs/self-host-quickstart.md)
11
11
  - Semver/release policy: [`docs/release-policy.md`](./docs/release-policy.md)
12
+ - Changelog: [`docs/changelog.md`](./docs/changelog.md)
12
13
  - Upgrade/migration guide: [`docs/migration-guide.md`](./docs/migration-guide.md)
13
14
  - Read this first before relying on any non-root or deep import path.
14
15
 
@@ -62,6 +63,34 @@ export { SyncDurableObject, SiteDurableObject };
62
63
  export default app satisfies ExportedHandler<Env>;
63
64
  ```
64
65
 
66
+ Add the Vite plugin preset so Redwood can resolve Lytx internals without manual alias setup:
67
+
68
+ ```ts
69
+ // vite.config.ts
70
+ import { defineConfig } from "vite";
71
+ import alchemy from "alchemy/cloudflare/redwood";
72
+ import tailwindcss from "@tailwindcss/vite";
73
+ import { lytxConsumerVitePlugin } from "lytx/vite";
74
+
75
+ export default defineConfig({
76
+ plugins: [...lytxConsumerVitePlugin(), alchemy(), tailwindcss()],
77
+ });
78
+ ```
79
+
80
+ `lytxConsumerVitePlugin()` uses built-in `lytx` defaults for both document and client entry, so consumers do not need local `src/Document.tsx` or `src/client.tsx`. To customize the document wrapper, use `createLytxApp({ routes: { document } })`.
81
+
82
+ For custom document wrappers, import the public stylesheet entrypoint:
83
+
84
+ ```tsx
85
+ import styles from "lytx/styles.css?url";
86
+ ```
87
+
88
+ When providing a custom document, render `{children}` directly (do not wrap it in another `hydrate-root` container):
89
+
90
+ ```tsx
91
+ {children}
92
+ ```
93
+
65
94
  `createLytxApp` supports:
66
95
 
67
96
  - `features.dashboard`, `features.events`, `features.auth`, `features.ai`, `features.tagScript`
@@ -72,7 +101,7 @@ export default app satisfies ExportedHandler<Env>;
72
101
  - `trackingRoutePrefix` (prefix all tracking routes, e.g. `/collect`)
73
102
  - `tagRoutes.scriptPath` + `tagRoutes.eventPath` (custom v2 route paths)
74
103
  - `auth.emailPasswordEnabled`, `auth.requireEmailVerification`, `auth.socialProviders.google`, `auth.socialProviders.github`
75
- - `auth.signupMode` (`"open" | "bootstrap_then_invite" | "invite_only"`; default is `"bootstrap_then_invite"`)
104
+ - `auth.signupMode` (`"open" | "bootstrap_then_invite" | "invite_only" | "demo"`; default is `"bootstrap_then_invite"`)
76
105
  - `ai.provider`, `ai.model`, `ai.baseURL`, `ai.apiKey`, `ai.accountId` (runtime AI vendor/model overrides; blank values are ignored; provider/model include preset autocomplete values)
77
106
  - `features.reportBuilderEnabled` + `features.askAiEnabled`
78
107
  - `names.*` (typed resource binding names for D1/KV/Queue/DO)
@@ -80,6 +109,72 @@ export default app satisfies ExportedHandler<Env>;
80
109
  - `startupValidation.*` + `env.*` (startup env requirement checks with field-level errors)
81
110
  - `env.AI_PROVIDER`, `env.AI_BASE_URL`, `env.AI_MODEL` (AI vendor/model routing overrides)
82
111
  - `env.EMAIL_FROM` (optional factory override for outgoing email sender)
112
+ - `routes.ui.dashboard`, `routes.ui.events`, `routes.ui.explore` (typed per-route UI overrides with route-specific `info`/props)
113
+ - `routes.document` (typed RedwoodSDK `Document` override for render wrapper)
114
+ - `routes.additionalRoutes` (typed RedwoodSDK route entries appended to core route tree)
115
+
116
+ ### Route UI overrides
117
+
118
+ Use `routes.ui` when you want to keep core routing/middleware but swap page UI for specific routes:
119
+
120
+ ```tsx
121
+ import {
122
+ DashboardPage,
123
+ EventsPage,
124
+ ExplorePage,
125
+ createLytxApp,
126
+ type DashboardPageProps,
127
+ } from "lytx";
128
+ import { route, type DocumentProps } from "rwsdk/router";
129
+
130
+ function CustomDocument({ children }: DocumentProps) {
131
+ return (
132
+ <html lang="en">
133
+ <body data-app="custom-document">{children}</body>
134
+ </html>
135
+ );
136
+ }
137
+
138
+ const app = createLytxApp({
139
+ routes: {
140
+ document: CustomDocument,
141
+ additionalRoutes: [
142
+ route("/dashboard/custom", ({ ctx }) => {
143
+ return new Response(`Hello team ${ctx.team.id}`);
144
+ }),
145
+ ],
146
+ ui: {
147
+ dashboard: ({ info, defaultProps, helpers }) => {
148
+ // Keep calling the same APIs/helpers core expects for this route.
149
+ // See /api docs before replacing route UI behavior.
150
+ const _teamId = info.ctx.team.id;
151
+ const _dashboardFetcher = helpers.getDashboardDataCore;
152
+ const _initialData = defaultProps.reportData.initialDashboardData;
153
+
154
+ const { reportData, ...pageProps } = defaultProps;
155
+
156
+ const customProps: DashboardPageProps = {
157
+ ...pageProps,
158
+ ...reportData,
159
+ activeReportBuilderItemId: "create-report",
160
+ };
161
+
162
+ return <DashboardPage {...customProps} />;
163
+ },
164
+ events: ({ info }) => {
165
+ const _userId = info.ctx.session.user.id;
166
+ return <EventsPage />;
167
+ },
168
+ explore: ({ defaultProps }) => {
169
+ return <ExplorePage {...defaultProps} />;
170
+ },
171
+ },
172
+ },
173
+ });
174
+ ```
175
+
176
+ TypeScript autocomplete is route-specific. For example, `routes.ui.dashboard` exposes dashboard defaults/helpers, while `routes.ui.explore` only exposes explore defaults.
177
+ `routes.additionalRoutes` enforces RedwoodSDK route entry types.
83
178
 
84
179
  For deployment scripts, use `resolveLytxResourceNames(...)` from `lytx/resource-names` to derive deterministic Cloudflare resource names with optional stage-based prefix/suffix strategy.
85
180
 
@@ -130,7 +225,6 @@ import {
130
225
  userApiRoutes,
131
226
  eventLabelsApi,
132
227
  reportsApi,
133
- legacyContainerRoute,
134
228
  newSiteSetup,
135
229
  lytxTag,
136
230
  trackWebEvent,
@@ -168,7 +262,6 @@ const app = defineApp<AppRequestInfo>([
168
262
  },
169
263
 
170
264
  // ── Tag & event ingestion (unauthenticated) ──
171
- legacyContainerRoute,
172
265
  lytxTag(dbAdapter),
173
266
  trackWebEvent(dbAdapter, "/trackWebEvent", { useQueue: true }),
174
267
  eventsApi,
@@ -371,10 +464,13 @@ createLytxApp({
371
464
  signupMode: "bootstrap_then_invite",
372
465
  // signupMode: "invite_only", // never allow public signup
373
466
  // signupMode: "open", // always allow public signup
467
+ // signupMode: "demo", // DISABLES auth and makes dashboard/app routes publicly accessible
374
468
  },
375
469
  });
376
470
  ```
377
471
 
472
+ `"demo"` mode is intentionally unsafe for production. It bypasses login/session requirements for dashboard and app routes so anyone with the URL can access the product experience.
473
+
378
474
  If you need to bootstrap an admin user without public signup, use the CLI:
379
475
 
380
476
  ```bash
package/cli/seed-data.ts CHANGED
@@ -28,6 +28,7 @@ Requires the dev server to be running (bun run dev).
28
28
  Options:
29
29
  -t, --team-id <id> Team ID to create sites for (required)
30
30
  -s, --sites <number> Number of sites to create (default: 3)
31
+ --site-names <csv> Comma-separated site names (used in order when creating sites)
31
32
  --site-id <id> Populate existing site ID with events (skips site creation)
32
33
  -e, --events <number> Number of events per site (default: 100)
33
34
  --days <number> Number of days back to generate events (default: 30)
@@ -37,6 +38,7 @@ Options:
37
38
 
38
39
  Example:
39
40
  bun run cli/seed-data.ts --team-id 1 --sites 2 --events 50 --seed-secret "$SEED_DATA_SECRET"
41
+ bun run cli/seed-data.ts --team-id 1 --sites 2 --site-names "Site1,Site2" --events 50 --seed-secret "$SEED_DATA_SECRET"
40
42
  bun run cli/seed-data.ts --team-id 1 --site-id 3 --events 100 --seed-secret "$SEED_DATA_SECRET"
41
43
  `);
42
44
  process.exit(0);
@@ -91,11 +93,26 @@ const getSiteIdArg = () => {
91
93
  }
92
94
  };
93
95
 
96
+ const getSiteNamesArg = () => {
97
+ try {
98
+ const value = getArg("--site-names");
99
+ const parsed = value
100
+ .split(",")
101
+ .map((name) => name.trim())
102
+ .filter((name) => name.length > 0);
103
+
104
+ return parsed.length > 0 ? parsed : null;
105
+ } catch {
106
+ return null;
107
+ }
108
+ };
109
+
94
110
  const teamId = getTeamIdArg();
95
111
  const numSites = getSitesArg();
96
112
  const numEvents = getEventsArg();
97
113
  const numDays = getDaysArg();
98
114
  const siteId = getSiteIdArg();
115
+ const customSiteNames = getSiteNamesArg();
99
116
  const devUrl = hasFlag("--dev-url") ? getArg("--dev-url") : "http://localhost:6123";
100
117
  const seedSecret = hasFlag("--seed-secret") ? getArg("--seed-secret") : "";
101
118
 
@@ -156,6 +173,13 @@ const sampleReferrers = [
156
173
  "https://medium.com",
157
174
  "https://dev.to",
158
175
  "https://hackernews.com",
176
+ "https://chatgpt.com",
177
+ "https://chat.openai.com",
178
+ "https://claude.ai",
179
+ "https://gemini.google.com",
180
+ "https://perplexity.ai",
181
+ "https://copilot.microsoft.com",
182
+ "https://poe.com",
159
183
  "",
160
184
  "direct",
161
185
  ];
@@ -180,15 +204,129 @@ const sampleDeviceTypes = ["desktop", "mobile", "tablet"];
180
204
 
181
205
  const sampleGeo = [
182
206
  { country: "US", region: "California", city: "San Francisco" },
207
+ { country: "US", region: "Virginia", city: "Ashburn" },
208
+ { country: "US", region: "Oregon", city: "Portland" },
183
209
  { country: "US", region: "Texas", city: "Austin" },
210
+ { country: "US", region: "Washington", city: "Seattle" },
184
211
  { country: "US", region: "New York", city: "New York" },
185
- { country: "GB", region: "London", city: "London" },
212
+ { country: "US", region: "North Carolina", city: "Charlotte" },
213
+ { country: "US", region: "Illinois", city: "Chicago" },
214
+ { country: "US", region: "Florida", city: "Miami" },
215
+ { country: "US", region: "Ohio", city: "Columbus" },
216
+ { country: "US", region: "New Jersey", city: "Newark" },
217
+ { country: "US", region: "Minnesota", city: "Minneapolis" },
218
+ { country: "US", region: "Michigan", city: "Detroit" },
219
+ { country: "US", region: "Maryland", city: "Baltimore" },
220
+ { country: "US", region: "Iowa", city: "Des Moines" },
186
221
  { country: "CA", region: "Ontario", city: "Toronto" },
187
- { country: "DE", region: "Bavaria", city: "Munich" },
222
+ { country: "CA", region: "Quebec", city: "Montreal" },
223
+ { country: "CA", region: "British Columbia", city: "Vancouver" },
224
+ { country: "GB", region: "England", city: "London" },
225
+ { country: "DE", region: "Saxony", city: "Dresden" },
226
+ { country: "DE", region: "Hesse", city: "Frankfurt" },
227
+ { country: "DE", region: "Lower Saxony", city: "Hanover" },
228
+ { country: "DE", region: "State of Berlin", city: "Berlin" },
188
229
  { country: "FR", region: "Ile-de-France", city: "Paris" },
189
- { country: "AU", region: "New South Wales", city: "Sydney" },
230
+ { country: "FR", region: "Brittany", city: "Rennes" },
231
+ { country: "FR", region: "Grand Est", city: "Strasbourg" },
232
+ { country: "NL", region: "North Holland", city: "Amsterdam" },
233
+ { country: "NL", region: "Overijssel", city: "Zwolle" },
234
+ { country: "BE", region: "Brussels Capital", city: "Brussels" },
235
+ { country: "PL", region: "Mazovia", city: "Warsaw" },
236
+ { country: "CH", region: "Ticino", city: "Lugano" },
237
+ { country: "SE", region: "Stockholm", city: "Stockholm" },
238
+ { country: "FI", region: "Uusimaa", city: "Helsinki" },
239
+ { country: "DK", region: "Capital Region", city: "Copenhagen" },
240
+ { country: "PT", region: "Lisbon", city: "Lisbon" },
241
+ { country: "IT", region: "Lombardy", city: "Milan" },
242
+ { country: "IT", region: "Sicily", city: "Palermo" },
243
+ { country: "IE", region: "Leinster", city: "Dublin" },
244
+ { country: "ES", region: "Madrid", city: "Madrid" },
245
+ { country: "CN", region: "Beijing", city: "Beijing" },
246
+ { country: "CN", region: "Shanghai", city: "Shanghai" },
247
+ { country: "CN", region: "Hebei", city: "Shijiazhuang" },
248
+ { country: "CN", region: "Fujian", city: "Fuzhou" },
249
+ { country: "CN", region: "Tianjin", city: "Tianjin" },
250
+ { country: "CN", region: "Xinjiang", city: "Urumqi" },
251
+ { country: "IN", region: "Telangana", city: "Hyderabad" },
252
+ { country: "IN", region: "Punjab", city: "Ludhiana" },
253
+ { country: "IN", region: "Maharashtra", city: "Mumbai" },
254
+ { country: "IN", region: "Tamil Nadu", city: "Chennai" },
255
+ { country: "IN", region: "Madhya Pradesh", city: "Bhopal" },
256
+ { country: "IN", region: "Uttarakhand", city: "Dehradun" },
257
+ { country: "VN", region: "Vinh Long", city: "Vinh Long" },
258
+ { country: "VN", region: "Thanh Hoa Province", city: "Thanh Hoa" },
259
+ { country: "VN", region: "Ninh Binh", city: "Ninh Binh" },
260
+ { country: "VN", region: "Quang Tri", city: "Dong Ha" },
261
+ { country: "BD", region: "Chittagong", city: "Chittagong" },
262
+ { country: "BD", region: "Sylhet Division", city: "Sylhet" },
263
+ { country: "BD", region: "Rajshahi Division", city: "Rajshahi" },
264
+ { country: "KR", region: "Seoul", city: "Seoul" },
265
+ { country: "KR", region: "Gyeonggi-do", city: "Suwon" },
190
266
  { country: "JP", region: "Tokyo", city: "Tokyo" },
267
+ { country: "TW", region: "Taiwan", city: "Taipei" },
268
+ { country: "SG", region: "Singapore", city: "Singapore" },
269
+ { country: "MY", region: "Selangor", city: "Shah Alam" },
270
+ { country: "ID", region: "North Sumatra", city: "Medan" },
271
+ { country: "PH", region: "Metro Manila", city: "Manila" },
272
+ { country: "TH", region: "Bangkok", city: "Bangkok" },
273
+ { country: "AU", region: "New South Wales", city: "Sydney" },
274
+ { country: "AU", region: "Victoria", city: "Melbourne" },
275
+ { country: "NZ", region: "Auckland", city: "Auckland" },
276
+ { country: "AQ", region: "Ross Dependency", city: "McMurdo Station" },
191
277
  { country: "BR", region: "Sao Paulo", city: "Sao Paulo" },
278
+ { country: "BR", region: "Rio de Janeiro", city: "Rio de Janeiro" },
279
+ { country: "BR", region: "Rio Grande do Sul", city: "Porto Alegre" },
280
+ { country: "BR", region: "Minas Gerais", city: "Belo Horizonte" },
281
+ { country: "BR", region: "Goias", city: "Goiania" },
282
+ { country: "BR", region: "Paraiba", city: "Joao Pessoa" },
283
+ { country: "MX", region: "Nuevo Leon", city: "Monterrey" },
284
+ { country: "MX", region: "Puebla", city: "Puebla" },
285
+ { country: "MX", region: "Queretaro", city: "Queretaro" },
286
+ { country: "MX", region: "Veracruz", city: "Veracruz" },
287
+ { country: "MX", region: "Mexico", city: "Mexico City" },
288
+ { country: "CO", region: "Bogota D.C.", city: "Bogota" },
289
+ { country: "AR", region: "Buenos Aires F.D.", city: "Buenos Aires" },
290
+ { country: "EC", region: "Pichincha", city: "Quito" },
291
+ { country: "PA", region: "Panama", city: "Panama City" },
292
+ { country: "JM", region: "Kingston", city: "Kingston" },
293
+ { country: "TT", region: "Port of Spain", city: "Port of Spain" },
294
+ { country: "DO", region: "Distrito Nacional", city: "Santo Domingo" },
295
+ { country: "PR", region: "San Juan", city: "San Juan" },
296
+ { country: "BS", region: "New Providence", city: "Nassau" },
297
+ { country: "BB", region: "Saint Michael", city: "Bridgetown" },
298
+ { country: "CU", region: "La Habana", city: "Havana" },
299
+ { country: "VE", region: "Zulia", city: "Maracaibo" },
300
+ { country: "ZA", region: "Western Cape", city: "Cape Town" },
301
+ { country: "ZA", region: "Gauteng", city: "Johannesburg" },
302
+ { country: "ZA", region: "KwaZulu-Natal", city: "Durban" },
303
+ { country: "SA", region: "Riyadh Region", city: "Riyadh" },
304
+ { country: "SA", region: "Mecca Region", city: "Jeddah" },
305
+ { country: "IL", region: "Tel Aviv", city: "Tel Aviv" },
306
+ { country: "JO", region: "Amman", city: "Amman" },
307
+ { country: "TR", region: "Istanbul", city: "Istanbul" },
308
+ { country: "TN", region: "Tunis Governorate", city: "Tunis" },
309
+ { country: "DZ", region: "Oran", city: "Oran" },
310
+ { country: "DZ", region: "M'Sila", city: "M'Sila" },
311
+ { country: "IQ", region: "Nineveh", city: "Mosul" },
312
+ { country: "LB", region: "Liban-Nord", city: "Tripoli" },
313
+ { country: "KG", region: "Bishkek", city: "Bishkek" },
314
+ { country: "UZ", region: "Tashkent", city: "Tashkent" },
315
+ { country: "KE", region: "Meru County", city: "Meru" },
316
+ { country: "KE", region: "Nairobi County", city: "Nairobi" },
317
+ { country: "KE", region: "Mombasa County", city: "Mombasa" },
318
+ { country: "NG", region: "Lagos", city: "Lagos" },
319
+ { country: "NG", region: "Federal Capital Territory", city: "Abuja" },
320
+ { country: "NG", region: "Rivers", city: "Port Harcourt" },
321
+ { country: "GH", region: "Greater Accra", city: "Accra" },
322
+ { country: "GH", region: "Ashanti", city: "Kumasi" },
323
+ { country: "AE", region: "Dubai", city: "Dubai" },
324
+ { country: "RO", region: "Bucharest", city: "Bucharest" },
325
+ { country: "LT", region: "Vilnius", city: "Vilnius" },
326
+ { country: "AZ", region: "Baku", city: "Baku" },
327
+ { country: "AL", region: "Vlore County", city: "Vlore" },
328
+ { country: "GT", region: "Guatemala", city: "Guatemala City" },
329
+ { country: "EG", region: "Cairo", city: "Cairo" },
192
330
  ];
193
331
 
194
332
  const sampleEvents = ["page_view", "form_fill", "phone_call"];
@@ -342,6 +480,9 @@ async function seedData() {
342
480
  console.log(`📈 Events to generate: ${numEvents}`);
343
481
  } else {
344
482
  console.log(`🌐 Sites to create: ${numSites}`);
483
+ if (customSiteNames) {
484
+ console.log(`🏷️ Custom site names: ${customSiteNames.join(", ")}`);
485
+ }
345
486
  console.log(`📈 Events per site: ${numEvents}`);
346
487
  }
347
488
  console.log(`📅 Days back: ${numDays}`);
@@ -367,7 +508,7 @@ async function seedData() {
367
508
  } else {
368
509
  // Create sample sites
369
510
  for (let i = 0; i < numSites; i++) {
370
- const name = sampleSiteNames[i % sampleSiteNames.length];
511
+ const name = customSiteNames?.[i] || sampleSiteNames[i % sampleSiteNames.length];
371
512
  const domain = sampleDomains[i % sampleDomains.length];
372
513
 
373
514
  console.log(`📡 Creating site: ${name}...`);
@@ -455,5 +596,20 @@ if (siteId && numSites !== 3) {
455
596
  console.log("ℹ️ Note: --sites parameter is ignored when using --site-id");
456
597
  }
457
598
 
599
+ if (siteId && customSiteNames) {
600
+ console.log("ℹ️ Note: --site-names parameter is ignored when using --site-id");
601
+ }
602
+
603
+ if (!siteId && customSiteNames && customSiteNames.length < numSites) {
604
+ console.error(
605
+ `❌ Error: --site-names provided ${customSiteNames.length} name(s), but --sites is ${numSites}. Provide at least ${numSites} names.`,
606
+ );
607
+ process.exit(1);
608
+ }
609
+
610
+ if (!siteId && customSiteNames && customSiteNames.length > numSites) {
611
+ console.log(`ℹ️ Note: --site-names provided ${customSiteNames.length} names; only the first ${numSites} will be used.`);
612
+ }
613
+
458
614
  // Run the seeding
459
615
  seedData();
@@ -1,18 +1,107 @@
1
- import { DasboardDataResult, Pagination } from "@db/types";
1
+ export type Pagination = {
2
+ limit: number;
3
+ offset: number;
4
+ total: number;
5
+ hasMore: boolean;
6
+ };
7
+
8
+ export type DashboardEventRow = {
9
+ page_url: string | null;
10
+ client_page_url: string | null;
11
+ referer: string | null;
12
+ event: string | null;
13
+ createdAt: Date | null;
14
+ operating_system: string | null;
15
+ browser: string | null;
16
+ country: string | null;
17
+ region: string | null;
18
+ rid: string | null;
19
+ city: string | null;
20
+ postal: string | null;
21
+ screen_width: number | null;
22
+ screen_height: number | null;
23
+ device_type: string | null;
24
+ };
25
+
26
+ export type DasboardDataResult = DashboardEventRow[];
27
+
28
+ type NivoDatumValue = string | number;
29
+
30
+ type NivoAxisLegendPosition = "start" | "middle" | "end";
31
+
32
+ type NivoLegendAnchor =
33
+ | "top"
34
+ | "top-right"
35
+ | "right"
36
+ | "bottom-right"
37
+ | "bottom"
38
+ | "bottom-left"
39
+ | "left"
40
+ | "top-left"
41
+ | "center";
42
+
43
+ type NivoLegendDirection = "row" | "column";
44
+
45
+ type NivoLegendItemDirection =
46
+ | "left-to-right"
47
+ | "right-to-left"
48
+ | "top-to-bottom"
49
+ | "bottom-to-top";
50
+
51
+ type NivoLegendEffect = {
52
+ on: "hover";
53
+ style: {
54
+ itemBackground?: string;
55
+ itemOpacity?: number;
56
+ };
57
+ };
58
+
59
+ export interface NivoAxisConfig {
60
+ tickSize?: number;
61
+ tickPadding?: number;
62
+ tickRotation?: number;
63
+ truncateTickAt?: number;
64
+ legend?: string;
65
+ legendOffset?: number;
66
+ legendPosition?: NivoAxisLegendPosition;
67
+ }
68
+
69
+ type NivoLegendBase = {
70
+ anchor: NivoLegendAnchor;
71
+ direction: NivoLegendDirection;
72
+ justify: boolean;
73
+ translateX: number;
74
+ translateY: number;
75
+ itemWidth: number;
76
+ itemHeight: number;
77
+ itemTextColor?: string;
78
+ itemDirection: NivoLegendItemDirection;
79
+ itemsSpacing: number;
80
+ itemOpacity: number;
81
+ symbolSize: number;
82
+ symbolShape?: "circle" | "diamond" | "square" | "triangle";
83
+ effects?: NivoLegendEffect[];
84
+ };
85
+
86
+ export type NivoBarLegendConfig = NivoLegendBase & {
87
+ dataFrom: "keys";
88
+ };
89
+
90
+ export type NivoPieLegendConfig = NivoLegendBase;
2
91
 
3
92
  export interface NivoBarChartData {
4
- data: Record<string, any>[];
93
+ data: Array<Record<string, NivoDatumValue>>;
5
94
  keys: string[];
6
95
  indexBy: string;
7
- axisBottom?: any;
8
- axisLeft?: any;
9
- legends?: any[];
96
+ axisBottom?: NivoAxisConfig;
97
+ axisLeft?: NivoAxisConfig;
98
+ legends?: NivoBarLegendConfig[];
10
99
  options: { chart: { type: "bar" } }; // Mandatory type
11
100
  }
12
101
 
13
102
  export interface NivoPieChartData {
14
103
  data: { id: string | number; value: number }[];
15
- legends?: any[];
104
+ legends?: NivoPieLegendConfig[];
16
105
  options: { chart: { type: "pie" } }; // Mandatory type
17
106
  }
18
107
 
@@ -21,10 +110,10 @@ export interface NivoLineChartData {
21
110
  id: string | number;
22
111
  data: { x: string | number; y: string | number }[];
23
112
  }[];
24
- legends?: any[]; // Optional
113
+ legends?: NivoPieLegendConfig[]; // Optional
25
114
  options: { chart: { type: "line" } }; // Mandatory type
26
- axisBottom?: any; // Optional, can be customized per chart
27
- axisLeft?: any; // Optional
115
+ axisBottom?: NivoAxisConfig; // Optional, can be customized per chart
116
+ axisLeft?: NivoAxisConfig; // Optional
28
117
  // Add other line-specific Nivo props if they need to be dynamic from data source
29
118
  }
30
119
 
package/index.d.ts CHANGED
@@ -32,8 +32,7 @@ export { aiChatRoute, aiConfigRoute, aiTagSuggestRoute } from "./src/api/ai_api"
32
32
  export { resendVerificationEmailRoute, userApiRoutes } from "./src/api/auth_api";
33
33
  export { eventLabelsApi } from "./src/api/event_labels_api";
34
34
  export { reportsApi } from "./src/api/reports_api";
35
- export { legacyContainerRoute, newSiteSetup } from "./src/api/tag_api";
36
- export { lytxTag, trackWebEvent } from "./src/api/tag_api_v2";
35
+ export { lytxTag, newSiteSetup, trackWebEvent } from "./src/api/tag_api_v2";
37
36
  export { handleQueueMessage } from "./src/api/queueWorker";
38
37
 
39
38
  export { authMiddleware, sessionMiddleware } from "./src/api/authMiddleware";
@@ -58,6 +57,19 @@ export type {
58
57
  LytxAiModelPreset,
59
58
  LytxAiModel,
60
59
  } from "./src/config/createLytxAppConfig";
60
+ export type {
61
+ LytxAdditionalRoute,
62
+ LytxDocumentComponent,
63
+ LytxDashboardReportData,
64
+ LytxDashboardRouteUiOverrideArgs,
65
+ LytxEventsRouteUiOverrideArgs,
66
+ LytxExploreRouteUiOverrideArgs,
67
+ LytxRouteOverrideResult,
68
+ LytxRouteRequestInfo,
69
+ LytxRouteUiOverrides,
70
+ LytxRoutesConfig,
71
+ LytxToolbarSiteOption,
72
+ } from "./src/config/routeUiOverrides";
61
73
  export function createLytxApp(
62
74
  config?: CreateLytxAppConfig,
63
75
  ): ReturnType<typeof import("./src/worker").createLytxApp>;
package/index.ts CHANGED
@@ -48,8 +48,7 @@ export { aiChatRoute, aiConfigRoute, aiTagSuggestRoute } from "./src/api/ai_api"
48
48
  export { resendVerificationEmailRoute, userApiRoutes } from "./src/api/auth_api";
49
49
  export { eventLabelsApi } from "./src/api/event_labels_api";
50
50
  export { reportsApi } from "./src/api/reports_api";
51
- export { legacyContainerRoute, newSiteSetup } from "./src/api/tag_api";
52
- export { lytxTag, trackWebEvent } from "./src/api/tag_api_v2";
51
+ export { lytxTag, newSiteSetup, trackWebEvent } from "./src/api/tag_api_v2";
53
52
  export { handleQueueMessage } from "./src/api/queueWorker";
54
53
 
55
54
  // ── Middleware ───────────────────────────────────────────────────────────────
@@ -80,6 +79,19 @@ export type {
80
79
  LytxAiModelPreset,
81
80
  LytxAiModel,
82
81
  } from "./src/config/createLytxAppConfig";
82
+ export type {
83
+ LytxAdditionalRoute,
84
+ LytxDocumentComponent,
85
+ LytxDashboardReportData,
86
+ LytxDashboardRouteUiOverrideArgs,
87
+ LytxEventsRouteUiOverrideArgs,
88
+ LytxExploreRouteUiOverrideArgs,
89
+ LytxRouteOverrideResult,
90
+ LytxRouteRequestInfo,
91
+ LytxRouteUiOverrides,
92
+ LytxRoutesConfig,
93
+ LytxToolbarSiteOption,
94
+ } from "./src/config/routeUiOverrides";
83
95
  export { DEFAULT_LYTX_RESOURCE_NAMES, resolveLytxResourceNames } from "./src/config/resourceNames";
84
96
  export type { LytxResourceNames, LytxResourceNamingOptions, LytxResourceStagePosition } from "./src/config/resourceNames";
85
97
 
package/lib/auth.ts CHANGED
@@ -18,7 +18,7 @@ type SocialProviderToggles = {
18
18
  github?: boolean;
19
19
  };
20
20
 
21
- export const SIGNUP_MODES = ["open", "bootstrap_then_invite", "invite_only"] as const;
21
+ export const SIGNUP_MODES = ["open", "bootstrap_then_invite", "invite_only", "demo"] as const;
22
22
  export type SignupMode = (typeof SIGNUP_MODES)[number];
23
23
 
24
24
  export type SignupAccessState = {
@@ -122,7 +122,7 @@ export function getAuthProviderAvailability(): AuthProviderAvailability {
122
122
  };
123
123
  }
124
124
 
125
- function getSignupMode(): SignupMode {
125
+ export function getSignupMode(): SignupMode {
126
126
  return auth_runtime_config.signupMode ?? "bootstrap_then_invite";
127
127
  }
128
128
 
@@ -173,7 +173,7 @@ async function hasPendingInviteForEmail(email: string): Promise<boolean> {
173
173
  export async function getSignupAccessState(): Promise<SignupAccessState> {
174
174
  const mode = getSignupMode();
175
175
 
176
- if (mode === "open") {
176
+ if (mode === "open" || mode === "demo") {
177
177
  return {
178
178
  mode,
179
179
  hasUsers: true,
@@ -207,7 +207,7 @@ export async function isPublicSignupOpen(): Promise<boolean> {
207
207
 
208
208
  export async function canRegisterEmail(email: string): Promise<boolean> {
209
209
  const signupMode = getSignupMode();
210
- if (signupMode === "open") return true;
210
+ if (signupMode === "open" || signupMode === "demo") return true;
211
211
 
212
212
  const invited = await hasPendingInviteForEmail(email);
213
213
  if (invited) return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lytx",
3
- "version": "0.3.11",
3
+ "version": "0.3.15",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -63,6 +63,12 @@
63
63
  "import": "./src/config/resourceNames.ts",
64
64
  "default": "./src/config/resourceNames.ts"
65
65
  },
66
+ "./vite": {
67
+ "types": "./vite/index.ts",
68
+ "import": "./vite/index.ts",
69
+ "default": "./vite/index.ts"
70
+ },
71
+ "./styles.css": "./src/index.css",
66
72
  "./package.json": "./package.json"
67
73
  },
68
74
  "bin": {
@@ -87,7 +93,6 @@
87
93
  "@tailwindcss/vite": "^4.1.8",
88
94
  "@types/better-sqlite3": "^7.6.13",
89
95
  "@types/bun": "^1.2.21",
90
- "@types/mustache": "^4.2.2",
91
96
  "@types/node": "^24.0.0",
92
97
  "@types/react": "^19.1.7",
93
98
  "@types/react-dom": "^19.1.6",
@@ -123,7 +128,6 @@
123
128
  "esbuild": "^0.27.2",
124
129
  "hono": "^4.11.7",
125
130
  "modern-monaco": "^0.3.7",
126
- "mustache": "^4.2.0",
127
131
  "postgres": "^3.4.3",
128
132
  "prism-react-renderer": "^2.4.1",
129
133
  "react": "^19.3.0-canary-4fdf7cf2-20251003",
package/src/Document.tsx CHANGED
@@ -79,7 +79,7 @@ export const Document: React.FC<{ children: React.ReactNode }> = ({
79
79
  <link rel="modulepreload" href="/src/client.tsx" />
80
80
  </head>
81
81
  <body className="overflow-x-hidden">
82
- <div id="root">{children}</div>
82
+ {children}
83
83
  <script>import("/src/client.tsx")</script>
84
84
  </body>
85
85
  </html>
@@ -39,7 +39,7 @@ export async function sessionMiddleware({ request, ctx }: RequestInfo<unknown, A
39
39
  export function getSiteFromContext(ctx: AppContext, site_id: number) {
40
40
  if (!ctx.session) return null;
41
41
  if (!ctx.sites) return null;
42
- return ctx.sites.find(s => s.site_id == site_id);
42
+ return ctx.sites.find((site) => site.site_id == site_id);
43
43
  }
44
44
 
45
45
  export const sessionName = 'lytx_session';