locallytics 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/adapters/drizzle.d.mts +25 -0
  2. package/dist/adapters/drizzle.d.ts +25 -0
  3. package/dist/adapters/drizzle.js +38 -0
  4. package/dist/adapters/drizzle.mjs +7 -0
  5. package/dist/adapters/prisma.d.mts +25 -0
  6. package/dist/adapters/prisma.d.ts +25 -0
  7. package/dist/adapters/prisma.js +38 -0
  8. package/dist/adapters/prisma.mjs +7 -0
  9. package/dist/adapters.d.mts +7 -0
  10. package/dist/adapters.d.ts +7 -0
  11. package/dist/adapters.js +39 -0
  12. package/dist/adapters.mjs +8 -0
  13. package/dist/browser.d.mts +41 -0
  14. package/dist/browser.d.ts +41 -0
  15. package/dist/browser.js +79 -0
  16. package/dist/browser.mjs +10 -0
  17. package/dist/index.d.mts +59 -0
  18. package/dist/index.d.ts +59 -6
  19. package/dist/index.js +367 -7
  20. package/dist/index.mjs +336 -0
  21. package/dist/react.d.mts +48 -0
  22. package/dist/react.d.ts +48 -0
  23. package/dist/react.js +132 -0
  24. package/dist/react.mjs +61 -0
  25. package/dist/shared/chunk-8tbv1rg5.js +45 -0
  26. package/package.json +60 -48
  27. package/README.md +0 -73
  28. package/dist/client/LocallyticsGrabber.d.ts +0 -30
  29. package/dist/client/LocallyticsGrabber.d.ts.map +0 -1
  30. package/dist/client/LocallyticsGrabber.js +0 -71
  31. package/dist/client/LocallyticsGrabber.js.map +0 -1
  32. package/dist/client/batcher.d.ts +0 -48
  33. package/dist/client/batcher.d.ts.map +0 -1
  34. package/dist/client/batcher.js +0 -134
  35. package/dist/client/batcher.js.map +0 -1
  36. package/dist/client/tracker.d.ts +0 -18
  37. package/dist/client/tracker.d.ts.map +0 -1
  38. package/dist/client/tracker.js +0 -101
  39. package/dist/client/tracker.js.map +0 -1
  40. package/dist/db/factory.d.ts +0 -11
  41. package/dist/db/factory.d.ts.map +0 -1
  42. package/dist/db/factory.js +0 -46
  43. package/dist/db/factory.js.map +0 -1
  44. package/dist/db/mysql.d.ts +0 -17
  45. package/dist/db/mysql.d.ts.map +0 -1
  46. package/dist/db/mysql.js +0 -144
  47. package/dist/db/mysql.js.map +0 -1
  48. package/dist/db/postgres.d.ts +0 -16
  49. package/dist/db/postgres.d.ts.map +0 -1
  50. package/dist/db/postgres.js +0 -142
  51. package/dist/db/postgres.js.map +0 -1
  52. package/dist/db/sqlite.d.ts +0 -17
  53. package/dist/db/sqlite.d.ts.map +0 -1
  54. package/dist/db/sqlite.js +0 -130
  55. package/dist/db/sqlite.js.map +0 -1
  56. package/dist/index.d.ts.map +0 -1
  57. package/dist/index.js.map +0 -1
  58. package/dist/server/handlers.d.ts +0 -10
  59. package/dist/server/handlers.d.ts.map +0 -1
  60. package/dist/server/handlers.js +0 -96
  61. package/dist/server/handlers.js.map +0 -1
  62. package/dist/server/index.d.ts +0 -42
  63. package/dist/server/index.d.ts.map +0 -1
  64. package/dist/server/index.js +0 -96
  65. package/dist/server/index.js.map +0 -1
  66. package/dist/server/queries.d.ts +0 -10
  67. package/dist/server/queries.d.ts.map +0 -1
  68. package/dist/server/queries.js +0 -24
  69. package/dist/server/queries.js.map +0 -1
  70. package/dist/server/validator.d.ts +0 -32
  71. package/dist/server/validator.d.ts.map +0 -1
  72. package/dist/server/validator.js +0 -129
  73. package/dist/server/validator.js.map +0 -1
  74. package/dist/types/index.d.ts +0 -135
  75. package/dist/types/index.d.ts.map +0 -1
  76. package/dist/types/index.js +0 -24
  77. package/dist/types/index.js.map +0 -1
  78. package/dist/utils/hash.d.ts +0 -16
  79. package/dist/utils/hash.d.ts.map +0 -1
  80. package/dist/utils/hash.js +0 -36
  81. package/dist/utils/hash.js.map +0 -1
  82. package/dist/utils/rate-limit.d.ts +0 -32
  83. package/dist/utils/rate-limit.d.ts.map +0 -1
  84. package/dist/utils/rate-limit.js +0 -73
  85. package/dist/utils/rate-limit.js.map +0 -1
  86. package/src/db/schema-mysql.sql +0 -17
  87. package/src/db/schema-sqlite.sql +0 -29
  88. package/src/db/schema.sql +0 -35
