@nodewrite-labs/analytics-sdk 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +98 -2
- package/dist/chunk-F7BY2JQ3.js +9 -0
- package/dist/chunk-GVOESWHZ.js +270 -0
- package/dist/chunk-GWOVVKP3.js +179 -0
- package/dist/chunk-Q4JRXZ6M.js +132 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.js +219 -0
- package/dist/event-body-fpV2mYpY.d.ts +18 -0
- package/dist/event-store-BhisLdw_.d.ts +72 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +49 -0
- package/dist/options-D3s0dcJ-.d.ts +19 -0
- package/dist/payload/admin-view.d.ts +6 -0
- package/dist/payload/admin-view.js +290 -0
- package/dist/payload/index.d.ts +70 -0
- package/dist/payload/index.js +100 -0
- package/dist/payload/nav-link.d.ts +5 -0
- package/dist/payload/nav-link.js +8 -0
- package/dist/postgres/index.d.ts +17 -0
- package/dist/postgres/index.js +156 -0
- package/dist/server/index.d.ts +44 -0
- package/dist/server/index.js +9 -0
- package/package.json +88 -4
- package/src/postgres/schema.sql +12 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalyticsEventKind
|
|
3
|
+
} from "./chunk-F7BY2JQ3.js";
|
|
4
|
+
|
|
5
|
+
// src/core/duration-seconds.ts
|
|
6
|
+
var MIN_DURATION_SECONDS = 0;
|
|
7
|
+
var MAX_DURATION_SECONDS = 1800;
|
|
8
|
+
function acceptDurationSeconds(value) {
|
|
9
|
+
if (!Number.isFinite(value)) return null;
|
|
10
|
+
if (value < MIN_DURATION_SECONDS || value > MAX_DURATION_SECONDS) return null;
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/core/summarize-events.ts
|
|
15
|
+
function summarizeAnalyticsEvents(rows, query, modules) {
|
|
16
|
+
const filtered = rows.filter((row) => inQuery(row, query));
|
|
17
|
+
const views = filtered.filter((row) => row.kind === AnalyticsEventKind.VIEW);
|
|
18
|
+
const summary = {
|
|
19
|
+
totalViews: views.length,
|
|
20
|
+
averageDurationSeconds: meanDuration(views),
|
|
21
|
+
byPath: groupByPath(views)
|
|
22
|
+
};
|
|
23
|
+
if (modules.country) {
|
|
24
|
+
summary.byCountry = groupByCountry(views);
|
|
25
|
+
}
|
|
26
|
+
if (modules.city) {
|
|
27
|
+
summary.byCity = groupByCity(views);
|
|
28
|
+
}
|
|
29
|
+
if (modules.sessions) {
|
|
30
|
+
summary.sessions = groupSessions(filtered);
|
|
31
|
+
if (query.sessionId !== null) {
|
|
32
|
+
summary.events = filtered.slice().sort(byOccurredAt).map((row) => ({
|
|
33
|
+
id: row.id,
|
|
34
|
+
kind: row.kind,
|
|
35
|
+
path: row.path,
|
|
36
|
+
occurredAt: row.occurredAt,
|
|
37
|
+
eventName: row.eventName
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (modules.clicks) {
|
|
42
|
+
summary.byEventName = groupByEventName(filtered);
|
|
43
|
+
}
|
|
44
|
+
return summary;
|
|
45
|
+
}
|
|
46
|
+
function inQuery(row, query) {
|
|
47
|
+
if (row.occurredAt < query.from || row.occurredAt > query.to) return false;
|
|
48
|
+
if (query.country !== null && row.country !== query.country) return false;
|
|
49
|
+
if (query.path !== null && row.path !== query.path) return false;
|
|
50
|
+
if (query.sessionId !== null && row.sessionId !== query.sessionId) return false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function meanDuration(views) {
|
|
54
|
+
let total = 0;
|
|
55
|
+
let count = 0;
|
|
56
|
+
for (const view of views) {
|
|
57
|
+
if (view.durationSeconds === null) continue;
|
|
58
|
+
total += view.durationSeconds;
|
|
59
|
+
count += 1;
|
|
60
|
+
}
|
|
61
|
+
if (count === 0) return 0;
|
|
62
|
+
return total / count;
|
|
63
|
+
}
|
|
64
|
+
function groupByPath(views) {
|
|
65
|
+
const groups = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const view of views) {
|
|
67
|
+
const existing = groups.get(view.path);
|
|
68
|
+
if (existing) existing.push(view);
|
|
69
|
+
else groups.set(view.path, [view]);
|
|
70
|
+
}
|
|
71
|
+
return [...groups.entries()].sort(([left], [right]) => compareString(left, right)).map(([path, pathViews]) => ({
|
|
72
|
+
path,
|
|
73
|
+
views: pathViews.length,
|
|
74
|
+
averageDurationSeconds: meanDuration(pathViews)
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function groupByCountry(views) {
|
|
78
|
+
const counts = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const view of views) {
|
|
80
|
+
if (view.country === null) continue;
|
|
81
|
+
counts.set(view.country, (counts.get(view.country) ?? 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
return [...counts.entries()].map(([country, viewCount]) => ({ country, views: viewCount })).sort((left, right) => compareString(left.country, right.country));
|
|
84
|
+
}
|
|
85
|
+
function groupByCity(views) {
|
|
86
|
+
const counts = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const view of views) {
|
|
88
|
+
if (view.city === null) continue;
|
|
89
|
+
counts.set(view.city, (counts.get(view.city) ?? 0) + 1);
|
|
90
|
+
}
|
|
91
|
+
return [...counts.entries()].map(([city, viewCount]) => ({ city, views: viewCount })).sort((left, right) => compareString(left.city, right.city));
|
|
92
|
+
}
|
|
93
|
+
function groupSessions(rows) {
|
|
94
|
+
const groups = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
if (row.sessionId === null) continue;
|
|
97
|
+
const existing = groups.get(row.sessionId);
|
|
98
|
+
if (!existing) {
|
|
99
|
+
groups.set(row.sessionId, {
|
|
100
|
+
startedAt: row.occurredAt,
|
|
101
|
+
pageCount: row.kind === AnalyticsEventKind.VIEW ? 1 : 0
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (row.occurredAt < existing.startedAt) existing.startedAt = row.occurredAt;
|
|
106
|
+
if (row.kind === AnalyticsEventKind.VIEW) existing.pageCount += 1;
|
|
107
|
+
}
|
|
108
|
+
return [...groups.entries()].map(([id, group]) => ({ id, startedAt: group.startedAt, pageCount: group.pageCount })).sort(
|
|
109
|
+
(left, right) => compareString(left.startedAt, right.startedAt) || compareString(left.id, right.id)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function groupByEventName(rows) {
|
|
113
|
+
const counts = /* @__PURE__ */ new Map();
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
if (row.kind !== AnalyticsEventKind.CLICK || row.eventName === null) continue;
|
|
116
|
+
counts.set(row.eventName, (counts.get(row.eventName) ?? 0) + 1);
|
|
117
|
+
}
|
|
118
|
+
return [...counts.entries()].map(([eventName, count]) => ({ eventName, count })).sort((left, right) => compareString(left.eventName, right.eventName));
|
|
119
|
+
}
|
|
120
|
+
function byOccurredAt(left, right) {
|
|
121
|
+
return compareString(left.occurredAt, right.occurredAt) || compareString(left.id, right.id);
|
|
122
|
+
}
|
|
123
|
+
function compareString(left, right) {
|
|
124
|
+
if (left < right) return -1;
|
|
125
|
+
if (left > right) return 1;
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
acceptDurationSeconds,
|
|
131
|
+
summarizeAnalyticsEvents
|
|
132
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { A as AnalyticsClientConfig } from '../options-D3s0dcJ-.js';
|
|
2
|
+
export { A as AnalyticsEventBody } from '../event-body-fpV2mYpY.js';
|
|
3
|
+
|
|
4
|
+
declare function recordNamedClick(name: string): void;
|
|
5
|
+
|
|
6
|
+
declare function startAnalytics(config: AnalyticsClientConfig): void;
|
|
7
|
+
|
|
8
|
+
export { AnalyticsClientConfig, recordNamedClick, startAnalytics };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// src/client/record-named-click.ts
|
|
2
|
+
var EVENT_NAME_PATTERN = /^[a-z0-9_]+$/;
|
|
3
|
+
var active = null;
|
|
4
|
+
function bindAnalyticsClient(client) {
|
|
5
|
+
active = client;
|
|
6
|
+
}
|
|
7
|
+
function recordNamedClick(name) {
|
|
8
|
+
if (active === null || !active.clicks) return;
|
|
9
|
+
if (!EVENT_NAME_PATTERN.test(name)) return;
|
|
10
|
+
active.send({
|
|
11
|
+
type: "click",
|
|
12
|
+
path: location.pathname,
|
|
13
|
+
locale: active.locale,
|
|
14
|
+
sessionId: active.sessionId,
|
|
15
|
+
eventName: name
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/client/session-cookie.ts
|
|
20
|
+
var COOKIE_NAME = "analytics_session";
|
|
21
|
+
var ONE_YEAR_SECONDS = 31536e3;
|
|
22
|
+
function readSessionId() {
|
|
23
|
+
const cookies = document.cookie ? document.cookie.split(";") : [];
|
|
24
|
+
for (const cookie of cookies) {
|
|
25
|
+
const separator = cookie.indexOf("=");
|
|
26
|
+
if (separator === -1) continue;
|
|
27
|
+
const name = cookie.slice(0, separator).trim();
|
|
28
|
+
if (name !== COOKIE_NAME) continue;
|
|
29
|
+
return decodeCookieValue(cookie.slice(separator + 1).trim());
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function ensureSessionId() {
|
|
34
|
+
const existing = readSessionId();
|
|
35
|
+
if (existing !== null) return existing;
|
|
36
|
+
const id = crypto.randomUUID();
|
|
37
|
+
document.cookie = `${COOKIE_NAME}=${id}; SameSite=Lax; Path=/; Max-Age=${ONE_YEAR_SECONDS}`;
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
function decodeCookieValue(value) {
|
|
41
|
+
if (value.length === 0) return null;
|
|
42
|
+
try {
|
|
43
|
+
const decoded = decodeURIComponent(value);
|
|
44
|
+
return decoded.length > 0 ? decoded : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/client/start-analytics.ts
|
|
51
|
+
var MAX_DURATION_SECONDS = 1800;
|
|
52
|
+
var endpoint = "";
|
|
53
|
+
var locale = null;
|
|
54
|
+
var sessionId = null;
|
|
55
|
+
var viewGeneration = 0;
|
|
56
|
+
var currentPath = null;
|
|
57
|
+
var currentViewId = null;
|
|
58
|
+
var viewSentAt = 0;
|
|
59
|
+
var restoreHistory = null;
|
|
60
|
+
var removePopState = null;
|
|
61
|
+
var removePageHide = null;
|
|
62
|
+
var removeClick = null;
|
|
63
|
+
function startAnalytics(config) {
|
|
64
|
+
stopAnalytics();
|
|
65
|
+
if (!config.enabled) return;
|
|
66
|
+
endpoint = config.endpoint;
|
|
67
|
+
locale = config.locale ?? null;
|
|
68
|
+
sessionId = config.sessions ? ensureSessionId() : null;
|
|
69
|
+
bindAnalyticsClient({
|
|
70
|
+
locale,
|
|
71
|
+
sessionId,
|
|
72
|
+
clicks: config.clicks,
|
|
73
|
+
send: (body) => {
|
|
74
|
+
void postJson(endpoint, body);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
installHistory();
|
|
78
|
+
installPageHide();
|
|
79
|
+
if (config.clicks) installClickListener();
|
|
80
|
+
void sendView();
|
|
81
|
+
}
|
|
82
|
+
function stopAnalytics() {
|
|
83
|
+
viewGeneration += 1;
|
|
84
|
+
currentViewId = null;
|
|
85
|
+
currentPath = null;
|
|
86
|
+
viewSentAt = 0;
|
|
87
|
+
bindAnalyticsClient(null);
|
|
88
|
+
removeClick?.();
|
|
89
|
+
removeClick = null;
|
|
90
|
+
removePageHide?.();
|
|
91
|
+
removePageHide = null;
|
|
92
|
+
removePopState?.();
|
|
93
|
+
removePopState = null;
|
|
94
|
+
restoreHistory?.();
|
|
95
|
+
restoreHistory = null;
|
|
96
|
+
}
|
|
97
|
+
function installHistory() {
|
|
98
|
+
const originalPushState = history.pushState.bind(history);
|
|
99
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
100
|
+
history.pushState = (data, unused, url) => {
|
|
101
|
+
originalPushState(data, unused, url);
|
|
102
|
+
void onPathChange();
|
|
103
|
+
};
|
|
104
|
+
history.replaceState = (data, unused, url) => {
|
|
105
|
+
originalReplaceState(data, unused, url);
|
|
106
|
+
void onPathChange();
|
|
107
|
+
};
|
|
108
|
+
const onPopState = () => {
|
|
109
|
+
void onPathChange();
|
|
110
|
+
};
|
|
111
|
+
window.addEventListener("popstate", onPopState);
|
|
112
|
+
restoreHistory = () => {
|
|
113
|
+
history.pushState = originalPushState;
|
|
114
|
+
history.replaceState = originalReplaceState;
|
|
115
|
+
};
|
|
116
|
+
removePopState = () => {
|
|
117
|
+
window.removeEventListener("popstate", onPopState);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function installPageHide() {
|
|
121
|
+
const onPageHide = () => {
|
|
122
|
+
if (currentViewId === null) return;
|
|
123
|
+
const body = {
|
|
124
|
+
type: "duration",
|
|
125
|
+
id: currentViewId,
|
|
126
|
+
durationSeconds: capDuration((Date.now() - viewSentAt) / 1e3)
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
navigator.sendBeacon(endpoint, new Blob([JSON.stringify(body)], { type: "application/json" }));
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
window.addEventListener("pagehide", onPageHide);
|
|
134
|
+
removePageHide = () => {
|
|
135
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function installClickListener() {
|
|
139
|
+
const onClick = (event) => {
|
|
140
|
+
const target = event.target;
|
|
141
|
+
if (!(target instanceof Element)) return;
|
|
142
|
+
const marked = target.closest("[data-analytics]");
|
|
143
|
+
if (marked === null) return;
|
|
144
|
+
const name = marked.getAttribute("data-analytics");
|
|
145
|
+
if (name === null) return;
|
|
146
|
+
recordNamedClick(name);
|
|
147
|
+
};
|
|
148
|
+
document.addEventListener("click", onClick);
|
|
149
|
+
removeClick = () => {
|
|
150
|
+
document.removeEventListener("click", onClick);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function onPathChange() {
|
|
154
|
+
const path = location.pathname;
|
|
155
|
+
if (path === currentPath) return;
|
|
156
|
+
const previousId = currentViewId;
|
|
157
|
+
const previousSentAt = viewSentAt;
|
|
158
|
+
viewGeneration += 1;
|
|
159
|
+
currentViewId = null;
|
|
160
|
+
currentPath = path;
|
|
161
|
+
if (previousId !== null) {
|
|
162
|
+
const durationBody = {
|
|
163
|
+
type: "duration",
|
|
164
|
+
id: previousId,
|
|
165
|
+
durationSeconds: capDuration((Date.now() - previousSentAt) / 1e3)
|
|
166
|
+
};
|
|
167
|
+
try {
|
|
168
|
+
await postJson(endpoint, durationBody);
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (location.pathname !== path) return;
|
|
173
|
+
await sendView();
|
|
174
|
+
}
|
|
175
|
+
async function sendView() {
|
|
176
|
+
const generation = ++viewGeneration;
|
|
177
|
+
const path = location.pathname;
|
|
178
|
+
currentPath = path;
|
|
179
|
+
const sentAt = Date.now();
|
|
180
|
+
viewSentAt = sentAt;
|
|
181
|
+
currentViewId = null;
|
|
182
|
+
const body = {
|
|
183
|
+
type: "view",
|
|
184
|
+
path,
|
|
185
|
+
locale,
|
|
186
|
+
sessionId
|
|
187
|
+
};
|
|
188
|
+
try {
|
|
189
|
+
const response = await postJson(endpoint, body);
|
|
190
|
+
const id = await readViewId(response);
|
|
191
|
+
if (generation !== viewGeneration) return;
|
|
192
|
+
currentViewId = id;
|
|
193
|
+
} catch {
|
|
194
|
+
if (generation !== viewGeneration) return;
|
|
195
|
+
currentViewId = null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function postJson(url, body) {
|
|
199
|
+
return fetch(url, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { "Content-Type": "application/json" },
|
|
202
|
+
body: JSON.stringify(body)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
async function readViewId(response) {
|
|
206
|
+
const payload = await response.json();
|
|
207
|
+
if (typeof payload !== "object" || payload === null || !("id" in payload)) return null;
|
|
208
|
+
const id = payload.id;
|
|
209
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
210
|
+
}
|
|
211
|
+
function capDuration(seconds) {
|
|
212
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return 0;
|
|
213
|
+
if (seconds >= MAX_DURATION_SECONDS) return MAX_DURATION_SECONDS;
|
|
214
|
+
return seconds;
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
recordNamedClick,
|
|
218
|
+
startAnalytics
|
|
219
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type AnalyticsEventBody = {
|
|
2
|
+
type: "view";
|
|
3
|
+
path: string;
|
|
4
|
+
locale: string | null;
|
|
5
|
+
sessionId: string | null;
|
|
6
|
+
} | {
|
|
7
|
+
type: "duration";
|
|
8
|
+
id: string;
|
|
9
|
+
durationSeconds: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: "click";
|
|
12
|
+
path: string;
|
|
13
|
+
locale: string | null;
|
|
14
|
+
sessionId: string | null;
|
|
15
|
+
eventName: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type { AnalyticsEventBody as A };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { a as AnalyticsModules } from './options-D3s0dcJ-.js';
|
|
2
|
+
|
|
3
|
+
declare const AnalyticsEventKind: {
|
|
4
|
+
readonly VIEW: "view";
|
|
5
|
+
readonly CLICK: "click";
|
|
6
|
+
};
|
|
7
|
+
type AnalyticsEventKind = (typeof AnalyticsEventKind)[keyof typeof AnalyticsEventKind];
|
|
8
|
+
|
|
9
|
+
interface NewAnalyticsEvent {
|
|
10
|
+
kind: AnalyticsEventKind;
|
|
11
|
+
path: string;
|
|
12
|
+
locale: string | null;
|
|
13
|
+
durationSeconds: number | null;
|
|
14
|
+
country: string | null;
|
|
15
|
+
city: string | null;
|
|
16
|
+
sessionId: string | null;
|
|
17
|
+
eventName: string | null;
|
|
18
|
+
}
|
|
19
|
+
interface StoredAnalyticsEvent extends NewAnalyticsEvent {
|
|
20
|
+
id: string;
|
|
21
|
+
occurredAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AnalyticsSummaryQuery {
|
|
25
|
+
from: string;
|
|
26
|
+
to: string;
|
|
27
|
+
country: string | null;
|
|
28
|
+
path: string | null;
|
|
29
|
+
sessionId: string | null;
|
|
30
|
+
}
|
|
31
|
+
interface AnalyticsSummary {
|
|
32
|
+
totalViews: number;
|
|
33
|
+
averageDurationSeconds: number;
|
|
34
|
+
byPath: {
|
|
35
|
+
path: string;
|
|
36
|
+
views: number;
|
|
37
|
+
averageDurationSeconds: number;
|
|
38
|
+
}[];
|
|
39
|
+
byCountry?: {
|
|
40
|
+
country: string;
|
|
41
|
+
views: number;
|
|
42
|
+
}[];
|
|
43
|
+
byCity?: {
|
|
44
|
+
city: string;
|
|
45
|
+
views: number;
|
|
46
|
+
}[];
|
|
47
|
+
sessions?: {
|
|
48
|
+
id: string;
|
|
49
|
+
startedAt: string;
|
|
50
|
+
pageCount: number;
|
|
51
|
+
}[];
|
|
52
|
+
events?: {
|
|
53
|
+
id: string;
|
|
54
|
+
kind: AnalyticsEventKind;
|
|
55
|
+
path: string;
|
|
56
|
+
occurredAt: string;
|
|
57
|
+
eventName: string | null;
|
|
58
|
+
}[];
|
|
59
|
+
byEventName?: {
|
|
60
|
+
eventName: string;
|
|
61
|
+
count: number;
|
|
62
|
+
}[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface AnalyticsEventStore {
|
|
66
|
+
insertAnalyticsEvent(event: NewAnalyticsEvent, occurredAt: string): Promise<StoredAnalyticsEvent>;
|
|
67
|
+
recordAnalyticsDuration(id: string, durationSeconds: number): Promise<void>;
|
|
68
|
+
summarizeAnalytics(query: AnalyticsSummaryQuery, modules: AnalyticsModules): Promise<AnalyticsSummary>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { AnalyticsEventKind as c };
|
|
72
|
+
export type { AnalyticsSummaryQuery as A, NewAnalyticsEvent as N, StoredAnalyticsEvent as S, AnalyticsSummary as a, AnalyticsEventStore as b };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { S as StoredAnalyticsEvent, A as AnalyticsSummaryQuery, a as AnalyticsSummary, b as AnalyticsEventStore, N as NewAnalyticsEvent } from './event-store-BhisLdw_.js';
|
|
2
|
+
export { c as AnalyticsEventKind } from './event-store-BhisLdw_.js';
|
|
3
|
+
import { a as AnalyticsModules } from './options-D3s0dcJ-.js';
|
|
4
|
+
export { A as AnalyticsClientConfig, b as AnalyticsServerOptions } from './options-D3s0dcJ-.js';
|
|
5
|
+
export { A as AnalyticsEventBody } from './event-body-fpV2mYpY.js';
|
|
6
|
+
|
|
7
|
+
/** Keeps a duration inside 0..1800. NaN and infinities are rejected. */
|
|
8
|
+
declare function acceptDurationSeconds(value: number): number | null;
|
|
9
|
+
|
|
10
|
+
declare function summarizeAnalyticsEvents(rows: readonly StoredAnalyticsEvent[], query: AnalyticsSummaryQuery, modules: AnalyticsModules): AnalyticsSummary;
|
|
11
|
+
|
|
12
|
+
declare class MemoryAnalyticsEventStore implements AnalyticsEventStore {
|
|
13
|
+
private readonly modules;
|
|
14
|
+
private readonly rows;
|
|
15
|
+
constructor(modules: AnalyticsModules);
|
|
16
|
+
insertAnalyticsEvent(event: NewAnalyticsEvent, occurredAt: string): Promise<StoredAnalyticsEvent>;
|
|
17
|
+
recordAnalyticsDuration(id: string, durationSeconds: number): Promise<void>;
|
|
18
|
+
summarizeAnalytics(query: AnalyticsSummaryQuery, modules: AnalyticsModules): Promise<AnalyticsSummary>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { AnalyticsEventStore, AnalyticsModules, AnalyticsSummary, AnalyticsSummaryQuery, MemoryAnalyticsEventStore, NewAnalyticsEvent, StoredAnalyticsEvent, acceptDurationSeconds, summarizeAnalyticsEvents };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acceptDurationSeconds,
|
|
3
|
+
summarizeAnalyticsEvents
|
|
4
|
+
} from "./chunk-Q4JRXZ6M.js";
|
|
5
|
+
import {
|
|
6
|
+
AnalyticsEventKind
|
|
7
|
+
} from "./chunk-F7BY2JQ3.js";
|
|
8
|
+
|
|
9
|
+
// src/core/memory-event-store.ts
|
|
10
|
+
import { randomUUID } from "crypto";
|
|
11
|
+
var MemoryAnalyticsEventStore = class {
|
|
12
|
+
constructor(modules) {
|
|
13
|
+
this.modules = modules;
|
|
14
|
+
}
|
|
15
|
+
modules;
|
|
16
|
+
rows = [];
|
|
17
|
+
async insertAnalyticsEvent(event, occurredAt) {
|
|
18
|
+
const stored = {
|
|
19
|
+
kind: event.kind,
|
|
20
|
+
path: event.path,
|
|
21
|
+
locale: event.locale,
|
|
22
|
+
durationSeconds: event.durationSeconds,
|
|
23
|
+
country: this.modules.country ? event.country : null,
|
|
24
|
+
city: this.modules.city ? event.city : null,
|
|
25
|
+
sessionId: this.modules.sessions ? event.sessionId : null,
|
|
26
|
+
eventName: this.modules.clicks ? event.eventName : null,
|
|
27
|
+
id: randomUUID(),
|
|
28
|
+
occurredAt
|
|
29
|
+
};
|
|
30
|
+
this.rows.push(stored);
|
|
31
|
+
return stored;
|
|
32
|
+
}
|
|
33
|
+
async recordAnalyticsDuration(id, durationSeconds) {
|
|
34
|
+
const row = this.rows.find((candidate) => candidate.id === id);
|
|
35
|
+
if (!row || row.kind !== AnalyticsEventKind.VIEW) return;
|
|
36
|
+
const accepted = acceptDurationSeconds(durationSeconds);
|
|
37
|
+
if (accepted === null) return;
|
|
38
|
+
row.durationSeconds = accepted;
|
|
39
|
+
}
|
|
40
|
+
async summarizeAnalytics(query, modules) {
|
|
41
|
+
return summarizeAnalyticsEvents(this.rows, query, modules);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
AnalyticsEventKind,
|
|
46
|
+
MemoryAnalyticsEventStore,
|
|
47
|
+
acceptDurationSeconds,
|
|
48
|
+
summarizeAnalyticsEvents
|
|
49
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface AnalyticsModules {
|
|
2
|
+
country: boolean;
|
|
3
|
+
city: boolean;
|
|
4
|
+
sessions: boolean;
|
|
5
|
+
clicks: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface AnalyticsServerOptions {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
modules: AnalyticsModules;
|
|
10
|
+
}
|
|
11
|
+
interface AnalyticsClientConfig {
|
|
12
|
+
endpoint: string;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
locale?: string;
|
|
15
|
+
sessions: boolean;
|
|
16
|
+
clicks: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type { AnalyticsClientConfig as A, AnalyticsModules as a, AnalyticsServerOptions as b };
|