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.
- package/.env.example +37 -0
- package/README.md +486 -0
- package/alchemy.run.ts +155 -0
- package/cli/bootstrap-admin.ts +284 -0
- package/cli/deploy-staging.ts +692 -0
- package/cli/import-events.ts +628 -0
- package/cli/import-sites.ts +518 -0
- package/cli/index.ts +609 -0
- package/cli/init-db.ts +269 -0
- package/cli/migrate-to-durable-objects.ts +564 -0
- package/cli/migration-worker.ts +300 -0
- package/cli/performance-test.ts +588 -0
- package/cli/pg/client.ts +4 -0
- package/cli/pg/new-site.ts +153 -0
- package/cli/rollback-durable-objects.ts +622 -0
- package/cli/seed-data.ts +459 -0
- package/cli/setup.js +18 -0
- package/cli/setup.ts +463 -0
- package/cli/validate-migration.ts +200 -0
- package/cli/wrangler-migration.jsonc +28 -0
- package/db/adapter.ts +166 -0
- package/db/analytics_engine/client.ts +0 -0
- package/db/analytics_engine/sites.ts +0 -0
- package/db/client.ts +16 -0
- package/db/d1/client.ts +8 -0
- package/db/d1/drizzle.config.ts +35 -0
- package/db/d1/migrations/0000_true_maelstrom.sql +165 -0
- package/db/d1/migrations/0001_wonderful_bloodaxe.sql +12 -0
- package/db/d1/migrations/0002_late_frightful_four.sql +1 -0
- package/db/d1/migrations/0003_cuddly_obadiah_stane.sql +16 -0
- package/db/d1/migrations/0004_mute_stardust.sql +1 -0
- package/db/d1/migrations/0005_awesome_silvermane.sql +3 -0
- package/db/d1/migrations/0006_volatile_shriek.sql +2 -0
- package/db/d1/migrations/0007_superb_lila_cheney.sql +1 -0
- package/db/d1/migrations/0008_bitter_longshot.sql +17 -0
- package/db/d1/migrations/0009_wonderful_madame_masque.sql +28 -0
- package/db/d1/migrations/meta/0000_snapshot.json +1112 -0
- package/db/d1/migrations/meta/0001_snapshot.json +1187 -0
- package/db/d1/migrations/meta/0002_snapshot.json +1194 -0
- package/db/d1/migrations/meta/0003_snapshot.json +1296 -0
- package/db/d1/migrations/meta/0004_snapshot.json +1303 -0
- package/db/d1/migrations/meta/0005_snapshot.json +1325 -0
- package/db/d1/migrations/meta/0006_snapshot.json +1339 -0
- package/db/d1/migrations/meta/0007_snapshot.json +1347 -0
- package/db/d1/migrations/meta/0008_snapshot.json +1464 -0
- package/db/d1/migrations/meta/0009_snapshot.json +1648 -0
- package/db/d1/migrations/meta/_journal.json +76 -0
- package/db/d1/schema.ts +407 -0
- package/db/d1/sites.ts +374 -0
- package/db/d1/teamAiUsage.ts +101 -0
- package/db/d1/teams.ts +127 -0
- package/db/durable/drizzle.config.ts +8 -0
- package/db/durable/durableObjectClient.ts +480 -0
- package/db/durable/events.ts +100 -0
- package/db/durable/migrations/0000_fair_bucky.sql +38 -0
- package/db/durable/migrations/meta/0000_snapshot.json +278 -0
- package/db/durable/migrations/meta/_journal.json +13 -0
- package/db/durable/migrations/migrations.js +10 -0
- package/db/durable/schema.ts +5 -0
- package/db/durable/siteDurableObject.ts +1352 -0
- package/db/durable/types.ts +53 -0
- package/db/postgres/client.ts +13 -0
- package/db/postgres/drizzle.config.ts +12 -0
- package/db/postgres/migrations/0000_brainy_sprite.sql +116 -0
- package/db/postgres/migrations/meta/0000_snapshot.json +681 -0
- package/db/postgres/migrations/meta/_journal.json +13 -0
- package/db/postgres/schema.ts +145 -0
- package/db/postgres/sites.ts +118 -0
- package/db/tranformReports.ts +595 -0
- package/db/types.ts +55 -0
- package/endpoints/api_worker.tsx +1854 -0
- package/endpoints/site_do_worker.ts +11 -0
- package/index.d.ts +63 -0
- package/index.ts +83 -0
- package/lib/auth.ts +279 -0
- package/lib/geojson/world_countries.json +45307 -0
- package/lib/random_name.ts +41 -0
- package/lib/sendMail.ts +252 -0
- package/package.json +142 -0
- package/public/favicon.ico +0 -0
- package/public/images/android-chrome-192x192.png +0 -0
- package/public/images/android-chrome-512x512.png +0 -0
- package/public/images/apple-touch-icon.png +0 -0
- package/public/images/favicon-16x16.png +0 -0
- package/public/images/favicon-32x32.png +0 -0
- package/public/images/lytx_dark_dashboard.png +0 -0
- package/public/images/lytx_light_dashboard.png +0 -0
- package/public/images/safari-pinned-tab.svg +4 -0
- package/public/logo.png +0 -0
- package/public/site.webmanifest +26 -0
- package/public/sw.js +107 -0
- package/src/Document.tsx +86 -0
- package/src/api/ai_api.ts +1156 -0
- package/src/api/authMiddleware.ts +45 -0
- package/src/api/auth_api.ts +465 -0
- package/src/api/event_labels_api.ts +193 -0
- package/src/api/events_api.ts +210 -0
- package/src/api/queueWorker.ts +303 -0
- package/src/api/reports_api.ts +278 -0
- package/src/api/seed_api.ts +288 -0
- package/src/api/sites_api.ts +904 -0
- package/src/api/tag_api.ts +458 -0
- package/src/api/tag_api_v2.ts +289 -0
- package/src/api/team_api.ts +456 -0
- package/src/app/Dashboard.tsx +1339 -0
- package/src/app/Events.tsx +974 -0
- package/src/app/Explore.tsx +312 -0
- package/src/app/Layout.tsx +58 -0
- package/src/app/Settings.tsx +1302 -0
- package/src/app/components/DashboardCard.tsx +118 -0
- package/src/app/components/EditableCell.tsx +123 -0
- package/src/app/components/EventForm.tsx +93 -0
- package/src/app/components/MarketingFooter.tsx +49 -0
- package/src/app/components/MarketingNav.tsx +150 -0
- package/src/app/components/Nav.tsx +755 -0
- package/src/app/components/NewSiteSetup.tsx +298 -0
- package/src/app/components/SQLEditor.tsx +740 -0
- package/src/app/components/SiteSelector.tsx +126 -0
- package/src/app/components/SiteTag.tsx +42 -0
- package/src/app/components/SiteTagInstallCard.tsx +241 -0
- package/src/app/components/WorldMapCard.tsx +337 -0
- package/src/app/components/charts/ChartComponents.tsx +1481 -0
- package/src/app/components/charts/EventFunnel.tsx +45 -0
- package/src/app/components/charts/EventSummary.tsx +194 -0
- package/src/app/components/charts/SankeyFlows.tsx +72 -0
- package/src/app/components/marketing/CheckIcon.tsx +16 -0
- package/src/app/components/marketing/MarketingLayout.tsx +23 -0
- package/src/app/components/marketing/SectionHeading.tsx +35 -0
- package/src/app/components/reports/AskAiWorkspace.tsx +371 -0
- package/src/app/components/reports/CreateReportStarter.tsx +74 -0
- package/src/app/components/reports/DashboardRouteFiltersContext.tsx +14 -0
- package/src/app/components/reports/DashboardToolbar.tsx +154 -0
- package/src/app/components/reports/DashboardWorkspaceLayout.tsx +63 -0
- package/src/app/components/reports/DashboardWorkspaceShell.tsx +118 -0
- package/src/app/components/reports/ReportBuilderWorkspace.tsx +76 -0
- package/src/app/components/reports/custom/CustomReportBuilderPage.tsx +1667 -0
- package/src/app/components/reports/custom/ReportWidgetChart.tsx +297 -0
- package/src/app/components/reports/custom/buildWidgetSql.ts +151 -0
- package/src/app/components/reports/custom/chartPalettes.ts +18 -0
- package/src/app/components/reports/custom/types.ts +50 -0
- package/src/app/components/reports/reportBuilderMenuItems.ts +17 -0
- package/src/app/components/reports/useDashboardToolbarControls.tsx +235 -0
- package/src/app/components/ui/AlertBanner.tsx +101 -0
- package/src/app/components/ui/Button.tsx +55 -0
- package/src/app/components/ui/Card.tsx +80 -0
- package/src/app/components/ui/Input.tsx +72 -0
- package/src/app/components/ui/Link.tsx +23 -0
- package/src/app/components/ui/ReportBuilderMenu.tsx +246 -0
- package/src/app/components/ui/ThemeToggle.tsx +54 -0
- package/src/app/constants.ts +6 -0
- package/src/app/headers.ts +33 -0
- package/src/app/providers/AuthProvider.tsx +189 -0
- package/src/app/providers/ClientProviders.tsx +18 -0
- package/src/app/providers/QueryProvider.tsx +23 -0
- package/src/app/providers/ThemeProvider.tsx +88 -0
- package/src/app/utils/chartThemes.ts +146 -0
- package/src/app/utils/keybinds.ts +96 -0
- package/src/app/utils/media.tsx +24 -0
- package/src/client.tsx +114 -0
- package/src/config/createLytxAppConfig.ts +252 -0
- package/src/config/resourceNames.ts +88 -0
- package/src/db/index.ts +67 -0
- package/src/index.css +285 -0
- package/src/lib/featureFlags.ts +69 -0
- package/src/pages/GetStarted.tsx +290 -0
- package/src/pages/Home.tsx +268 -0
- package/src/pages/Login.tsx +283 -0
- package/src/pages/PrivacyPolicy.tsx +120 -0
- package/src/pages/Signup.tsx +267 -0
- package/src/pages/TermsOfService.tsx +126 -0
- package/src/pages/VerifyEmail.tsx +56 -0
- package/src/session/durableObject.ts +7 -0
- package/src/session/siteSchema.ts +86 -0
- package/src/session/types.ts +36 -0
- package/src/templates/README.md +80 -0
- package/src/templates/cleanFunctions.js +44 -0
- package/src/templates/embedFunctions.js +52 -0
- package/src/templates/lytx-shared.ts +662 -0
- package/src/templates/lytxpixel-core.ts +144 -0
- package/src/templates/lytxpixel.ts +267 -0
- package/src/templates/lytxpixelBrowser.js +634 -0
- package/src/templates/lytxpixelBrowser.mjs +634 -0
- package/src/templates/parseData.js +12 -0
- package/src/templates/script.ts +31 -0
- package/src/templates/template.tsx +50 -0
- package/src/templates/test.js +3 -0
- package/src/templates/trackWebEvents.ts +177 -0
- package/src/templates/vendors/clickcease.ts +8 -0
- package/src/templates/vendors/google.ts +174 -0
- package/src/templates/vendors/linkedin.ts +23 -0
- package/src/templates/vendors/meta.ts +56 -0
- package/src/templates/vendors/quantcast.ts +22 -0
- package/src/templates/vendors/simplfi.ts +7 -0
- package/src/types/app-context.ts +16 -0
- package/src/utilities/dashboardParams.ts +188 -0
- package/src/utilities/dashboardQueries.ts +537 -0
- package/src/utilities/dashboardTransforms.ts +167 -0
- package/src/utilities/dataValidation.ts +414 -0
- package/src/utilities/detector.ts +73 -0
- package/src/utilities/encrypt.ts +103 -0
- package/src/utilities/index.ts +13 -0
- package/src/utilities/parser.ts +117 -0
- package/src/utilities/performanceMonitoring.ts +570 -0
- package/src/utilities/route_interuptors.ts +24 -0
- package/src/worker.tsx +675 -0
- package/tsconfig.json +78 -0
- package/types/env.d.ts +16 -0
- package/types/rw.d.ts +7 -0
- package/types/shims.d.ts +53 -0
- package/types/vite.d.ts +19 -0
- package/vite/vite-plugin-pixel-bundle.ts +126 -0
- package/vite.config.ts +53 -0
- package/worker-configuration.d.ts +8401 -0
package/.env.example
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ── Lytx Kit – Environment Variables ──
|
|
2
|
+
# Copy this file to .env and fill in your values:
|
|
3
|
+
# cp .env.example .env
|
|
4
|
+
|
|
5
|
+
# Domain that appears in emails and auth callbacks
|
|
6
|
+
LYTX_DOMAIN=localhost:5173
|
|
7
|
+
|
|
8
|
+
# Auth (required)
|
|
9
|
+
BETTER_AUTH_SECRET=
|
|
10
|
+
BETTER_AUTH_URL=http://localhost:5173
|
|
11
|
+
ENCRYPTION_KEY=
|
|
12
|
+
|
|
13
|
+
# Email sender address (used for verification, invites, etc.)
|
|
14
|
+
EMAIL_FROM=noreply@yourdomain.com
|
|
15
|
+
|
|
16
|
+
# Auth providers (optional – fill in to enable)
|
|
17
|
+
GITHUB_CLIENT_ID=
|
|
18
|
+
GITHUB_CLIENT_SECRET=
|
|
19
|
+
GOOGLE_CLIENT_ID=
|
|
20
|
+
GOOGLE_CLIENT_SECRET=
|
|
21
|
+
|
|
22
|
+
# Email via Resend (optional – required for verification/invite emails)
|
|
23
|
+
RESEND_API_KEY=
|
|
24
|
+
|
|
25
|
+
# AI features (optional)
|
|
26
|
+
AI_API_KEY=
|
|
27
|
+
AI_BASE_URL=
|
|
28
|
+
AI_MODEL=
|
|
29
|
+
AI_DAILY_TOKEN_LIMIT=
|
|
30
|
+
|
|
31
|
+
# Report builder feature toggles
|
|
32
|
+
REPORT_BUILDER=false
|
|
33
|
+
ASK_AI=true
|
|
34
|
+
|
|
35
|
+
# Misc
|
|
36
|
+
SEED_DATA_SECRET=
|
|
37
|
+
ENVIRONMENT=development
|
package/README.md
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# Lytx Kit – Core
|
|
2
|
+
|
|
3
|
+
Open-source web analytics platform built on [RedwoodSDK](https://rwsdk.com) (rwsdk) and Cloudflare Workers. Ship a full analytics dashboard — event ingestion, dashboards, team management, auth — inside your own Redwood app.
|
|
4
|
+
|
|
5
|
+
## OSS contract
|
|
6
|
+
|
|
7
|
+
The supported public API surface for `lytx` is documented in `core/docs/oss-contract.md`.
|
|
8
|
+
|
|
9
|
+
- Contract doc: [`docs/oss-contract.md`](./docs/oss-contract.md)
|
|
10
|
+
- Self-host quickstart: [`docs/self-host-quickstart.md`](./docs/self-host-quickstart.md)
|
|
11
|
+
- Semver/release policy: [`docs/release-policy.md`](./docs/release-policy.md)
|
|
12
|
+
- Upgrade/migration guide: [`docs/migration-guide.md`](./docs/migration-guide.md)
|
|
13
|
+
- Read this first before relying on any non-root or deep import path.
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
`lytx` exposes a canonical app factory, `createLytxApp`, from the package root. Use it to bootstrap a full worker without importing internals. For advanced composition, root exports also include route, page, middleware, and Durable Object building blocks.
|
|
18
|
+
|
|
19
|
+
An experimental pre-wired worker entrypoint also exists at `lytx/worker`; this entrypoint is intentionally not part of the stable API contract.
|
|
20
|
+
|
|
21
|
+
Think of it like a parts catalog: pull in the full analytics stack, or cherry-pick just the event ingestion API and build your own UI.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- [Bun](https://bun.sh) (runtime)
|
|
26
|
+
- A Redwood SDK (rwsdk) project — `npx rwsdk@latest new my-app`
|
|
27
|
+
- Cloudflare account (D1, KV, Durable Objects, Queues)
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# from your rwsdk project root
|
|
33
|
+
bun add lytx
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> Until this is published to npm, add it as a workspace dependency or link it locally.
|
|
37
|
+
|
|
38
|
+
## Quick start — app factory (recommended)
|
|
39
|
+
|
|
40
|
+
Use the root app factory to bootstrap the full analytics stack with one import:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// src/worker.tsx
|
|
44
|
+
import type { ExportedHandler } from "cloudflare:workers";
|
|
45
|
+
import { createLytxApp, SyncDurableObject, SiteDurableObject } from "lytx";
|
|
46
|
+
|
|
47
|
+
const app = createLytxApp({
|
|
48
|
+
db: {
|
|
49
|
+
dbAdapter: "sqlite",
|
|
50
|
+
eventStore: "durable_objects",
|
|
51
|
+
},
|
|
52
|
+
auth: {
|
|
53
|
+
socialProviders: {
|
|
54
|
+
google: true,
|
|
55
|
+
github: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export { SyncDurableObject, SiteDurableObject };
|
|
61
|
+
|
|
62
|
+
export default app satisfies ExportedHandler<Env>;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`createLytxApp` supports:
|
|
66
|
+
|
|
67
|
+
- `features.dashboard`, `features.events`, `features.auth`, `features.ai`, `features.tagScript`
|
|
68
|
+
- `db.dbAdapter` (`"sqlite" | "postgres" | "singlestore" | "analytics_engine"`)
|
|
69
|
+
- `db.eventStore` (`db.dbAdapter` values + `"durable_objects"`; defaults to `"durable_objects"`)
|
|
70
|
+
- `useQueueIngestion` (`true`/`false`)
|
|
71
|
+
- `includeLegacyTagRoutes` (`true` by default for `/lytx.js` and `/trackWebEvent` compatibility)
|
|
72
|
+
- `trackingRoutePrefix` (prefix all tracking routes, e.g. `/collect`)
|
|
73
|
+
- `tagRoutes.scriptPath` + `tagRoutes.eventPath` (custom v2 route paths)
|
|
74
|
+
- `auth.emailPasswordEnabled`, `auth.requireEmailVerification`, `auth.socialProviders.google`, `auth.socialProviders.github`
|
|
75
|
+
- `features.reportBuilderEnabled` + `features.askAiEnabled`
|
|
76
|
+
- `names.*` (typed resource binding names for D1/KV/Queue/DO)
|
|
77
|
+
- `domains.app` + `domains.tracking` (typed host/domain values)
|
|
78
|
+
- `startupValidation.*` + `env.*` (startup env requirement checks with field-level errors)
|
|
79
|
+
- `env.EMAIL_FROM` (optional factory override for outgoing email sender)
|
|
80
|
+
|
|
81
|
+
For deployment scripts, use `resolveLytxResourceNames(...)` from `lytx/resource-names` to derive deterministic Cloudflare resource names with optional stage-based prefix/suffix strategy.
|
|
82
|
+
|
|
83
|
+
## Quick start — manual composition (advanced)
|
|
84
|
+
|
|
85
|
+
This drops the entire Lytx analytics platform into your Redwood app. Copy-paste into your `src/worker.tsx` and adjust as needed.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// src/worker.tsx
|
|
89
|
+
import { defineApp, type RequestInfo } from "rwsdk/worker";
|
|
90
|
+
import { route, render, prefix, layout } from "rwsdk/router";
|
|
91
|
+
import type { ExportedHandler } from "cloudflare:workers";
|
|
92
|
+
import { IS_DEV } from "rwsdk/constants";
|
|
93
|
+
|
|
94
|
+
import {
|
|
95
|
+
// Document shell
|
|
96
|
+
Document,
|
|
97
|
+
|
|
98
|
+
// Public pages
|
|
99
|
+
Signup,
|
|
100
|
+
Login,
|
|
101
|
+
VerifyEmail,
|
|
102
|
+
|
|
103
|
+
// Authenticated app pages
|
|
104
|
+
AppLayout,
|
|
105
|
+
DashboardPage,
|
|
106
|
+
EventsPage,
|
|
107
|
+
ExplorePage,
|
|
108
|
+
SettingsPage,
|
|
109
|
+
NewSiteSetup,
|
|
110
|
+
DashboardWorkspaceLayout,
|
|
111
|
+
ReportBuilderWorkspace,
|
|
112
|
+
CustomReportBuilderPage,
|
|
113
|
+
|
|
114
|
+
// API routes
|
|
115
|
+
eventsApi,
|
|
116
|
+
seedApi,
|
|
117
|
+
team_dashboard_endpoints,
|
|
118
|
+
world_countries,
|
|
119
|
+
getCurrentVisitorsRoute,
|
|
120
|
+
getDashboardDataRoute,
|
|
121
|
+
siteEventsSqlRoute,
|
|
122
|
+
siteEventsSchemaRoute,
|
|
123
|
+
aiChatRoute,
|
|
124
|
+
aiConfigRoute,
|
|
125
|
+
aiTagSuggestRoute,
|
|
126
|
+
resendVerificationEmailRoute,
|
|
127
|
+
userApiRoutes,
|
|
128
|
+
eventLabelsApi,
|
|
129
|
+
reportsApi,
|
|
130
|
+
legacyContainerRoute,
|
|
131
|
+
newSiteSetup,
|
|
132
|
+
lytxTag,
|
|
133
|
+
trackWebEvent,
|
|
134
|
+
handleQueueMessage,
|
|
135
|
+
|
|
136
|
+
// Middleware
|
|
137
|
+
authMiddleware,
|
|
138
|
+
sessionMiddleware,
|
|
139
|
+
|
|
140
|
+
// Auth
|
|
141
|
+
auth,
|
|
142
|
+
|
|
143
|
+
// Route guards
|
|
144
|
+
checkIfTeamSetupSites,
|
|
145
|
+
onlyAllowGetPost,
|
|
146
|
+
|
|
147
|
+
// Durable Objects (re-export so Cloudflare can find them)
|
|
148
|
+
SyncDurableObject,
|
|
149
|
+
SiteDurableObject,
|
|
150
|
+
|
|
151
|
+
// Types
|
|
152
|
+
type AppContext,
|
|
153
|
+
type DBAdapter,
|
|
154
|
+
} from "lytx";
|
|
155
|
+
|
|
156
|
+
export { SyncDurableObject, SiteDurableObject };
|
|
157
|
+
|
|
158
|
+
type AppRequestInfo = RequestInfo<any, AppContext>;
|
|
159
|
+
|
|
160
|
+
const dbAdapter: DBAdapter = "sqlite";
|
|
161
|
+
|
|
162
|
+
const app = defineApp<AppRequestInfo>([
|
|
163
|
+
({ request }) => {
|
|
164
|
+
if (IS_DEV) console.log(request.method, request.url);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// ── Tag & event ingestion (unauthenticated) ──
|
|
168
|
+
legacyContainerRoute,
|
|
169
|
+
lytxTag(dbAdapter),
|
|
170
|
+
trackWebEvent(dbAdapter, "/trackWebEvent", { useQueue: true }),
|
|
171
|
+
eventsApi,
|
|
172
|
+
seedApi,
|
|
173
|
+
|
|
174
|
+
// ── Auth API ──
|
|
175
|
+
route("/api/auth/*", (r) => authMiddleware(r)),
|
|
176
|
+
resendVerificationEmailRoute,
|
|
177
|
+
userApiRoutes,
|
|
178
|
+
|
|
179
|
+
// ── Rendered pages ──
|
|
180
|
+
render<AppRequestInfo>(Document, [
|
|
181
|
+
route("/", [onlyAllowGetPost, ({ request }) => Response.redirect(new URL("/login", request.url).toString(), 308)]),
|
|
182
|
+
route("/signup", [onlyAllowGetPost, () => <Signup />]),
|
|
183
|
+
route("/login", [onlyAllowGetPost, () => <Login />]),
|
|
184
|
+
route("/verify-email", [
|
|
185
|
+
onlyAllowGetPost,
|
|
186
|
+
async ({ request }) => {
|
|
187
|
+
const url = new URL(request.url);
|
|
188
|
+
const token = url.searchParams.get("token") || "";
|
|
189
|
+
if (!token) {
|
|
190
|
+
return <VerifyEmail status={{ type: "error", message: "Missing token." }} />;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await auth.api.verifyEmail({ query: { token } });
|
|
194
|
+
return <VerifyEmail status={{ type: "success", message: "Email verified." }} />;
|
|
195
|
+
} catch {
|
|
196
|
+
return <VerifyEmail status={{ type: "error", message: "Verification failed." }} />;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
]),
|
|
200
|
+
|
|
201
|
+
// ── Authenticated app shell ──
|
|
202
|
+
layout(AppLayout, [
|
|
203
|
+
sessionMiddleware,
|
|
204
|
+
|
|
205
|
+
// Authenticated API routes
|
|
206
|
+
prefix("/api", [
|
|
207
|
+
world_countries,
|
|
208
|
+
getDashboardDataRoute,
|
|
209
|
+
getCurrentVisitorsRoute,
|
|
210
|
+
aiConfigRoute,
|
|
211
|
+
aiChatRoute,
|
|
212
|
+
aiTagSuggestRoute,
|
|
213
|
+
siteEventsSqlRoute,
|
|
214
|
+
siteEventsSchemaRoute,
|
|
215
|
+
eventLabelsApi,
|
|
216
|
+
reportsApi,
|
|
217
|
+
newSiteSetup(),
|
|
218
|
+
team_dashboard_endpoints,
|
|
219
|
+
]),
|
|
220
|
+
|
|
221
|
+
onlyAllowGetPost,
|
|
222
|
+
|
|
223
|
+
// Dashboard pages
|
|
224
|
+
route("/dashboard", [
|
|
225
|
+
checkIfTeamSetupSites,
|
|
226
|
+
() => <DashboardPage activeReportBuilderItemId="create-report" />,
|
|
227
|
+
]),
|
|
228
|
+
layout(DashboardWorkspaceLayout, [
|
|
229
|
+
route("/dashboard/reports/create-report", [
|
|
230
|
+
checkIfTeamSetupSites,
|
|
231
|
+
() => <ReportBuilderWorkspace activeReportBuilderItemId="create-report" />,
|
|
232
|
+
]),
|
|
233
|
+
route("/dashboard/reports/custom/new", [
|
|
234
|
+
checkIfTeamSetupSites,
|
|
235
|
+
({ request }) => {
|
|
236
|
+
const template = new URL(request.url).searchParams.get("template");
|
|
237
|
+
return <CustomReportBuilderPage initialTemplate={template} />;
|
|
238
|
+
},
|
|
239
|
+
]),
|
|
240
|
+
// ... add more report routes as needed
|
|
241
|
+
]),
|
|
242
|
+
route("/dashboard/events", [checkIfTeamSetupSites, () => <EventsPage />]),
|
|
243
|
+
route("/dashboard/settings", [() => <SettingsPage />]),
|
|
244
|
+
route("/dashboard/explore", [checkIfTeamSetupSites, () => <ExplorePage />]),
|
|
245
|
+
route("/dashboard/new-site", [() => <NewSiteSetup />]),
|
|
246
|
+
]),
|
|
247
|
+
]),
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
export default {
|
|
251
|
+
fetch: app.fetch,
|
|
252
|
+
queue: handleQueueMessage,
|
|
253
|
+
} satisfies ExportedHandler<Env>;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Consumer starter template
|
|
257
|
+
|
|
258
|
+
For a copy/paste starter workspace (worker + vite + `alchemy.run.ts`) that uses public root exports, see `demo/README.md`.
|
|
259
|
+
|
|
260
|
+
## Minimal setup — event ingestion only
|
|
261
|
+
|
|
262
|
+
If you only need the tracking pixel and event API (no dashboard UI):
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// src/worker.tsx
|
|
266
|
+
import { defineApp, type RequestInfo } from "rwsdk/worker";
|
|
267
|
+
import { route } from "rwsdk/router";
|
|
268
|
+
import type { ExportedHandler } from "cloudflare:workers";
|
|
269
|
+
import {
|
|
270
|
+
lytxTag,
|
|
271
|
+
trackWebEvent,
|
|
272
|
+
eventsApi,
|
|
273
|
+
handleQueueMessage,
|
|
274
|
+
authMiddleware,
|
|
275
|
+
type AppContext,
|
|
276
|
+
} from "lytx";
|
|
277
|
+
|
|
278
|
+
export { SiteDurableObject } from "lytx";
|
|
279
|
+
|
|
280
|
+
type AppRequestInfo = RequestInfo<any, AppContext>;
|
|
281
|
+
|
|
282
|
+
const app = defineApp<AppRequestInfo>([
|
|
283
|
+
lytxTag("sqlite"),
|
|
284
|
+
trackWebEvent("sqlite", "/trackWebEvent", { useQueue: true }),
|
|
285
|
+
eventsApi,
|
|
286
|
+
route("/api/auth/*", (r) => authMiddleware(r)),
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
export default {
|
|
290
|
+
fetch: app.fetch,
|
|
291
|
+
queue: handleQueueMessage,
|
|
292
|
+
} satisfies ExportedHandler<Env>;
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Cloudflare bindings
|
|
296
|
+
|
|
297
|
+
Your `wrangler.jsonc` (or `alchemy.run.ts`) needs these bindings for the full stack:
|
|
298
|
+
|
|
299
|
+
| Binding | Type | Purpose |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `lytx_core_db` | D1 Database | Primary data store (users, teams, sites, events) |
|
|
302
|
+
| `LYTX_EVENTS` | KV Namespace | Event storage / caching |
|
|
303
|
+
| `lytx_config` | KV Namespace | Configuration store |
|
|
304
|
+
| `lytx_sessions` | KV Namespace | Session storage |
|
|
305
|
+
| `SITE_EVENTS_QUEUE` | Queue | Async event ingestion |
|
|
306
|
+
| `SITE_DURABLE_OBJECT` | Durable Object | Per-site event aggregation |
|
|
307
|
+
|
|
308
|
+
### Resource naming strategy
|
|
309
|
+
|
|
310
|
+
Resource binding keys in worker code stay fixed (`LYTX_EVENTS`, `lytx_config`, etc.), but physical Cloudflare resource names can be configured deterministically in `alchemy.run.ts` via `resolveLytxResourceNames` (`lytx/resource-names`).
|
|
311
|
+
|
|
312
|
+
Supported naming env vars:
|
|
313
|
+
|
|
314
|
+
```env
|
|
315
|
+
# Optional global strategy
|
|
316
|
+
LYTX_RESOURCE_PREFIX=
|
|
317
|
+
LYTX_RESOURCE_SUFFIX=
|
|
318
|
+
# one of: prefix | suffix | none
|
|
319
|
+
LYTX_RESOURCE_STAGE_POSITION=none
|
|
320
|
+
|
|
321
|
+
# Optional per-resource overrides
|
|
322
|
+
LYTX_WORKER_NAME=
|
|
323
|
+
LYTX_DURABLE_HOST_WORKER_NAME=
|
|
324
|
+
LYTX_DURABLE_OBJECT_NAMESPACE_NAME=
|
|
325
|
+
LYTX_D1_DATABASE_NAME=
|
|
326
|
+
LYTX_KV_EVENTS_NAME=
|
|
327
|
+
LYTX_KV_CONFIG_NAME=
|
|
328
|
+
LYTX_KV_SESSIONS_NAME=
|
|
329
|
+
LYTX_QUEUE_NAME=
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
This keeps naming deterministic across deploys and avoids accidental resource drift between stages.
|
|
333
|
+
|
|
334
|
+
### Domain and route prefix strategy
|
|
335
|
+
|
|
336
|
+
Use these env vars in `alchemy.run.ts` to configure app/tracking domains without editing source:
|
|
337
|
+
|
|
338
|
+
```env
|
|
339
|
+
# Optional custom worker domain
|
|
340
|
+
LYTX_APP_DOMAIN=analytics.example.com
|
|
341
|
+
|
|
342
|
+
# Optional tracking domain used in LYTX_DOMAIN binding
|
|
343
|
+
LYTX_TRACKING_DOMAIN=collect.example.com
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Use `createLytxApp({ tagRoutes: { pathPrefix: "/collect" } })` to prefix tracking script and ingestion endpoints.
|
|
347
|
+
|
|
348
|
+
### Environment variables
|
|
349
|
+
|
|
350
|
+
Add these to your `.env` (local) or worker secrets (production):
|
|
351
|
+
|
|
352
|
+
```env
|
|
353
|
+
# Required
|
|
354
|
+
BETTER_AUTH_SECRET=<random-secret>
|
|
355
|
+
BETTER_AUTH_URL=http://localhost:5173
|
|
356
|
+
ENCRYPTION_KEY=<random-secret>
|
|
357
|
+
|
|
358
|
+
# Auth providers (optional — enable the ones you want)
|
|
359
|
+
GITHUB_CLIENT_ID=...
|
|
360
|
+
GITHUB_CLIENT_SECRET=...
|
|
361
|
+
GOOGLE_CLIENT_ID=...
|
|
362
|
+
GOOGLE_CLIENT_SECRET=...
|
|
363
|
+
|
|
364
|
+
# Email (required for verification/invite emails)
|
|
365
|
+
EMAIL_FROM=noreply@yourdomain.com
|
|
366
|
+
RESEND_API_KEY=...
|
|
367
|
+
|
|
368
|
+
# AI features (optional)
|
|
369
|
+
AI_API_KEY=...
|
|
370
|
+
AI_BASE_URL=...
|
|
371
|
+
AI_MODEL=...
|
|
372
|
+
AI_DAILY_TOKEN_LIMIT=
|
|
373
|
+
|
|
374
|
+
# Report builder toggle (optional)
|
|
375
|
+
# Set to `true` to enable report routes and UI
|
|
376
|
+
REPORT_BUILDER=false
|
|
377
|
+
# Set to `false` to hide Ask AI while keeping report builder enabled
|
|
378
|
+
ASK_AI=true
|
|
379
|
+
|
|
380
|
+
# Modular feature toggles (optional)
|
|
381
|
+
LYTX_FEATURE_DASHBOARD=true
|
|
382
|
+
LYTX_FEATURE_EVENTS=true
|
|
383
|
+
LYTX_FEATURE_AUTH=true
|
|
384
|
+
LYTX_FEATURE_AI=true
|
|
385
|
+
LYTX_FEATURE_TAG_SCRIPT=true
|
|
386
|
+
|
|
387
|
+
# Misc
|
|
388
|
+
LYTX_DOMAIN=localhost:5173
|
|
389
|
+
ENVIRONMENT=development
|
|
390
|
+
SEED_DATA_SECRET=<random-secret>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
If `EMAIL_FROM` is missing (or left as the placeholder `noreply@example.com`), email send attempts fail with a clear runtime error explaining how to configure it.
|
|
394
|
+
|
|
395
|
+
On a fresh install, the first successful signup becomes the initial admin and creates the default team. For scripted/bootstrap environments, you can use:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
cd core
|
|
399
|
+
bun run cli/bootstrap-admin.ts --email admin@example.com --password "StrongPassword123"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Use `--remote` to apply bootstrap changes directly to Cloudflare D1 via Wrangler. This requires Wrangler authentication (`wrangler login` or a valid Cloudflare API token) and access to the target database.
|
|
403
|
+
|
|
404
|
+
## Database setup
|
|
405
|
+
|
|
406
|
+
Generate and apply D1 migrations:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
bunx drizzle-kit generate --config=db/d1/drizzle.config.ts
|
|
410
|
+
wrangler d1 migrations apply lytx-core-db --local
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Seed dev data:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
bun run cli/seed-data.ts --team-id 1 --site-id 1 --durable-only --events 50 --seed-secret "$SEED_DATA_SECRET"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## What's included
|
|
420
|
+
|
|
421
|
+
### App Factory
|
|
422
|
+
|
|
423
|
+
| Export | Description |
|
|
424
|
+
|---|---|
|
|
425
|
+
| `createLytxApp` | Canonical factory that returns a worker handler (`fetch` + `queue`) with configurable tag routes and feature toggles |
|
|
426
|
+
|
|
427
|
+
### API Routes
|
|
428
|
+
|
|
429
|
+
| Export | Path | Description |
|
|
430
|
+
|---|---|---|
|
|
431
|
+
| `lytxTag` | `/lytx.js` | JavaScript tracking tag |
|
|
432
|
+
| `trackWebEvent` | `/trackWebEvent` | Event ingestion endpoint |
|
|
433
|
+
| `eventsApi` | `/api/events/*` | Event CRUD |
|
|
434
|
+
| `getDashboardDataRoute` | `/api/dashboard-data` | Dashboard aggregation |
|
|
435
|
+
| `getCurrentVisitorsRoute` | `/api/current-visitors` | Real-time visitor count |
|
|
436
|
+
| `siteEventsSqlRoute` | `/api/sql` | Raw SQL query interface |
|
|
437
|
+
| `team_dashboard_endpoints` | `/api/team/*` | Team management |
|
|
438
|
+
| `eventLabelsApi` | `/api/event-labels/*` | Event label CRUD |
|
|
439
|
+
| `reportsApi` | `/api/reports/*` | Custom reports |
|
|
440
|
+
| `aiChatRoute` | `/api/ai/chat` | AI data assistant |
|
|
441
|
+
| `authMiddleware` | `/api/auth/*` | better-auth handler |
|
|
442
|
+
|
|
443
|
+
### Pages & Components
|
|
444
|
+
|
|
445
|
+
| Export | Description |
|
|
446
|
+
|---|---|
|
|
447
|
+
| `DashboardPage` | Main analytics dashboard with charts, maps, tables |
|
|
448
|
+
| `EventsPage` | Event explorer / raw event viewer |
|
|
449
|
+
| `ExplorePage` | SQL explorer with Monaco editor |
|
|
450
|
+
| `SettingsPage` | Team settings, API keys, site tag install |
|
|
451
|
+
| `Signup`, `Login`, `VerifyEmail` | Auth pages |
|
|
452
|
+
| `AppLayout` | Authenticated app shell with nav |
|
|
453
|
+
| `Document` | HTML document wrapper |
|
|
454
|
+
|
|
455
|
+
### Middleware
|
|
456
|
+
|
|
457
|
+
| Export | Description |
|
|
458
|
+
|---|---|
|
|
459
|
+
| `authMiddleware` | Handles `/api/auth/*` (better-auth) |
|
|
460
|
+
| `sessionMiddleware` | Loads user session + team context into `AppContext` |
|
|
461
|
+
| `onlyAllowGetPost` | Rejects non-GET/POST requests |
|
|
462
|
+
| `checkIfTeamSetupSites` | Redirects to setup if team has no sites |
|
|
463
|
+
|
|
464
|
+
### Durable Objects
|
|
465
|
+
|
|
466
|
+
| Export | Description |
|
|
467
|
+
|---|---|
|
|
468
|
+
| `SiteDurableObject` | Per-site event storage and aggregation |
|
|
469
|
+
| `SyncDurableObject` | Session synchronization |
|
|
470
|
+
|
|
471
|
+
> You **must** re-export Durable Objects from your worker entry point so Cloudflare can instantiate them.
|
|
472
|
+
|
|
473
|
+
## Customization
|
|
474
|
+
|
|
475
|
+
Since you control `defineApp`, you can:
|
|
476
|
+
|
|
477
|
+
- **Drop routes** you don't need (remove the AI routes, the seed API, etc.)
|
|
478
|
+
- **Add your own routes** alongside Lytx routes
|
|
479
|
+
- **Replace pages** with your own React components while keeping the API routes
|
|
480
|
+
- **Mount under a prefix** — wrap Lytx routes in `prefix("/analytics", [...])`
|
|
481
|
+
- **Swap the DB adapter** — pass `"postgres"` instead of `"sqlite"` to tag routes
|
|
482
|
+
- **Add middleware** — insert your own auth/rate-limiting before or after `sessionMiddleware`
|
|
483
|
+
|
|
484
|
+
## License
|
|
485
|
+
|
|
486
|
+
MIT
|
package/alchemy.run.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { SiteDurableObject } from "./db/durable/siteDurableObject";
|
|
2
|
+
import alchemy from "alchemy";
|
|
3
|
+
import {
|
|
4
|
+
D1Database,
|
|
5
|
+
KVNamespace,
|
|
6
|
+
DurableObjectNamespace,
|
|
7
|
+
Redwood,
|
|
8
|
+
Queue,
|
|
9
|
+
Worker,
|
|
10
|
+
} from "alchemy/cloudflare";
|
|
11
|
+
import {
|
|
12
|
+
resolveLytxResourceNames,
|
|
13
|
+
type LytxResourceStagePosition,
|
|
14
|
+
} from "./src/config/resourceNames";
|
|
15
|
+
|
|
16
|
+
const alchemyAppName = process.env.LYTX_APP_NAME ?? "lytx";
|
|
17
|
+
const app = await alchemy(alchemyAppName);
|
|
18
|
+
if (app.local && app.stage !== "dev") {
|
|
19
|
+
throw new Error(`Refusing local run on non-dev stage: ${app.stage}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const adoptMode = false;
|
|
23
|
+
|
|
24
|
+
const stagePositionRaw = process.env.LYTX_RESOURCE_STAGE_POSITION;
|
|
25
|
+
const stagePosition: LytxResourceStagePosition =
|
|
26
|
+
stagePositionRaw === "prefix" || stagePositionRaw === "suffix" || stagePositionRaw === "none"
|
|
27
|
+
? stagePositionRaw
|
|
28
|
+
: "none";
|
|
29
|
+
|
|
30
|
+
const appDomain = process.env.LYTX_APP_DOMAIN?.trim();
|
|
31
|
+
const trackingDomain = process.env.LYTX_TRACKING_DOMAIN?.trim();
|
|
32
|
+
|
|
33
|
+
const resourceNames = resolveLytxResourceNames({
|
|
34
|
+
stage: app.stage,
|
|
35
|
+
prefix: process.env.LYTX_RESOURCE_PREFIX,
|
|
36
|
+
suffix: process.env.LYTX_RESOURCE_SUFFIX,
|
|
37
|
+
stagePosition,
|
|
38
|
+
overrides: {
|
|
39
|
+
workerName: process.env.LYTX_WORKER_NAME,
|
|
40
|
+
durableHostWorkerName: process.env.LYTX_DURABLE_HOST_WORKER_NAME,
|
|
41
|
+
durableObjectNamespaceName: process.env.LYTX_DURABLE_OBJECT_NAMESPACE_NAME,
|
|
42
|
+
d1DatabaseName: process.env.LYTX_D1_DATABASE_NAME,
|
|
43
|
+
eventsKvNamespaceName: process.env.LYTX_KV_EVENTS_NAME,
|
|
44
|
+
configKvNamespaceName: process.env.LYTX_KV_CONFIG_NAME,
|
|
45
|
+
sessionsKvNamespaceName: process.env.LYTX_KV_SESSIONS_NAME,
|
|
46
|
+
eventsQueueName: process.env.LYTX_QUEUE_NAME,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const siteDurableObject = DurableObjectNamespace<SiteDurableObject>(resourceNames.durableObjectNamespaceName, {
|
|
51
|
+
className: "SiteDurableObject",
|
|
52
|
+
sqlite: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const lytxKv = await KVNamespace(resourceNames.eventsKvNamespaceName, {
|
|
56
|
+
adopt: adoptMode,
|
|
57
|
+
delete: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const lytx_config = await KVNamespace(resourceNames.configKvNamespaceName, {
|
|
61
|
+
adopt: adoptMode,
|
|
62
|
+
delete: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const siteEventsQueue = await Queue(resourceNames.eventsQueueName, {
|
|
66
|
+
name: resourceNames.eventsQueueName,
|
|
67
|
+
adopt: adoptMode,
|
|
68
|
+
delete: false,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const lytx_sessions = await KVNamespace(resourceNames.sessionsKvNamespaceName, {
|
|
72
|
+
adopt: adoptMode,
|
|
73
|
+
delete: false,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const lytxCoreDb = await D1Database(resourceNames.d1DatabaseName, {
|
|
77
|
+
name: resourceNames.d1DatabaseName,
|
|
78
|
+
migrationsDir: "./db/d1/migrations",
|
|
79
|
+
adopt: adoptMode,
|
|
80
|
+
delete: false,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const localDurableHost = app.local
|
|
84
|
+
? await Worker(resourceNames.durableHostWorkerName, {
|
|
85
|
+
entrypoint: "./endpoints/site_do_worker.ts",
|
|
86
|
+
bindings: {
|
|
87
|
+
SITE_DURABLE_OBJECT: siteDurableObject,
|
|
88
|
+
lytx_core_db: lytxCoreDb,
|
|
89
|
+
ENVIRONMENT: process.env.ENVIRONMENT ?? "development",
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
: undefined;
|
|
93
|
+
|
|
94
|
+
export const worker = await Redwood(resourceNames.workerName, {
|
|
95
|
+
adopt: false,
|
|
96
|
+
url: false,
|
|
97
|
+
noBundle: false,
|
|
98
|
+
...(appDomain
|
|
99
|
+
? {
|
|
100
|
+
domains: [
|
|
101
|
+
{
|
|
102
|
+
adopt: adoptMode,
|
|
103
|
+
domainName: appDomain,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
}
|
|
107
|
+
: {}),
|
|
108
|
+
wrangler: {
|
|
109
|
+
main: "src/worker.tsx",
|
|
110
|
+
transform: (spec) => ({
|
|
111
|
+
...spec,
|
|
112
|
+
compatibility_flags: ["nodejs_compat"],
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
eventSources: [
|
|
116
|
+
{
|
|
117
|
+
queue: siteEventsQueue,
|
|
118
|
+
settings: {
|
|
119
|
+
batchSize: 100,
|
|
120
|
+
maxConcurrency: 4,
|
|
121
|
+
maxRetries: 3,
|
|
122
|
+
maxWaitTimeMs: 20000,
|
|
123
|
+
retryDelay: 30,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
bindings: {
|
|
128
|
+
SITE_DURABLE_OBJECT: siteDurableObject,
|
|
129
|
+
LYTX_EVENTS: lytxKv,
|
|
130
|
+
lytx_config: lytx_config,
|
|
131
|
+
lytx_sessions: lytx_sessions,
|
|
132
|
+
lytx_core_db: lytxCoreDb,
|
|
133
|
+
SITE_EVENTS_QUEUE: siteEventsQueue,
|
|
134
|
+
LYTX_DOMAIN: trackingDomain || appDomain || process.env.LYTX_DOMAIN || "localhost:5173",
|
|
135
|
+
EMAIL_FROM: process.env.EMAIL_FROM || "noreply@example.com",
|
|
136
|
+
BETTER_AUTH_SECRET: alchemy.secret(process.env.BETTER_AUTH_SECRET),
|
|
137
|
+
GITHUB_CLIENT_SECRET: alchemy.secret(process.env.GITHUB_CLIENT_SECRET),
|
|
138
|
+
GOOGLE_CLIENT_SECRET: alchemy.secret(process.env.GOOGLE_CLIENT_SECRET),
|
|
139
|
+
RESEND_API_KEY: alchemy.secret(process.env.RESEND_API_KEY),
|
|
140
|
+
ENCRYPTION_KEY: alchemy.secret(process.env.ENCRYPTION_KEY),
|
|
141
|
+
AI_API_KEY: alchemy.secret(process.env.AI_API_KEY),
|
|
142
|
+
SEED_DATA_SECRET: alchemy.secret(process.env.SEED_DATA_SECRET),
|
|
143
|
+
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
|
|
144
|
+
GITHUB_CLIENT_ID: alchemy.secret(process.env.GITHUB_CLIENT_ID),
|
|
145
|
+
GOOGLE_CLIENT_ID: alchemy.secret(process.env.GOOGLE_CLIENT_ID),
|
|
146
|
+
ENVIRONMENT: process.env.ENVIRONMENT ?? "development",
|
|
147
|
+
REPORT_BUILDER: process.env.REPORT_BUILDER ?? "false",
|
|
148
|
+
ASK_AI: process.env.ASK_AI ?? "true",
|
|
149
|
+
AI_BASE_URL: process.env.AI_BASE_URL ?? "",
|
|
150
|
+
AI_MODEL: process.env.AI_MODEL ?? "",
|
|
151
|
+
AI_DAILY_TOKEN_LIMIT: process.env.AI_DAILY_TOKEN_LIMIT ?? "",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await app.finalize();
|