package/dist/index.js CHANGED
@@ -1,7 +1,367 @@
1
- // Server exports
2
- // Client exports
3
- export { LocallyticsGrabber } from "./client/LocallyticsGrabber.js";
4
- export { createDatabase } from "./db/factory.js";
5
- export { LocallyticsData, locallytics } from "./server/index.js";
6
- export { LocallyticsError } from "./types/index.js";
7
- //# sourceMappingURL=index.js.map
1
+ var import_node_module = require("node:module");
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // src/index.ts
31
+ var exports_src = {};
32
+ __export(exports_src, {
33
+ createLocallytics: () => createLocallytics
34
+ });
35
+ module.exports = __toCommonJS(exports_src);
36
+ function toError(error) {
37
+ return error instanceof Error ? error : new Error(String(error));
38
+ }
39
+ function isRecord(value) {
40
+ return typeof value === "object" && value !== null;
41
+ }
42
+ function parseMetadata(value) {
43
+ if (value == null) {
44
+ return;
45
+ }
46
+ if (isRecord(value)) {
47
+ return value;
48
+ }
49
+ if (typeof value === "string") {
50
+ try {
51
+ const parsed = JSON.parse(value);
52
+ return isRecord(parsed) ? parsed : undefined;
53
+ } catch {
54
+ return;
55
+ }
56
+ }
57
+ return;
58
+ }
59
+ function normalizeEventRow(row) {
60
+ return {
61
+ id: row.id,
62
+ type: String(row.type ?? ""),
63
+ timestamp: Number(row.timestamp ?? Date.now()),
64
+ sessionId: row.session_id ?? row.sessionId,
65
+ userId: row.user_id ?? row.userId,
66
+ metadata: parseMetadata(row.metadata)
67
+ };
68
+ }
69
+ function stringifyMetadata(metadata) {
70
+ return metadata ? JSON.stringify(metadata) : null;
71
+ }
72
+ function isDatabaseAdapter(database) {
73
+ return "insertEvent" in database && typeof database.insertEvent === "function" && "findEvents" in database && typeof database.findEvents === "function";
74
+ }
75
+ function isPostgresLikeDatabase(database) {
76
+ return "query" in database && typeof database.query === "function";
77
+ }
78
+ function isMysqlLikeDatabase(database) {
79
+ return "execute" in database && typeof database.execute === "function";
80
+ }
81
+ function isSqliteLikeDatabase(database) {
82
+ return "prepare" in database && typeof database.prepare === "function";
83
+ }
84
+ function createPostgresAdapter(database) {
85
+ let initialized = false;
86
+ async function ensureInitialized() {
87
+ if (initialized) {
88
+ return;
89
+ }
90
+ await database.query(`
91
+ CREATE TABLE IF NOT EXISTS locallytics_events (
92
+ id BIGSERIAL PRIMARY KEY,
93
+ type TEXT NOT NULL,
94
+ timestamp BIGINT NOT NULL,
95
+ session_id TEXT,
96
+ user_id TEXT,
97
+ metadata JSONB
98
+ )
99
+ `);
100
+ initialized = true;
101
+ }
102
+ return {
103
+ initialize: ensureInitialized,
104
+ async insertEvent(event) {
105
+ await ensureInitialized();
106
+ const result = await database.query(`
107
+ INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
108
+ VALUES ($1, $2, $3, $4, $5::jsonb)
109
+ RETURNING id, type, timestamp, session_id, user_id, metadata
110
+ `, [
111
+ event.type,
112
+ event.timestamp,
113
+ event.sessionId ?? null,
114
+ event.userId ?? null,
115
+ stringifyMetadata(event.metadata)
116
+ ]);
117
+ const first = result.rows[0];
118
+ return first ? normalizeEventRow(first) : { ...event };
119
+ },
120
+ async findEvents() {
121
+ await ensureInitialized();
122
+ const result = await database.query(`
123
+ SELECT id, type, timestamp, session_id, user_id, metadata
124
+ FROM locallytics_events
125
+ ORDER BY timestamp DESC
126
+ `);
127
+ return result.rows.map(normalizeEventRow);
128
+ }
129
+ };
130
+ }
131
+ function createMysqlAdapter(database) {
132
+ let initialized = false;
133
+ async function ensureInitialized() {
134
+ if (initialized) {
135
+ return;
136
+ }
137
+ await database.execute(`
138
+ CREATE TABLE IF NOT EXISTS locallytics_events (
139
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
140
+ type VARCHAR(255) NOT NULL,
141
+ timestamp BIGINT NOT NULL,
142
+ session_id VARCHAR(255),
143
+ user_id VARCHAR(255),
144
+ metadata JSON
145
+ )
146
+ `);
147
+ initialized = true;
148
+ }
149
+ return {
150
+ initialize: ensureInitialized,
151
+ async insertEvent(event) {
152
+ await ensureInitialized();
153
+ const [insertResult] = await database.execute(`
154
+ INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
155
+ VALUES (?, ?, ?, ?, ?)
156
+ `, [
157
+ event.type,
158
+ event.timestamp,
159
+ event.sessionId ?? null,
160
+ event.userId ?? null,
161
+ stringifyMetadata(event.metadata)
162
+ ]);
163
+ const insertId = Number(insertResult.insertId ?? 0);
164
+ if (!insertId) {
165
+ return { ...event };
166
+ }
167
+ const [rows] = await database.execute(`
168
+ SELECT id, type, timestamp, session_id, user_id, metadata
169
+ FROM locallytics_events
170
+ WHERE id = ?
171
+ LIMIT 1
172
+ `, [insertId]);
173
+ const first = rows[0];
174
+ return first ? normalizeEventRow(first) : { ...event, id: insertId };
175
+ },
176
+ async findEvents() {
177
+ await ensureInitialized();
178
+ const [rows] = await database.execute(`
179
+ SELECT id, type, timestamp, session_id, user_id, metadata
180
+ FROM locallytics_events
181
+ ORDER BY timestamp DESC
182
+ `);
183
+ return rows.map(normalizeEventRow);
184
+ }
185
+ };
186
+ }
187
+ function createSqliteAdapter(database) {
188
+ let initialized = false;
189
+ function ensureInitialized() {
190
+ if (initialized) {
191
+ return;
192
+ }
193
+ database.prepare(`
194
+ CREATE TABLE IF NOT EXISTS locallytics_events (
195
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
196
+ type TEXT NOT NULL,
197
+ timestamp INTEGER NOT NULL,
198
+ session_id TEXT,
199
+ user_id TEXT,
200
+ metadata TEXT
201
+ )
202
+ `).run();
203
+ initialized = true;
204
+ }
205
+ return {
206
+ initialize: ensureInitialized,
207
+ async insertEvent(event) {
208
+ ensureInitialized();
209
+ const insert = database.prepare(`
210
+ INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
211
+ VALUES (?, ?, ?, ?, ?)
212
+ `);
213
+ const result = insert.run(event.type, event.timestamp, event.sessionId ?? null, event.userId ?? null, stringifyMetadata(event.metadata));
214
+ const insertId = Number(result.lastInsertRowid ?? 0);
215
+ const select = database.prepare(`
216
+ SELECT id, type, timestamp, session_id, user_id, metadata
217
+ FROM locallytics_events
218
+ WHERE id = ?
219
+ LIMIT 1
220
+ `);
221
+ const row = select.get ? select.get(insertId) : select.all(insertId)[0];
222
+ return row ? normalizeEventRow(row) : { ...event, id: insertId };
223
+ },
224
+ async findEvents() {
225
+ ensureInitialized();
226
+ const rows = database.prepare(`
227
+ SELECT id, type, timestamp, session_id, user_id, metadata
228
+ FROM locallytics_events
229
+ ORDER BY timestamp DESC
230
+ `).all();
231
+ return rows.map(normalizeEventRow);
232
+ }
233
+ };
234
+ }
235
+ function createDatabaseAdapter(database) {
236
+ if (isDatabaseAdapter(database)) {
237
+ return database;
238
+ }
239
+ if (isPostgresLikeDatabase(database)) {
240
+ return createPostgresAdapter(database);
241
+ }
242
+ if (isMysqlLikeDatabase(database)) {
243
+ return createMysqlAdapter(database);
244
+ }
245
+ if (isSqliteLikeDatabase(database)) {
246
+ return createSqliteAdapter(database);
247
+ }
248
+ throw new Error("Unsupported database input. Use a pg Pool, mysql2 pool, better-sqlite3 Database, or a DatabaseAdapter.");
249
+ }
250
+ function createSessionId() {
251
+ return `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
252
+ }
253
+ function createLocallytics(config) {
254
+ const adapter = createDatabaseAdapter(config.database);
255
+ const listeners = new Set;
256
+ const isBrowser = typeof window !== "undefined";
257
+ let userId = config.userId;
258
+ let currentSessionId;
259
+ const queryState = {
260
+ events: [],
261
+ loading: false,
262
+ error: null,
263
+ loaded: false,
264
+ pending: undefined
265
+ };
266
+ function notify() {
267
+ for (const listener of listeners) {
268
+ listener();
269
+ }
270
+ }
271
+ function getSessionId() {
272
+ if (!config.sessionTracking) {
273
+ return;
274
+ }
275
+ if (!currentSessionId) {
276
+ currentSessionId = createSessionId();
277
+ }
278
+ return currentSessionId;
279
+ }
280
+ async function runQuery(force = false) {
281
+ if (!force && queryState.pending) {
282
+ return queryState.pending;
283
+ }
284
+ if (!force && isBrowser && queryState.loaded) {
285
+ return queryState.events;
286
+ }
287
+ queryState.loading = true;
288
+ queryState.error = null;
289
+ notify();
290
+ const pending = (async () => {
291
+ if (adapter.initialize) {
292
+ await adapter.initialize();
293
+ }
294
+ const events = await adapter.findEvents();
295
+ queryState.events = events;
296
+ queryState.loaded = true;
297
+ return events;
298
+ })().catch((error) => {
299
+ const normalized = toError(error);
300
+ queryState.error = normalized;
301
+ throw normalized;
302
+ }).finally(() => {
303
+ queryState.loading = false;
304
+ queryState.pending = undefined;
305
+ notify();
306
+ });
307
+ queryState.pending = pending;
308
+ return pending;
309
+ }
310
+ async function track(type, metadata) {
311
+ if (adapter.initialize) {
312
+ await adapter.initialize();
313
+ }
314
+ const event = {
315
+ type,
316
+ timestamp: Date.now(),
317
+ sessionId: getSessionId(),
318
+ userId,
319
+ metadata
320
+ };
321
+ const saved = await adapter.insertEvent(event);
322
+ if (isBrowser && queryState.loaded) {
323
+ queryState.events = [saved, ...queryState.events];
324
+ notify();
325
+ }
326
+ return saved;
327
+ }
328
+ function createQueryResult(promise) {
329
+ return {
330
+ get events() {
331
+ return queryState.events;
332
+ },
333
+ get loading() {
334
+ return queryState.loading;
335
+ },
336
+ get error() {
337
+ return queryState.error;
338
+ },
339
+ refresh: () => runQuery(true),
340
+ then: promise.then.bind(promise)
341
+ };
342
+ }
343
+ return {
344
+ track,
345
+ async trackPageView(route) {
346
+ const resolvedRoute = route ?? (typeof location !== "undefined" ? `${location.pathname}${location.search}` : "/");
347
+ return track("page_view", { route: resolvedRoute });
348
+ },
349
+ query() {
350
+ const promise = runQuery(!isBrowser);
351
+ return createQueryResult(promise);
352
+ },
353
+ setUserId(nextUserId) {
354
+ userId = nextUserId;
355
+ },
356
+ getUserId() {
357
+ return userId;
358
+ },
359
+ getSessionId,
360
+ subscribe(listener) {
361
+ listeners.add(listener);
362
+ return () => {
363
+ listeners.delete(listener);
364
+ };
365
+ }
366
+ };
367
+ }
package/dist/index.mjs ADDED
@@ -0,0 +1,336 @@
1
+ // src/index.ts
2
+ function toError(error) {
3
+ return error instanceof Error ? error : new Error(String(error));
4
+ }
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null;
7
+ }
8
+ function parseMetadata(value) {
9
+ if (value == null) {
10
+ return;
11
+ }
12
+ if (isRecord(value)) {
13
+ return value;
14
+ }
15
+ if (typeof value === "string") {
16
+ try {
17
+ const parsed = JSON.parse(value);
18
+ return isRecord(parsed) ? parsed : undefined;
19
+ } catch {
20
+ return;
21
+ }
22
+ }
23
+ return;
24
+ }
25
+ function normalizeEventRow(row) {
26
+ return {
27
+ id: row.id,
28
+ type: String(row.type ?? ""),
29
+ timestamp: Number(row.timestamp ?? Date.now()),
30
+ sessionId: row.session_id ?? row.sessionId,
31
+ userId: row.user_id ?? row.userId,
32
+ metadata: parseMetadata(row.metadata)
33
+ };
34
+ }
35
+ function stringifyMetadata(metadata) {
36
+ return metadata ? JSON.stringify(metadata) : null;
37
+ }
38
+ function isDatabaseAdapter(database) {
39
+ return "insertEvent" in database && typeof database.insertEvent === "function" && "findEvents" in database && typeof database.findEvents === "function";
40
+ }
41
+ function isPostgresLikeDatabase(database) {
42
+ return "query" in database && typeof database.query === "function";
43
+ }
44
+ function isMysqlLikeDatabase(database) {
45
+ return "execute" in database && typeof database.execute === "function";
46
+ }
47
+ function isSqliteLikeDatabase(database) {
48
+ return "prepare" in database && typeof database.prepare === "function";
49
+ }
50
+ function createPostgresAdapter(database) {
51
+ let initialized = false;
52
+ async function ensureInitialized() {
53
+ if (initialized) {
54
+ return;
55
+ }
56
+ await database.query(`
57
+ CREATE TABLE IF NOT EXISTS locallytics_events (
58
+ id BIGSERIAL PRIMARY KEY,
59
+ type TEXT NOT NULL,
60
+ timestamp BIGINT NOT NULL,
61
+ session_id TEXT,
62
+ user_id TEXT,
63
+ metadata JSONB
64
+ )
65
+ `);
66
+ initialized = true;
67
+ }
68
+ return {
69
+ initialize: ensureInitialized,
70
+ async insertEvent(event) {
71
+ await ensureInitialized();
72
+ const result = await database.query(`
73
+ INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
74
+ VALUES ($1, $2, $3, $4, $5::jsonb)
75
+ RETURNING id, type, timestamp, session_id, user_id, metadata
76
+ `, [
77
+ event.type,
78
+ event.timestamp,
79
+ event.sessionId ?? null,
80
+ event.userId ?? null,
81
+ stringifyMetadata(event.metadata)
82
+ ]);
83
+ const first = result.rows[0];
84
+ return first ? normalizeEventRow(first) : { ...event };
85
+ },
86
+ async findEvents() {
87
+ await ensureInitialized();
88
+ const result = await database.query(`
89
+ SELECT id, type, timestamp, session_id, user_id, metadata
90
+ FROM locallytics_events
91
+ ORDER BY timestamp DESC
92
+ `);
93
+ return result.rows.map(normalizeEventRow);
94
+ }
95
+ };
96
+ }
97
+ function createMysqlAdapter(database) {
98
+ let initialized = false;
99
+ async function ensureInitialized() {
100
+ if (initialized) {
101
+ return;
102
+ }
103
+ await database.execute(`
104
+ CREATE TABLE IF NOT EXISTS locallytics_events (
105
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
106
+ type VARCHAR(255) NOT NULL,
107
+ timestamp BIGINT NOT NULL,
108
+ session_id VARCHAR(255),
109
+ user_id VARCHAR(255),
110
+ metadata JSON
111
+ )
112
+ `);
113
+ initialized = true;
114
+ }
115
+ return {
116
+ initialize: ensureInitialized,
117
+ async insertEvent(event) {
118
+ await ensureInitialized();
119
+ const [insertResult] = await database.execute(`
120
+ INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
121
+ VALUES (?, ?, ?, ?, ?)
122
+ `, [
123
+ event.type,
124
+ event.timestamp,
125
+ event.sessionId ?? null,
126
+ event.userId ?? null,
127
+ stringifyMetadata(event.metadata)
128
+ ]);
129
+ const insertId = Number(insertResult.insertId ?? 0);
130
+ if (!insertId) {
131
+ return { ...event };
132
+ }
133
+ const [rows] = await database.execute(`
134
+ SELECT id, type, timestamp, session_id, user_id, metadata
135
+ FROM locallytics_events
136
+ WHERE id = ?
137
+ LIMIT 1
138
+ `, [insertId]);
139
+ const first = rows[0];
140
+ return first ? normalizeEventRow(first) : { ...event, id: insertId };
141
+ },
142
+ async findEvents() {
143
+ await ensureInitialized();
144
+ const [rows] = await database.execute(`
145
+ SELECT id, type, timestamp, session_id, user_id, metadata
146
+ FROM locallytics_events
147
+ ORDER BY timestamp DESC
148
+ `);
149
+ return rows.map(normalizeEventRow);
150
+ }
151
+ };
152
+ }
153
+ function createSqliteAdapter(database) {
154
+ let initialized = false;
155
+ function ensureInitialized() {
156
+ if (initialized) {
157
+ return;
158
+ }
159
+ database.prepare(`
160
+ CREATE TABLE IF NOT EXISTS locallytics_events (
161
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
162
+ type TEXT NOT NULL,
163
+ timestamp INTEGER NOT NULL,
164
+ session_id TEXT,
165
+ user_id TEXT,
166
+ metadata TEXT
167
+ )
168
+ `).run();
169
+ initialized = true;
170
+ }
171
+ return {
172
+ initialize: ensureInitialized,
173
+ async insertEvent(event) {
174
+ ensureInitialized();
175
+ const insert = database.prepare(`
176
+ INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
177
+ VALUES (?, ?, ?, ?, ?)
178
+ `);
179
+ const result = insert.run(event.type, event.timestamp, event.sessionId ?? null, event.userId ?? null, stringifyMetadata(event.metadata));
180
+ const insertId = Number(result.lastInsertRowid ?? 0);
181
+ const select = database.prepare(`
182
+ SELECT id, type, timestamp, session_id, user_id, metadata
183
+ FROM locallytics_events
184
+ WHERE id = ?
185
+ LIMIT 1
186
+ `);
187
+ const row = select.get ? select.get(insertId) : select.all(insertId)[0];
188
+ return row ? normalizeEventRow(row) : { ...event, id: insertId };
189
+ },
190
+ async findEvents() {
191
+ ensureInitialized();
192
+ const rows = database.prepare(`
193
+ SELECT id, type, timestamp, session_id, user_id, metadata
194
+ FROM locallytics_events
195
+ ORDER BY timestamp DESC
196
+ `).all();
197
+ return rows.map(normalizeEventRow);
198
+ }
199
+ };
200
+ }
201
+ function createDatabaseAdapter(database) {
202
+ if (isDatabaseAdapter(database)) {
203
+ return database;
204
+ }
205
+ if (isPostgresLikeDatabase(database)) {
206
+ return createPostgresAdapter(database);
207
+ }
208
+ if (isMysqlLikeDatabase(database)) {
209
+ return createMysqlAdapter(database);
210
+ }
211
+ if (isSqliteLikeDatabase(database)) {
212
+ return createSqliteAdapter(database);
213
+ }
214
+ throw new Error("Unsupported database input. Use a pg Pool, mysql2 pool, better-sqlite3 Database, or a DatabaseAdapter.");
215
+ }
216
+ function createSessionId() {
217
+ return `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
218
+ }
219
+ function createLocallytics(config) {
220
+ const adapter = createDatabaseAdapter(config.database);
221
+ const listeners = new Set;
222
+ const isBrowser = typeof window !== "undefined";
223
+ let userId = config.userId;
224
+ let currentSessionId;
225
+ const queryState = {
226
+ events: [],
227
+ loading: false,
228
+ error: null,
229
+ loaded: false,
230
+ pending: undefined
231
+ };
232
+ function notify() {
233
+ for (const listener of listeners) {
234
+ listener();
235
+ }
236
+ }
237
+ function getSessionId() {
238
+ if (!config.sessionTracking) {
239
+ return;
240
+ }
241
+ if (!currentSessionId) {
242
+ currentSessionId = createSessionId();
243
+ }
244
+ return currentSessionId;
245
+ }
246
+ async function runQuery(force = false) {
247
+ if (!force && queryState.pending) {
248
+ return queryState.pending;
249
+ }
250
+ if (!force && isBrowser && queryState.loaded) {
251
+ return queryState.events;
252
+ }
253
+ queryState.loading = true;
254
+ queryState.error = null;
255
+ notify();
256
+ const pending = (async () => {
257
+ if (adapter.initialize) {
258
+ await adapter.initialize();
259
+ }
260
+ const events = await adapter.findEvents();
261
+ queryState.events = events;
262
+ queryState.loaded = true;
263
+ return events;
264
+ })().catch((error) => {
265
+ const normalized = toError(error);
266
+ queryState.error = normalized;
267
+ throw normalized;
268
+ }).finally(() => {
269
+ queryState.loading = false;
270
+ queryState.pending = undefined;
271
+ notify();
272
+ });
273
+ queryState.pending = pending;
274
+ return pending;
275
+ }
276
+ async function track(type, metadata) {
277
+ if (adapter.initialize) {
278
+ await adapter.initialize();
279
+ }
280
+ const event = {
281
+ type,
282
+ timestamp: Date.now(),
283
+ sessionId: getSessionId(),
284
+ userId,
285
+ metadata
286
+ };
287
+ const saved = await adapter.insertEvent(event);
288
+ if (isBrowser && queryState.loaded) {
289
+ queryState.events = [saved, ...queryState.events];
290
+ notify();
291
+ }
292
+ return saved;
293
+ }
294
+ function createQueryResult(promise) {
295
+ return {
296
+ get events() {
297
+ return queryState.events;
298
+ },
299
+ get loading() {
300
+ return queryState.loading;
301
+ },
302
+ get error() {
303
+ return queryState.error;
304
+ },
305
+ refresh: () => runQuery(true),
306
+ then: promise.then.bind(promise)
307
+ };
308
+ }
309
+ return {
310
+ track,
311
+ async trackPageView(route) {
312
+ const resolvedRoute = route ?? (typeof location !== "undefined" ? `${location.pathname}${location.search}` : "/");
313
+ return track("page_view", { route: resolvedRoute });
314
+ },
315
+ query() {
316
+ const promise = runQuery(!isBrowser);
317
+ return createQueryResult(promise);
318
+ },
319
+ setUserId(nextUserId) {
320
+ userId = nextUserId;
321
+ },
322
+ getUserId() {
323
+ return userId;
324
+ },
325
+ getSessionId,
326
+ subscribe(listener) {
327
+ listeners.add(listener);
328
+ return () => {
329
+ listeners.delete(listener);
330
+ };
331
+ }
332
+ };
333
+ }
334
+ export {
335
+ createLocallytics
336
+ };