@rovela-ai/sdk 0.4.3 → 0.5.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/dist/admin/components/AdminNav.d.ts.map +1 -1
- package/dist/admin/components/AdminNav.js +10 -1
- package/dist/admin/components/AdminNav.js.map +1 -1
- package/dist/admin/components/ExampleContentBanner.js +2 -2
- package/dist/admin/components/ExampleContentBanner.js.map +1 -1
- package/dist/admin/components/SetupGuide.d.ts.map +1 -1
- package/dist/admin/components/SetupGuide.js +4 -4
- package/dist/admin/components/SetupGuide.js.map +1 -1
- package/dist/admin/components/index.d.ts +0 -1
- package/dist/admin/components/index.d.ts.map +1 -1
- package/dist/admin/components/index.js +0 -1
- package/dist/admin/components/index.js.map +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/styles/admin-theme.css +11 -0
- package/dist/analytics/api/dashboard.d.ts +16 -0
- package/dist/analytics/api/dashboard.d.ts.map +1 -0
- package/dist/analytics/api/dashboard.js +37 -0
- package/dist/analytics/api/dashboard.js.map +1 -0
- package/dist/analytics/api/events.d.ts +23 -0
- package/dist/analytics/api/events.d.ts.map +1 -0
- package/dist/analytics/api/events.js +55 -0
- package/dist/analytics/api/events.js.map +1 -0
- package/dist/analytics/api/index.d.ts +15 -0
- package/dist/analytics/api/index.d.ts.map +1 -0
- package/dist/analytics/api/index.js +15 -0
- package/dist/analytics/api/index.js.map +1 -0
- package/dist/analytics/api/track.d.ts +20 -0
- package/dist/analytics/api/track.d.ts.map +1 -0
- package/dist/analytics/api/track.js +233 -0
- package/dist/analytics/api/track.js.map +1 -0
- package/dist/analytics/api/visitors.d.ts +19 -0
- package/dist/analytics/api/visitors.d.ts.map +1 -0
- package/dist/analytics/api/visitors.js +49 -0
- package/dist/analytics/api/visitors.js.map +1 -0
- package/dist/analytics/client/tracker.d.ts +51 -0
- package/dist/analytics/client/tracker.d.ts.map +1 -0
- package/dist/analytics/client/tracker.js +208 -0
- package/dist/analytics/client/tracker.js.map +1 -0
- package/dist/analytics/components/AnalyticsDashboard.d.ts +2 -0
- package/dist/analytics/components/AnalyticsDashboard.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsDashboard.js +26 -0
- package/dist/analytics/components/AnalyticsDashboard.js.map +1 -0
- package/dist/analytics/components/AnalyticsPeriodContext.d.ts +13 -0
- package/dist/analytics/components/AnalyticsPeriodContext.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsPeriodContext.js +28 -0
- package/dist/analytics/components/AnalyticsPeriodContext.js.map +1 -0
- package/dist/analytics/components/AnalyticsProvider.d.ts +22 -0
- package/dist/analytics/components/AnalyticsProvider.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsProvider.js +152 -0
- package/dist/analytics/components/AnalyticsProvider.js.map +1 -0
- package/dist/analytics/components/AnalyticsTabNav.d.ts +17 -0
- package/dist/analytics/components/AnalyticsTabNav.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsTabNav.js +42 -0
- package/dist/analytics/components/AnalyticsTabNav.js.map +1 -0
- package/dist/analytics/hooks/useAnalytics.d.ts +9 -0
- package/dist/analytics/hooks/useAnalytics.d.ts.map +1 -0
- package/dist/analytics/hooks/useAnalytics.js +8 -0
- package/dist/analytics/hooks/useAnalytics.js.map +1 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.d.ts +9 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.d.ts.map +1 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.js +45 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.js.map +1 -0
- package/dist/analytics/hooks/useEventsLog.d.ts +24 -0
- package/dist/analytics/hooks/useEventsLog.d.ts.map +1 -0
- package/dist/analytics/hooks/useEventsLog.js +81 -0
- package/dist/analytics/hooks/useEventsLog.js.map +1 -0
- package/dist/analytics/hooks/useVisitorsList.d.ts +20 -0
- package/dist/analytics/hooks/useVisitorsList.d.ts.map +1 -0
- package/dist/analytics/hooks/useVisitorsList.js +69 -0
- package/dist/analytics/hooks/useVisitorsList.js.map +1 -0
- package/dist/analytics/index.d.ts +44 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +39 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/server/index.d.ts +10 -0
- package/dist/analytics/server/index.d.ts.map +1 -0
- package/dist/analytics/server/index.js +9 -0
- package/dist/analytics/server/index.js.map +1 -0
- package/dist/analytics/server/normalize.d.ts +23 -0
- package/dist/analytics/server/normalize.d.ts.map +1 -0
- package/dist/analytics/server/normalize.js +75 -0
- package/dist/analytics/server/normalize.js.map +1 -0
- package/dist/analytics/server/queries.d.ts +74 -0
- package/dist/analytics/server/queries.d.ts.map +1 -0
- package/dist/analytics/server/queries.js +470 -0
- package/dist/analytics/server/queries.js.map +1 -0
- package/dist/analytics/types.d.ts +186 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +16 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/analytics/views/DashboardsView.d.ts +6 -0
- package/dist/analytics/views/DashboardsView.d.ts.map +1 -0
- package/dist/analytics/views/DashboardsView.js +93 -0
- package/dist/analytics/views/DashboardsView.js.map +1 -0
- package/dist/analytics/views/EventsView.d.ts +6 -0
- package/dist/analytics/views/EventsView.d.ts.map +1 -0
- package/dist/analytics/views/EventsView.js +85 -0
- package/dist/analytics/views/EventsView.js.map +1 -0
- package/dist/analytics/views/VisitorsView.d.ts +6 -0
- package/dist/analytics/views/VisitorsView.d.ts.map +1 -0
- package/dist/analytics/views/VisitorsView.js +57 -0
- package/dist/analytics/views/VisitorsView.js.map +1 -0
- package/dist/cart/store.d.ts.map +1 -1
- package/dist/cart/store.js +12 -0
- package/dist/cart/store.js.map +1 -1
- package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
- package/dist/checkout/components/CheckoutFlow.js +26 -2
- package/dist/checkout/components/CheckoutFlow.js.map +1 -1
- package/dist/checkout/components/ShippingForm.js +10 -5
- package/dist/checkout/components/ShippingForm.js.map +1 -1
- package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
- package/dist/checkout/hooks/useCheckout.js +12 -1
- package/dist/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/checkout/server/handle-webhook.js +15 -0
- package/dist/checkout/server/handle-webhook.js.map +1 -1
- package/dist/checkout/types.d.ts +13 -0
- package/dist/checkout/types.d.ts.map +1 -1
- package/dist/core/db/client.d.ts +14 -0
- package/dist/core/db/client.d.ts.map +1 -1
- package/dist/core/db/client.js +12 -0
- package/dist/core/db/client.js.map +1 -1
- package/dist/core/db/index.d.ts +2 -1
- package/dist/core/db/index.d.ts.map +1 -1
- package/dist/core/db/index.js +1 -1
- package/dist/core/db/index.js.map +1 -1
- package/dist/core/db/queries.d.ts +8 -7
- package/dist/core/db/queries.d.ts.map +1 -1
- package/dist/core/db/queries.js +80 -0
- package/dist/core/db/queries.js.map +1 -1
- package/dist/core/db/schema.d.ts +340 -4
- package/dist/core/db/schema.d.ts.map +1 -1
- package/dist/core/db/schema.js +55 -1
- package/dist/core/db/schema.js.map +1 -1
- package/dist/core/server/index.d.ts +2 -2
- package/dist/core/server/index.d.ts.map +1 -1
- package/dist/core/server/index.js +2 -0
- package/dist/core/server/index.js.map +1 -1
- package/package.json +25 -1
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/server/queries
|
|
3
|
+
*
|
|
4
|
+
* Per-store analytics read + write helpers. Reads from the store's own Neon
|
|
5
|
+
* branch via `getDb()` (no tenant filtering). Each read returns a typed
|
|
6
|
+
* payload ready for the dashboard.
|
|
7
|
+
*
|
|
8
|
+
* Writes are best-effort: `recordEvent` swallows failures so an analytics
|
|
9
|
+
* outage NEVER breaks customer flows (checkout, cart, browse). The endpoint
|
|
10
|
+
* + webhook callers rely on that contract.
|
|
11
|
+
*/
|
|
12
|
+
import { sql } from 'drizzle-orm';
|
|
13
|
+
import { getDb } from '../../core/db/client';
|
|
14
|
+
import { ensureAnalyticsTable } from '../../core/db/queries';
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// DB resolution — every read helper accepts an optional `db` param so the
|
|
17
|
+
// platform side (which iterates over merchant stores) can pass a per-store
|
|
18
|
+
// Drizzle client built via `createDb(project.databaseUrl)`. Defaults to
|
|
19
|
+
// `getDb()` (env's DATABASE_URL) for the storefront-side admin dashboard.
|
|
20
|
+
// =============================================================================
|
|
21
|
+
function resolveDb(db) {
|
|
22
|
+
return db ?? getDb();
|
|
23
|
+
}
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Period helpers
|
|
26
|
+
// =============================================================================
|
|
27
|
+
const PERIOD_DAYS = {
|
|
28
|
+
today: 1,
|
|
29
|
+
'7d': 7,
|
|
30
|
+
'30d': 30,
|
|
31
|
+
'90d': 90,
|
|
32
|
+
};
|
|
33
|
+
export function periodToDays(period) {
|
|
34
|
+
return PERIOD_DAYS[period];
|
|
35
|
+
}
|
|
36
|
+
function pctDelta(current, prior) {
|
|
37
|
+
if (prior === 0)
|
|
38
|
+
return null;
|
|
39
|
+
return (current - prior) / prior;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Insert a single event. Never throws — analytics MUST NEVER break the
|
|
43
|
+
* customer flow. The caller is the track endpoint OR the Stripe webhook
|
|
44
|
+
* wrapper; both have a "don't fail the user request on analytics failure"
|
|
45
|
+
* contract.
|
|
46
|
+
*
|
|
47
|
+
* For `purchase` / `refund` events, the partial unique index on `order_id`
|
|
48
|
+
* absorbs Stripe at-least-once webhook redelivery via ON CONFLICT.
|
|
49
|
+
*/
|
|
50
|
+
export async function recordEvent(input) {
|
|
51
|
+
try {
|
|
52
|
+
await ensureAnalyticsTable();
|
|
53
|
+
const db = getDb();
|
|
54
|
+
const e = input.event;
|
|
55
|
+
const isDedupable = e.event === 'purchase' || e.event === 'refund';
|
|
56
|
+
// Sparse-column extraction — TS narrowing keeps the union types honest.
|
|
57
|
+
const productId = 'product_id' in e ? e.product_id : null;
|
|
58
|
+
const variantId = 'variant_id' in e ? e.variant_id ?? null : null;
|
|
59
|
+
const categoryId = 'category_id' in e ? e.category_id ?? null : null;
|
|
60
|
+
const orderId = 'order_id' in e ? e.order_id : null;
|
|
61
|
+
const valueCents = 'value_cents' in e ? e.value_cents : null;
|
|
62
|
+
const quantity = 'quantity' in e ? e.quantity : null;
|
|
63
|
+
const currency = 'currency' in e ? e.currency : null;
|
|
64
|
+
if (isDedupable && orderId) {
|
|
65
|
+
// ON CONFLICT DO NOTHING via the partial unique index keyed on
|
|
66
|
+
// (order_id) WHERE event = 'purchase' OR 'refund'.
|
|
67
|
+
await db.execute(sql `
|
|
68
|
+
INSERT INTO analytics_events
|
|
69
|
+
(visitor_id, session_id, event, path, referrer,
|
|
70
|
+
product_id, variant_id, category_id, order_id,
|
|
71
|
+
value_cents, quantity, currency,
|
|
72
|
+
utm_source, utm_medium, utm_campaign, device, country)
|
|
73
|
+
VALUES
|
|
74
|
+
(${e.visitor_id}, ${e.session_id}, ${e.event}, ${e.path}, ${e.referrer ?? null},
|
|
75
|
+
${productId}, ${variantId}, ${categoryId}, ${orderId},
|
|
76
|
+
${valueCents}, ${quantity}, ${currency},
|
|
77
|
+
${e.utm_source ?? null}, ${e.utm_medium ?? null}, ${e.utm_campaign ?? null},
|
|
78
|
+
${input.device}, ${input.country})
|
|
79
|
+
ON CONFLICT DO NOTHING
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
await db.execute(sql `
|
|
84
|
+
INSERT INTO analytics_events
|
|
85
|
+
(visitor_id, session_id, event, path, referrer,
|
|
86
|
+
product_id, variant_id, category_id, order_id,
|
|
87
|
+
value_cents, quantity, currency,
|
|
88
|
+
utm_source, utm_medium, utm_campaign, device, country)
|
|
89
|
+
VALUES
|
|
90
|
+
(${e.visitor_id}, ${e.session_id}, ${e.event}, ${e.path}, ${e.referrer ?? null},
|
|
91
|
+
${productId}, ${variantId}, ${categoryId}, ${orderId},
|
|
92
|
+
${valueCents}, ${quantity}, ${currency},
|
|
93
|
+
${e.utm_source ?? null}, ${e.utm_medium ?? null}, ${e.utm_campaign ?? null},
|
|
94
|
+
${input.device}, ${input.country})
|
|
95
|
+
`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// Best-effort: never bubble to the caller. The track endpoint catches and
|
|
100
|
+
// returns 204 even on insert failure, so client tracking remains lossless
|
|
101
|
+
// from the visitor's perspective.
|
|
102
|
+
console.error('[analytics.recordEvent] insert failed:', err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function fetchOverview(start, end, db) {
|
|
106
|
+
const rows = (await db.execute(sql `
|
|
107
|
+
SELECT
|
|
108
|
+
COUNT(DISTINCT visitor_id) FILTER (WHERE event = 'page_view') AS visitors,
|
|
109
|
+
COUNT(DISTINCT session_id) FILTER (WHERE event = 'page_view') AS sessions,
|
|
110
|
+
COUNT(*) FILTER (WHERE event = 'page_view') AS pageviews,
|
|
111
|
+
COUNT(*) FILTER (WHERE event = 'purchase') AS purchases
|
|
112
|
+
FROM analytics_events
|
|
113
|
+
WHERE ts >= ${start.toISOString()} AND ts < ${end.toISOString()}
|
|
114
|
+
`));
|
|
115
|
+
const r = rows.rows?.[0] ?? { visitors: 0, sessions: 0, pageviews: 0, purchases: 0 };
|
|
116
|
+
const sessions = Number(r.sessions);
|
|
117
|
+
const purchases = Number(r.purchases);
|
|
118
|
+
return {
|
|
119
|
+
visitors: Number(r.visitors),
|
|
120
|
+
sessions,
|
|
121
|
+
pageviews: Number(r.pageviews),
|
|
122
|
+
conversionRate: sessions === 0 ? null : purchases / sessions,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function fetchTrend(start, end, db) {
|
|
126
|
+
const rows = (await db.execute(sql `
|
|
127
|
+
SELECT
|
|
128
|
+
date_trunc('day', ts)::date::text AS date,
|
|
129
|
+
COUNT(DISTINCT visitor_id) AS visitors
|
|
130
|
+
FROM analytics_events
|
|
131
|
+
WHERE event = 'page_view'
|
|
132
|
+
AND ts >= ${start.toISOString()} AND ts < ${end.toISOString()}
|
|
133
|
+
GROUP BY 1
|
|
134
|
+
ORDER BY 1
|
|
135
|
+
`));
|
|
136
|
+
return (rows.rows ?? []).map((r) => ({
|
|
137
|
+
date: r.date,
|
|
138
|
+
visitors: Number(r.visitors),
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
async function fetchTopPages(start, end, db) {
|
|
142
|
+
const rows = (await db.execute(sql `
|
|
143
|
+
SELECT path, COUNT(*) AS views
|
|
144
|
+
FROM analytics_events
|
|
145
|
+
WHERE event = 'page_view'
|
|
146
|
+
AND ts >= ${start.toISOString()} AND ts < ${end.toISOString()}
|
|
147
|
+
GROUP BY path
|
|
148
|
+
ORDER BY views DESC
|
|
149
|
+
LIMIT 10
|
|
150
|
+
`));
|
|
151
|
+
return (rows.rows ?? []).map((r) => ({ path: r.path, views: Number(r.views) }));
|
|
152
|
+
}
|
|
153
|
+
async function fetchTopProducts(start, end, db) {
|
|
154
|
+
const rows = (await db.execute(sql `
|
|
155
|
+
SELECT
|
|
156
|
+
product_id::text AS product_id,
|
|
157
|
+
COUNT(*) FILTER (WHERE event = 'view_item') AS views,
|
|
158
|
+
COUNT(*) FILTER (WHERE event = 'add_to_cart') AS adds,
|
|
159
|
+
COUNT(*) FILTER (WHERE event = 'purchase') AS purchases
|
|
160
|
+
FROM analytics_events
|
|
161
|
+
WHERE product_id IS NOT NULL
|
|
162
|
+
AND ts >= ${start.toISOString()} AND ts < ${end.toISOString()}
|
|
163
|
+
GROUP BY product_id
|
|
164
|
+
ORDER BY views DESC, adds DESC
|
|
165
|
+
LIMIT 10
|
|
166
|
+
`));
|
|
167
|
+
return (rows.rows ?? []).map((r) => ({
|
|
168
|
+
productId: r.product_id,
|
|
169
|
+
views: Number(r.views),
|
|
170
|
+
addsToCart: Number(r.adds),
|
|
171
|
+
purchases: Number(r.purchases),
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
async function fetchSources(start, end, db) {
|
|
175
|
+
// Group by UTM source if present, else by referrer host, else 'direct'.
|
|
176
|
+
const rows = (await db.execute(sql `
|
|
177
|
+
SELECT
|
|
178
|
+
COALESCE(NULLIF(utm_source, ''), NULLIF(referrer, ''), 'direct') AS source,
|
|
179
|
+
COUNT(DISTINCT visitor_id) AS visitors
|
|
180
|
+
FROM analytics_events
|
|
181
|
+
WHERE event = 'page_view'
|
|
182
|
+
AND ts >= ${start.toISOString()} AND ts < ${end.toISOString()}
|
|
183
|
+
GROUP BY 1
|
|
184
|
+
ORDER BY visitors DESC
|
|
185
|
+
LIMIT 8
|
|
186
|
+
`));
|
|
187
|
+
return (rows.rows ?? []).map((r) => ({
|
|
188
|
+
source: r.source,
|
|
189
|
+
visitors: Number(r.visitors),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
async function fetchDevices(start, end, db) {
|
|
193
|
+
const rows = (await db.execute(sql `
|
|
194
|
+
SELECT
|
|
195
|
+
COALESCE(NULLIF(device, ''), 'unknown') AS device,
|
|
196
|
+
COUNT(DISTINCT visitor_id) AS visitors
|
|
197
|
+
FROM analytics_events
|
|
198
|
+
WHERE event = 'page_view'
|
|
199
|
+
AND ts >= ${start.toISOString()} AND ts < ${end.toISOString()}
|
|
200
|
+
GROUP BY 1
|
|
201
|
+
ORDER BY visitors DESC
|
|
202
|
+
`));
|
|
203
|
+
return (rows.rows ?? []).map((r) => ({
|
|
204
|
+
device: r.device,
|
|
205
|
+
visitors: Number(r.visitors),
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Dashboard payload. Period-scoped current + prior windows for delta-vs-prior
|
|
210
|
+
* KPIs. All seven panels fan out in one Promise.all (~150ms warm Neon).
|
|
211
|
+
*
|
|
212
|
+
* @param db Optional Drizzle client. Default: `getDb()` (env DATABASE_URL).
|
|
213
|
+
* Platform-side passes `createDb(project.databaseUrl)` to read
|
|
214
|
+
* from a merchant's branch.
|
|
215
|
+
*/
|
|
216
|
+
export async function getAnalyticsDashboard(period, db) {
|
|
217
|
+
const dbRef = resolveDb(db);
|
|
218
|
+
// Pre-provisioned platform stores already have the table; the self-heal
|
|
219
|
+
// here is a no-op then. New stores get it created on first call.
|
|
220
|
+
// Skipped when caller passes a `db` — caller (platform) shouldn't trigger
|
|
221
|
+
// schema mutations against arbitrary merchant branches from a Rovela
|
|
222
|
+
// platform request. The storefront-side AnalyticsProvider track endpoint
|
|
223
|
+
// already runs the ensure path on first write.
|
|
224
|
+
if (!db) {
|
|
225
|
+
await ensureAnalyticsTable();
|
|
226
|
+
}
|
|
227
|
+
const days = periodToDays(period);
|
|
228
|
+
const now = new Date();
|
|
229
|
+
const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
230
|
+
const priorStart = new Date(start.getTime() - days * 24 * 60 * 60 * 1000);
|
|
231
|
+
const [current, prior, trend, topPages, topProducts, sources, devices] = await Promise.all([
|
|
232
|
+
fetchOverview(start, now, dbRef),
|
|
233
|
+
fetchOverview(priorStart, start, dbRef),
|
|
234
|
+
fetchTrend(start, now, dbRef),
|
|
235
|
+
fetchTopPages(start, now, dbRef),
|
|
236
|
+
fetchTopProducts(start, now, dbRef),
|
|
237
|
+
fetchSources(start, now, dbRef),
|
|
238
|
+
fetchDevices(start, now, dbRef),
|
|
239
|
+
]);
|
|
240
|
+
const overview = {
|
|
241
|
+
current,
|
|
242
|
+
prior,
|
|
243
|
+
deltas: {
|
|
244
|
+
visitors: pctDelta(current.visitors, prior.visitors),
|
|
245
|
+
sessions: pctDelta(current.sessions, prior.sessions),
|
|
246
|
+
pageviews: pctDelta(current.pageviews, prior.pageviews),
|
|
247
|
+
conversionRate: current.conversionRate === null || prior.conversionRate === null
|
|
248
|
+
? null
|
|
249
|
+
: pctDelta(current.conversionRate, prior.conversionRate),
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
period,
|
|
254
|
+
overview,
|
|
255
|
+
trend,
|
|
256
|
+
topPages,
|
|
257
|
+
topProducts,
|
|
258
|
+
sources,
|
|
259
|
+
devices,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// =============================================================================
|
|
263
|
+
// Platform-side compact summary (per-store overview card)
|
|
264
|
+
// =============================================================================
|
|
265
|
+
/**
|
|
266
|
+
* 7-day visitor + conversion summary for the Rovela platform's per-store
|
|
267
|
+
* Traffic card. Fast (3 small aggregates) — the platform fan-outs across
|
|
268
|
+
* every store the user can see, so we keep this tight.
|
|
269
|
+
*/
|
|
270
|
+
export async function getTrafficSummary7d(db) {
|
|
271
|
+
const dbRef = resolveDb(db);
|
|
272
|
+
if (!db) {
|
|
273
|
+
await ensureAnalyticsTable();
|
|
274
|
+
}
|
|
275
|
+
const now = new Date();
|
|
276
|
+
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
277
|
+
const fourteenDaysAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
278
|
+
const [current, prior] = await Promise.all([
|
|
279
|
+
fetchOverview(sevenDaysAgo, now, dbRef),
|
|
280
|
+
fetchOverview(fourteenDaysAgo, sevenDaysAgo, dbRef),
|
|
281
|
+
]);
|
|
282
|
+
return {
|
|
283
|
+
visitors7d: current.visitors,
|
|
284
|
+
visitorsPrior7d: prior.visitors,
|
|
285
|
+
delta: pctDelta(current.visitors, prior.visitors),
|
|
286
|
+
conversionRate7d: current.conversionRate,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const EVENT_LOG_SORT_COLUMNS = new Set([
|
|
290
|
+
'ts',
|
|
291
|
+
'event',
|
|
292
|
+
'path',
|
|
293
|
+
'referrer',
|
|
294
|
+
'country',
|
|
295
|
+
]);
|
|
296
|
+
export async function getEventsLog(options, db) {
|
|
297
|
+
const dbRef = resolveDb(db);
|
|
298
|
+
if (!db) {
|
|
299
|
+
await ensureAnalyticsTable();
|
|
300
|
+
}
|
|
301
|
+
const days = periodToDays(options.period);
|
|
302
|
+
const now = new Date();
|
|
303
|
+
const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
304
|
+
const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
|
|
305
|
+
// Fetch one extra row to detect "is there a next page". The extra row is
|
|
306
|
+
// sliced off before returning.
|
|
307
|
+
const fetchLimit = limit + 1;
|
|
308
|
+
const sortBy = EVENT_LOG_SORT_COLUMNS.has(options.sortBy)
|
|
309
|
+
? options.sortBy
|
|
310
|
+
: 'ts';
|
|
311
|
+
const sortDir = options.sortDir === 'asc' ? 'asc' : 'desc';
|
|
312
|
+
// Cursor predicate — only meaningful for the default ts-desc sort because
|
|
313
|
+
// mixing sort + cursor on arbitrary columns is error-prone. For non-ts
|
|
314
|
+
// sorts we ignore the cursor and just return the first page; the UI
|
|
315
|
+
// disables paginate-by-cursor when a custom sort is active.
|
|
316
|
+
const cursorBigInt = options.cursor && /^\d+$/.test(options.cursor)
|
|
317
|
+
? options.cursor
|
|
318
|
+
: null;
|
|
319
|
+
const orderClause = sortBy === 'ts'
|
|
320
|
+
? sortDir === 'asc'
|
|
321
|
+
? sql `ts ASC, id ASC`
|
|
322
|
+
: sql `ts DESC, id DESC`
|
|
323
|
+
: sortBy === 'event'
|
|
324
|
+
? sortDir === 'asc'
|
|
325
|
+
? sql `event ASC, ts DESC`
|
|
326
|
+
: sql `event DESC, ts DESC`
|
|
327
|
+
: sortBy === 'path'
|
|
328
|
+
? sortDir === 'asc'
|
|
329
|
+
? sql `path ASC, ts DESC`
|
|
330
|
+
: sql `path DESC, ts DESC`
|
|
331
|
+
: sortBy === 'referrer'
|
|
332
|
+
? sortDir === 'asc'
|
|
333
|
+
? sql `referrer ASC NULLS LAST, ts DESC`
|
|
334
|
+
: sql `referrer DESC NULLS LAST, ts DESC`
|
|
335
|
+
: sortDir === 'asc'
|
|
336
|
+
? sql `country ASC NULLS LAST, ts DESC`
|
|
337
|
+
: sql `country DESC NULLS LAST, ts DESC`;
|
|
338
|
+
const cursorClause = sortBy === 'ts' && cursorBigInt
|
|
339
|
+
? sortDir === 'desc'
|
|
340
|
+
? sql `AND id < ${cursorBigInt}::bigint`
|
|
341
|
+
: sql `AND id > ${cursorBigInt}::bigint`
|
|
342
|
+
: sql ``;
|
|
343
|
+
const heartbeatClause = options.hideHeartbeats
|
|
344
|
+
? sql `AND event <> 'session_heartbeat'`
|
|
345
|
+
: sql ``;
|
|
346
|
+
const result = (await dbRef.execute(sql `
|
|
347
|
+
SELECT id, ts, event, path, referrer, country, session_id, visitor_id,
|
|
348
|
+
product_id, value_cents, currency, device
|
|
349
|
+
FROM analytics_events
|
|
350
|
+
WHERE ts >= ${start.toISOString()} AND ts < ${now.toISOString()}
|
|
351
|
+
${cursorClause}
|
|
352
|
+
${heartbeatClause}
|
|
353
|
+
ORDER BY ${orderClause}
|
|
354
|
+
LIMIT ${fetchLimit}
|
|
355
|
+
`));
|
|
356
|
+
const allRows = result.rows ?? [];
|
|
357
|
+
const hasMore = allRows.length > limit;
|
|
358
|
+
const slice = hasMore ? allRows.slice(0, limit) : allRows;
|
|
359
|
+
// Cursor for next page is only stable for ts-sorted pagination.
|
|
360
|
+
const nextCursor = hasMore && sortBy === 'ts' ? String(slice[slice.length - 1].id) : null;
|
|
361
|
+
const mapped = slice.map((r) => ({
|
|
362
|
+
id: String(r.id),
|
|
363
|
+
ts: timestampToIso(r.ts),
|
|
364
|
+
event: r.event,
|
|
365
|
+
path: r.path,
|
|
366
|
+
referrer: r.referrer,
|
|
367
|
+
country: r.country,
|
|
368
|
+
sessionId: r.session_id,
|
|
369
|
+
visitorId: r.visitor_id,
|
|
370
|
+
productId: r.product_id,
|
|
371
|
+
valueCents: r.value_cents === null ? null : Number(r.value_cents),
|
|
372
|
+
currency: r.currency,
|
|
373
|
+
device: r.device,
|
|
374
|
+
}));
|
|
375
|
+
return { rows: mapped, nextCursor };
|
|
376
|
+
}
|
|
377
|
+
const VISITOR_SORT_COLUMNS = new Set([
|
|
378
|
+
'last_seen',
|
|
379
|
+
'first_seen',
|
|
380
|
+
'sessions',
|
|
381
|
+
'pageviews',
|
|
382
|
+
'country',
|
|
383
|
+
]);
|
|
384
|
+
function timestampToIso(value) {
|
|
385
|
+
if (value instanceof Date)
|
|
386
|
+
return value.toISOString();
|
|
387
|
+
return String(value ?? '');
|
|
388
|
+
}
|
|
389
|
+
export async function getVisitorsList(options, db) {
|
|
390
|
+
const dbRef = resolveDb(db);
|
|
391
|
+
if (!db) {
|
|
392
|
+
await ensureAnalyticsTable();
|
|
393
|
+
}
|
|
394
|
+
const days = periodToDays(options.period);
|
|
395
|
+
const now = new Date();
|
|
396
|
+
const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
|
397
|
+
const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
|
|
398
|
+
const fetchLimit = limit + 1;
|
|
399
|
+
const offset = options.cursor && /^\d+$/.test(options.cursor)
|
|
400
|
+
? Number(options.cursor)
|
|
401
|
+
: 0;
|
|
402
|
+
const sortBy = VISITOR_SORT_COLUMNS.has(options.sortBy)
|
|
403
|
+
? options.sortBy
|
|
404
|
+
: 'last_seen';
|
|
405
|
+
const sortDir = options.sortDir === 'asc' ? 'asc' : 'desc';
|
|
406
|
+
const orderClause = sortBy === 'last_seen'
|
|
407
|
+
? sortDir === 'asc'
|
|
408
|
+
? sql `last_seen ASC`
|
|
409
|
+
: sql `last_seen DESC`
|
|
410
|
+
: sortBy === 'first_seen'
|
|
411
|
+
? sortDir === 'asc'
|
|
412
|
+
? sql `first_seen ASC`
|
|
413
|
+
: sql `first_seen DESC`
|
|
414
|
+
: sortBy === 'sessions'
|
|
415
|
+
? sortDir === 'asc'
|
|
416
|
+
? sql `sessions ASC`
|
|
417
|
+
: sql `sessions DESC`
|
|
418
|
+
: sortBy === 'pageviews'
|
|
419
|
+
? sortDir === 'asc'
|
|
420
|
+
? sql `pageviews ASC`
|
|
421
|
+
: sql `pageviews DESC`
|
|
422
|
+
: sortDir === 'asc'
|
|
423
|
+
? sql `country ASC NULLS LAST`
|
|
424
|
+
: sql `country DESC NULLS LAST`;
|
|
425
|
+
// First-touch attribution via a correlated subquery scoped to the same
|
|
426
|
+
// period window. Cheap because (visitor_id, ts) is indexed.
|
|
427
|
+
const result = (await dbRef.execute(sql `
|
|
428
|
+
SELECT
|
|
429
|
+
ae.visitor_id,
|
|
430
|
+
MIN(ae.ts) AS first_seen,
|
|
431
|
+
MAX(ae.ts) AS last_seen,
|
|
432
|
+
COUNT(DISTINCT ae.session_id) AS sessions,
|
|
433
|
+
COUNT(*) FILTER (WHERE ae.event = 'page_view') AS pageviews,
|
|
434
|
+
COUNT(*) FILTER (WHERE ae.event = 'purchase') AS purchases,
|
|
435
|
+
MAX(ae.country) AS country,
|
|
436
|
+
(
|
|
437
|
+
SELECT COALESCE(NULLIF(sub.utm_source, ''),
|
|
438
|
+
NULLIF(sub.referrer, ''),
|
|
439
|
+
'direct')
|
|
440
|
+
FROM analytics_events sub
|
|
441
|
+
WHERE sub.visitor_id = ae.visitor_id
|
|
442
|
+
AND sub.ts >= ${start.toISOString()}
|
|
443
|
+
AND sub.ts < ${now.toISOString()}
|
|
444
|
+
ORDER BY sub.ts ASC
|
|
445
|
+
LIMIT 1
|
|
446
|
+
) AS initial_source
|
|
447
|
+
FROM analytics_events ae
|
|
448
|
+
WHERE ae.ts >= ${start.toISOString()} AND ae.ts < ${now.toISOString()}
|
|
449
|
+
GROUP BY ae.visitor_id
|
|
450
|
+
ORDER BY ${orderClause}
|
|
451
|
+
LIMIT ${fetchLimit}
|
|
452
|
+
OFFSET ${offset}
|
|
453
|
+
`));
|
|
454
|
+
const allRows = result.rows ?? [];
|
|
455
|
+
const hasMore = allRows.length > limit;
|
|
456
|
+
const slice = hasMore ? allRows.slice(0, limit) : allRows;
|
|
457
|
+
const nextCursor = hasMore ? String(offset + limit) : null;
|
|
458
|
+
const mapped = slice.map((r) => ({
|
|
459
|
+
visitorId: r.visitor_id,
|
|
460
|
+
firstSeen: timestampToIso(r.first_seen),
|
|
461
|
+
lastSeen: timestampToIso(r.last_seen),
|
|
462
|
+
sessions: Number(r.sessions),
|
|
463
|
+
pageviews: Number(r.pageviews),
|
|
464
|
+
purchases: Number(r.purchases),
|
|
465
|
+
country: r.country,
|
|
466
|
+
initialSource: r.initial_source ?? 'direct',
|
|
467
|
+
}));
|
|
468
|
+
return { rows: mapped, nextCursor };
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../../src/analytics/server/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/types
|
|
3
|
+
*
|
|
4
|
+
* Event vocabulary for the storefront analytics tracker. GA4-shaped.
|
|
5
|
+
*
|
|
6
|
+
* Discriminated union — every event type lists its required fields explicitly.
|
|
7
|
+
* The server's track endpoint validates against this shape; unknown events are
|
|
8
|
+
* rejected with 400. New events must be added BOTH to the union below AND to
|
|
9
|
+
* the CHECK constraint in `ensureAnalyticsTable()` (core/db/queries.ts).
|
|
10
|
+
*
|
|
11
|
+
* Identity fields (`visitor_id`, `session_id`) are supplied by the client
|
|
12
|
+
* tracker, not declared on each event. Server-derived fields (`device`,
|
|
13
|
+
* `country`, `ts`) are added during the INSERT and not part of the wire shape.
|
|
14
|
+
*/
|
|
15
|
+
/** Event kinds. Keep in lockstep with the SQL CHECK constraint
|
|
16
|
+
* (core/db/queries.ts `ensureAnalyticsTable`). */
|
|
17
|
+
export type AnalyticsEventKind = 'page_view' | 'session_start' | 'session_heartbeat' | 'session_end' | 'view_item_list' | 'view_item' | 'select_item' | 'add_to_cart' | 'view_cart' | 'begin_checkout' | 'add_shipping_info' | 'purchase' | 'refund';
|
|
18
|
+
/** Identity envelope present on every wire event. */
|
|
19
|
+
export interface AnalyticsIdentity {
|
|
20
|
+
visitor_id: string;
|
|
21
|
+
session_id: string;
|
|
22
|
+
}
|
|
23
|
+
/** Shared optional context every event may carry. */
|
|
24
|
+
export interface AnalyticsCommon {
|
|
25
|
+
path: string;
|
|
26
|
+
referrer?: string | null;
|
|
27
|
+
utm_source?: string | null;
|
|
28
|
+
utm_medium?: string | null;
|
|
29
|
+
utm_campaign?: string | null;
|
|
30
|
+
}
|
|
31
|
+
/** Per-event-type payload, before identity + server-derived fields are merged. */
|
|
32
|
+
export type AnalyticsPayload = ({
|
|
33
|
+
event: 'page_view';
|
|
34
|
+
} & AnalyticsCommon) | ({
|
|
35
|
+
event: 'session_start';
|
|
36
|
+
} & AnalyticsCommon) | ({
|
|
37
|
+
event: 'session_heartbeat';
|
|
38
|
+
} & AnalyticsCommon) | ({
|
|
39
|
+
event: 'session_end';
|
|
40
|
+
} & AnalyticsCommon) | ({
|
|
41
|
+
event: 'view_item_list';
|
|
42
|
+
category_id: string;
|
|
43
|
+
} & AnalyticsCommon) | ({
|
|
44
|
+
event: 'view_item';
|
|
45
|
+
product_id: string;
|
|
46
|
+
value_cents: number;
|
|
47
|
+
currency: string;
|
|
48
|
+
} & AnalyticsCommon) | ({
|
|
49
|
+
event: 'select_item';
|
|
50
|
+
product_id: string;
|
|
51
|
+
category_id?: string | null;
|
|
52
|
+
} & AnalyticsCommon) | ({
|
|
53
|
+
event: 'add_to_cart';
|
|
54
|
+
product_id: string;
|
|
55
|
+
variant_id?: string | null;
|
|
56
|
+
quantity: number;
|
|
57
|
+
value_cents: number;
|
|
58
|
+
currency: string;
|
|
59
|
+
} & AnalyticsCommon) | ({
|
|
60
|
+
event: 'view_cart';
|
|
61
|
+
value_cents: number;
|
|
62
|
+
currency: string;
|
|
63
|
+
} & AnalyticsCommon) | ({
|
|
64
|
+
event: 'begin_checkout';
|
|
65
|
+
value_cents: number;
|
|
66
|
+
currency: string;
|
|
67
|
+
} & AnalyticsCommon) | ({
|
|
68
|
+
event: 'add_shipping_info';
|
|
69
|
+
value_cents: number;
|
|
70
|
+
currency: string;
|
|
71
|
+
} & AnalyticsCommon) | ({
|
|
72
|
+
event: 'purchase';
|
|
73
|
+
order_id: string;
|
|
74
|
+
value_cents: number;
|
|
75
|
+
currency: string;
|
|
76
|
+
} & AnalyticsCommon) | ({
|
|
77
|
+
event: 'refund';
|
|
78
|
+
order_id: string;
|
|
79
|
+
value_cents: number;
|
|
80
|
+
currency: string;
|
|
81
|
+
} & AnalyticsCommon);
|
|
82
|
+
/** Full wire event = identity + payload. */
|
|
83
|
+
export type AnalyticsWireEvent = AnalyticsIdentity & AnalyticsPayload;
|
|
84
|
+
export type AnalyticsPeriod = 'today' | '7d' | '30d' | '90d';
|
|
85
|
+
/** Primary KPI row — visitors / sessions / pageviews / conversion. */
|
|
86
|
+
export interface AnalyticsOverview {
|
|
87
|
+
visitors: number;
|
|
88
|
+
sessions: number;
|
|
89
|
+
pageviews: number;
|
|
90
|
+
/** Decimal ratio. `purchases / sessions`. null when sessions=0. */
|
|
91
|
+
conversionRate: number | null;
|
|
92
|
+
}
|
|
93
|
+
export interface AnalyticsOverviewWithDeltas {
|
|
94
|
+
current: AnalyticsOverview;
|
|
95
|
+
prior: AnalyticsOverview;
|
|
96
|
+
/** Decimal delta against prior (e.g. +0.18 = +18%). null when prior=0. */
|
|
97
|
+
deltas: {
|
|
98
|
+
visitors: number | null;
|
|
99
|
+
sessions: number | null;
|
|
100
|
+
pageviews: number | null;
|
|
101
|
+
conversionRate: number | null;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export interface AnalyticsTrendPoint {
|
|
105
|
+
date: string;
|
|
106
|
+
visitors: number;
|
|
107
|
+
}
|
|
108
|
+
export interface AnalyticsTopPage {
|
|
109
|
+
path: string;
|
|
110
|
+
views: number;
|
|
111
|
+
}
|
|
112
|
+
export interface AnalyticsTopProduct {
|
|
113
|
+
productId: string;
|
|
114
|
+
views: number;
|
|
115
|
+
addsToCart: number;
|
|
116
|
+
purchases: number;
|
|
117
|
+
}
|
|
118
|
+
export interface AnalyticsSourceBucket {
|
|
119
|
+
source: string;
|
|
120
|
+
visitors: number;
|
|
121
|
+
}
|
|
122
|
+
export interface AnalyticsDeviceBucket {
|
|
123
|
+
device: string;
|
|
124
|
+
visitors: number;
|
|
125
|
+
}
|
|
126
|
+
export interface AnalyticsDashboardPayload {
|
|
127
|
+
period: AnalyticsPeriod;
|
|
128
|
+
overview: AnalyticsOverviewWithDeltas;
|
|
129
|
+
trend: AnalyticsTrendPoint[];
|
|
130
|
+
topPages: AnalyticsTopPage[];
|
|
131
|
+
topProducts: AnalyticsTopProduct[];
|
|
132
|
+
sources: AnalyticsSourceBucket[];
|
|
133
|
+
devices: AnalyticsDeviceBucket[];
|
|
134
|
+
}
|
|
135
|
+
export interface AnalyticsTrafficSummary {
|
|
136
|
+
visitors7d: number;
|
|
137
|
+
visitorsPrior7d: number;
|
|
138
|
+
/** Decimal delta against prior 7d. null when prior=0. */
|
|
139
|
+
delta: number | null;
|
|
140
|
+
/** Decimal ratio. null when sessions=0. */
|
|
141
|
+
conversionRate7d: number | null;
|
|
142
|
+
}
|
|
143
|
+
export interface AnalyticsEventLogRow {
|
|
144
|
+
/** Row id — opaque, used for cursor pagination. Encoded as string in JSON to
|
|
145
|
+
* preserve precision for clients (bigserial > Number.MAX_SAFE_INTEGER one
|
|
146
|
+
* day, far away but cheap to future-proof). */
|
|
147
|
+
id: string;
|
|
148
|
+
ts: string;
|
|
149
|
+
event: AnalyticsEventKind;
|
|
150
|
+
path: string;
|
|
151
|
+
referrer: string | null;
|
|
152
|
+
country: string | null;
|
|
153
|
+
sessionId: string;
|
|
154
|
+
visitorId: string;
|
|
155
|
+
/** Optional commerce context — populated for view_item / add_to_cart /
|
|
156
|
+
* purchase / refund / etc. */
|
|
157
|
+
productId: string | null;
|
|
158
|
+
valueCents: number | null;
|
|
159
|
+
currency: string | null;
|
|
160
|
+
device: string | null;
|
|
161
|
+
}
|
|
162
|
+
export type AnalyticsSortDir = 'asc' | 'desc';
|
|
163
|
+
export interface AnalyticsEventsLogPage {
|
|
164
|
+
rows: AnalyticsEventLogRow[];
|
|
165
|
+
/** Cursor for the next page (the `id` of the last row, ascending sort order).
|
|
166
|
+
* null when there's no more data. */
|
|
167
|
+
nextCursor: string | null;
|
|
168
|
+
}
|
|
169
|
+
export interface AnalyticsVisitorRow {
|
|
170
|
+
visitorId: string;
|
|
171
|
+
firstSeen: string;
|
|
172
|
+
lastSeen: string;
|
|
173
|
+
sessions: number;
|
|
174
|
+
pageviews: number;
|
|
175
|
+
purchases: number;
|
|
176
|
+
country: string | null;
|
|
177
|
+
/** First touch attribution — utm_source if present, else referrer host,
|
|
178
|
+
* else 'direct'. */
|
|
179
|
+
initialSource: string;
|
|
180
|
+
}
|
|
181
|
+
export interface AnalyticsVisitorsPage {
|
|
182
|
+
rows: AnalyticsVisitorRow[];
|
|
183
|
+
/** Offset cursor (string for symmetry with events). null when no more. */
|
|
184
|
+
nextCursor: string | null;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=types.d.ts.map
|