locallytics 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/adapters/custom.d.ts +3 -0
- package/dist/adapters/custom.d.ts.map +1 -0
- package/dist/adapters/sqlite.d.ts +3 -0
- package/dist/adapters/sqlite.d.ts.map +1 -0
- package/dist/create-locallytics.d.ts +3 -0
- package/dist/create-locallytics.d.ts.map +1 -0
- package/dist/index.d.ts +4 -59
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +172 -285
- package/dist/index.js.map +15 -0
- package/dist/normalize.d.ts +5 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/sql.d.ts +3 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +13 -44
- package/dist/adapters/drizzle.d.mts +0 -25
- package/dist/adapters/drizzle.d.ts +0 -25
- package/dist/adapters/drizzle.js +0 -38
- package/dist/adapters/drizzle.mjs +0 -7
- package/dist/adapters/prisma.d.mts +0 -25
- package/dist/adapters/prisma.d.ts +0 -25
- package/dist/adapters/prisma.js +0 -38
- package/dist/adapters/prisma.mjs +0 -7
- package/dist/adapters.d.mts +0 -7
- package/dist/adapters.d.ts +0 -7
- package/dist/adapters.js +0 -39
- package/dist/adapters.mjs +0 -8
- package/dist/browser.d.mts +0 -41
- package/dist/browser.d.ts +0 -41
- package/dist/browser.js +0 -79
- package/dist/browser.mjs +0 -10
- package/dist/index.d.mts +0 -59
- package/dist/index.mjs +0 -336
- package/dist/react.d.mts +0 -48
- package/dist/react.d.ts +0 -48
- package/dist/react.js +0 -132
- package/dist/react.mjs +0 -61
- package/dist/shared/chunk-8tbv1rg5.js +0 -45
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# locallytics
|
|
2
|
+
|
|
3
|
+
`locallytics` is a lightweight, framework-agnostic analytics runtime.
|
|
4
|
+
This release currently supports SQLite.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```npm
|
|
9
|
+
npm install locallytics better-sqlite3
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
Create one shared Locallytics instance.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createLocallytics } from "locallytics";
|
|
18
|
+
import Database from "better-sqlite3";
|
|
19
|
+
|
|
20
|
+
export const locallytics = createLocallytics({
|
|
21
|
+
database: new Database("database.sqlite"),
|
|
22
|
+
sessionTracking: true,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Track custom events.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
await locallytics.track("button_click", { button: "cta" });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Read stored events.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const events = await locallytics.listEvents({ limit: 25 });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
- `createLocallytics(config)`
|
|
41
|
+
- `locallytics.track(type, metadata?)`
|
|
42
|
+
- `locallytics.trackPageView(route?)`
|
|
43
|
+
- `locallytics.listEvents(options?)`
|
|
44
|
+
- `locallytics.setUserId(userId)`
|
|
45
|
+
- `locallytics.getUserId()`
|
|
46
|
+
- `locallytics.getSessionId()`
|
|
47
|
+
|
|
48
|
+
## CLI
|
|
49
|
+
|
|
50
|
+
Use `@locallytics/cli` to generate and run migrations:
|
|
51
|
+
|
|
52
|
+
```npm
|
|
53
|
+
npm install -D @locallytics/cli
|
|
54
|
+
npx locallytics generate
|
|
55
|
+
npx locallytics migrate
|
|
56
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/adapters/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,CAE7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EAIf,cAAc,EACf,MAAM,UAAU,CAAC;AAmDlB,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,eAAe,CAkF7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-locallytics.d.ts","sourceRoot":"","sources":["../src/create-locallytics.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,WAAW,EACX,iBAAiB,EAKlB,MAAM,SAAS,CAAC;AA8DjB,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CA4DxE"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,59 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type LocallyticsEvent = {
|
|
6
|
-
id?: string | number;
|
|
7
|
-
type: string;
|
|
8
|
-
timestamp: number;
|
|
9
|
-
sessionId?: string;
|
|
10
|
-
userId?: string;
|
|
11
|
-
metadata?: Record<string, unknown>;
|
|
12
|
-
};
|
|
13
|
-
interface DatabaseAdapter {
|
|
14
|
-
initialize?(): Promise<void> | void;
|
|
15
|
-
insertEvent(event: Omit<LocallyticsEvent, "id">): Promise<LocallyticsEvent>;
|
|
16
|
-
findEvents(): Promise<LocallyticsEvent[]>;
|
|
17
|
-
}
|
|
18
|
-
type PostgresLikeDatabase = {
|
|
19
|
-
query: (sql: string, params?: unknown[]) => Promise<{
|
|
20
|
-
rows: Record<string, unknown>[];
|
|
21
|
-
}>;
|
|
22
|
-
};
|
|
23
|
-
type MysqlLikeDatabase = {
|
|
24
|
-
execute: (sql: string, params?: unknown[]) => Promise<[unknown, unknown]>;
|
|
25
|
-
};
|
|
26
|
-
type SqliteLikeStatement = {
|
|
27
|
-
run: (...params: unknown[]) => {
|
|
28
|
-
lastInsertRowid?: number | bigint;
|
|
29
|
-
};
|
|
30
|
-
all: (...params: unknown[]) => Record<string, unknown>[];
|
|
31
|
-
get?: (...params: unknown[]) => Record<string, unknown> | undefined;
|
|
32
|
-
};
|
|
33
|
-
type SqliteLikeDatabase = {
|
|
34
|
-
prepare: (sql: string) => SqliteLikeStatement;
|
|
35
|
-
};
|
|
36
|
-
type SupportedDatabase = DatabaseAdapter | PostgresLikeDatabase | MysqlLikeDatabase | SqliteLikeDatabase;
|
|
37
|
-
type LocallyticsConfig = {
|
|
38
|
-
database: SupportedDatabase;
|
|
39
|
-
sessionTracking?: boolean;
|
|
40
|
-
userId?: string;
|
|
41
|
-
};
|
|
42
|
-
type ClientQueryResult = {
|
|
43
|
-
events: LocallyticsEvent[];
|
|
44
|
-
loading: boolean;
|
|
45
|
-
error: Error | null;
|
|
46
|
-
refresh: () => Promise<LocallyticsEvent[]>;
|
|
47
|
-
};
|
|
48
|
-
type QueryResult = ClientQueryResult & PromiseLike<LocallyticsEvent[]>;
|
|
49
|
-
interface Locallytics {
|
|
50
|
-
track(type: string, metadata?: Record<string, unknown>): Promise<LocallyticsEvent>;
|
|
51
|
-
trackPageView(route?: string): Promise<LocallyticsEvent>;
|
|
52
|
-
query(): QueryResult;
|
|
53
|
-
setUserId(userId: string | undefined): void;
|
|
54
|
-
getUserId(): string | undefined;
|
|
55
|
-
getSessionId(): string | undefined;
|
|
56
|
-
subscribe(listener: () => void): () => void;
|
|
57
|
-
}
|
|
58
|
-
declare function createLocallytics(config: LocallyticsConfig): Locallytics;
|
|
59
|
-
export { createLocallytics, SupportedDatabase, SqliteLikeStatement, SqliteLikeDatabase, QueryResult, PostgresLikeDatabase, MysqlLikeDatabase, LocallyticsEvent, LocallyticsConfig, Locallytics, DatabaseAdapter, ClientQueryResult };
|
|
1
|
+
export { createLocallytics } from "./create-locallytics";
|
|
2
|
+
export { EVENTS_TABLE_NAME, SQLITE_CREATE_EVENTS_TABLE_SQL } from "./sql";
|
|
3
|
+
export type { DatabaseAdapter, EventMetadata, ListEventsOptions, Locallytics, LocallyticsConfig, LocallyticsEvent, LocallyticsEventInput, SqliteDatabase, SqliteStatement, SupportedDatabase, } from "./types";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,8BAA8B,EAAE,MAAM,OAAO,CAAC;AAC1E,YAAY,EACV,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,41 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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));
|
|
1
|
+
// src/adapters/custom.ts
|
|
2
|
+
function createCustomAdapter(adapter) {
|
|
3
|
+
return adapter;
|
|
38
4
|
}
|
|
5
|
+
|
|
6
|
+
// src/normalize.ts
|
|
39
7
|
function isRecord(value) {
|
|
40
8
|
return typeof value === "object" && value !== null;
|
|
41
9
|
}
|
|
@@ -46,15 +14,21 @@ function parseMetadata(value) {
|
|
|
46
14
|
if (isRecord(value)) {
|
|
47
15
|
return value;
|
|
48
16
|
}
|
|
49
|
-
if (typeof value
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
17
|
+
if (typeof value !== "string") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(value);
|
|
22
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
23
|
+
} catch {
|
|
24
|
+
return;
|
|
56
25
|
}
|
|
57
|
-
|
|
26
|
+
}
|
|
27
|
+
function stringifyMetadata(metadata) {
|
|
28
|
+
if (!metadata) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return JSON.stringify(metadata);
|
|
58
32
|
}
|
|
59
33
|
function normalizeEventRow(row) {
|
|
60
34
|
return {
|
|
@@ -66,123 +40,54 @@ function normalizeEventRow(row) {
|
|
|
66
40
|
metadata: parseMetadata(row.metadata)
|
|
67
41
|
};
|
|
68
42
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
43
|
+
|
|
44
|
+
// src/sql.ts
|
|
45
|
+
var EVENTS_TABLE_NAME = "locallytics_events";
|
|
46
|
+
var SQLITE_CREATE_EVENTS_TABLE_SQL = `
|
|
47
|
+
CREATE TABLE IF NOT EXISTS locallytics_events (
|
|
48
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
+
type TEXT NOT NULL,
|
|
50
|
+
timestamp INTEGER NOT NULL,
|
|
51
|
+
session_id TEXT,
|
|
52
|
+
user_id TEXT,
|
|
53
|
+
metadata TEXT
|
|
54
|
+
);
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
// src/adapters/sqlite.ts
|
|
58
|
+
function toRow(value) {
|
|
59
|
+
if (typeof value === "object" && value !== null) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
83
63
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
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;
|
|
64
|
+
function toInsertId(value) {
|
|
65
|
+
if (!value || typeof value !== "object") {
|
|
66
|
+
return;
|
|
101
67
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
};
|
|
68
|
+
const row = value;
|
|
69
|
+
const id = row.lastInsertRowid;
|
|
70
|
+
if (typeof id === "number") {
|
|
71
|
+
return id;
|
|
72
|
+
}
|
|
73
|
+
if (typeof id === "bigint") {
|
|
74
|
+
return Number(id);
|
|
75
|
+
}
|
|
76
|
+
if (typeof id === "string" && id.length > 0) {
|
|
77
|
+
return Number(id);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
130
80
|
}
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
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;
|
|
81
|
+
function toSafeLimit(limit) {
|
|
82
|
+
if (limit == null) {
|
|
83
|
+
return;
|
|
148
84
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
};
|
|
85
|
+
const normalized = Math.floor(limit);
|
|
86
|
+
return normalized > 0 ? normalized : 1;
|
|
87
|
+
}
|
|
88
|
+
function toSqlResultRows(database, sql, params) {
|
|
89
|
+
const statement = database.prepare(sql);
|
|
90
|
+
return statement.all(...params).map((row) => normalizeEventRow(toRow(row)));
|
|
186
91
|
}
|
|
187
92
|
function createSqliteAdapter(database) {
|
|
188
93
|
let initialized = false;
|
|
@@ -190,178 +95,160 @@ function createSqliteAdapter(database) {
|
|
|
190
95
|
if (initialized) {
|
|
191
96
|
return;
|
|
192
97
|
}
|
|
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();
|
|
98
|
+
database.prepare(SQLITE_CREATE_EVENTS_TABLE_SQL).run();
|
|
203
99
|
initialized = true;
|
|
204
100
|
}
|
|
205
101
|
return {
|
|
206
102
|
initialize: ensureInitialized,
|
|
207
103
|
async insertEvent(event) {
|
|
208
104
|
ensureInitialized();
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
105
|
+
const result = database.prepare(`
|
|
106
|
+
INSERT INTO locallytics_events (type, timestamp, session_id, user_id, metadata)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?)
|
|
108
|
+
`).run(event.type, event.timestamp, event.sessionId ?? null, event.userId ?? null, stringifyMetadata(event.metadata));
|
|
109
|
+
const id = toInsertId(result);
|
|
110
|
+
if (id == null) {
|
|
111
|
+
return { ...event };
|
|
112
|
+
}
|
|
215
113
|
const select = database.prepare(`
|
|
216
114
|
SELECT id, type, timestamp, session_id, user_id, metadata
|
|
217
115
|
FROM locallytics_events
|
|
218
116
|
WHERE id = ?
|
|
219
117
|
LIMIT 1
|
|
220
118
|
`);
|
|
221
|
-
const row = select.get ? select.get(
|
|
222
|
-
return row ? normalizeEventRow(row) : { ...event, id
|
|
119
|
+
const row = select.get ? select.get(id) : select.all(id)[0];
|
|
120
|
+
return row ? normalizeEventRow(toRow(row)) : { ...event, id };
|
|
223
121
|
},
|
|
224
|
-
async
|
|
122
|
+
async listEvents(options) {
|
|
225
123
|
ensureInitialized();
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
124
|
+
const params = [];
|
|
125
|
+
const where = [];
|
|
126
|
+
if (options?.type) {
|
|
127
|
+
where.push("type = ?");
|
|
128
|
+
params.push(options.type);
|
|
129
|
+
}
|
|
130
|
+
let sql = `
|
|
131
|
+
SELECT id, type, timestamp, session_id, user_id, metadata
|
|
132
|
+
FROM locallytics_events
|
|
133
|
+
`;
|
|
134
|
+
if (where.length > 0) {
|
|
135
|
+
sql += ` WHERE ${where.join(" AND ")}`;
|
|
136
|
+
}
|
|
137
|
+
sql += " ORDER BY timestamp DESC, id DESC";
|
|
138
|
+
const limit = toSafeLimit(options?.limit);
|
|
139
|
+
if (limit) {
|
|
140
|
+
sql += " LIMIT ?";
|
|
141
|
+
params.push(limit);
|
|
142
|
+
}
|
|
143
|
+
return toSqlResultRows(database, sql, params);
|
|
232
144
|
}
|
|
233
145
|
};
|
|
234
146
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return createPostgresAdapter(database);
|
|
147
|
+
|
|
148
|
+
// src/session.ts
|
|
149
|
+
function randomSuffix() {
|
|
150
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
151
|
+
return crypto.randomUUID();
|
|
241
152
|
}
|
|
242
|
-
|
|
243
|
-
|
|
153
|
+
return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
154
|
+
}
|
|
155
|
+
function createSessionManager(sessionTracking) {
|
|
156
|
+
let sessionId;
|
|
157
|
+
return {
|
|
158
|
+
getSessionId() {
|
|
159
|
+
if (!sessionTracking) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!sessionId) {
|
|
163
|
+
sessionId = `ltx_${randomSuffix()}`;
|
|
164
|
+
}
|
|
165
|
+
return sessionId;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/create-locallytics.ts
|
|
171
|
+
function isDatabaseAdapter(database) {
|
|
172
|
+
return typeof database === "object" && database !== null && "insertEvent" in database && typeof database.insertEvent === "function" && "listEvents" in database && typeof database.listEvents === "function";
|
|
173
|
+
}
|
|
174
|
+
function isSqliteDatabase(database) {
|
|
175
|
+
return typeof database === "object" && database !== null && "prepare" in database && typeof database.prepare === "function";
|
|
176
|
+
}
|
|
177
|
+
function toDatabaseAdapter(database) {
|
|
178
|
+
if (isDatabaseAdapter(database)) {
|
|
179
|
+
return createCustomAdapter(database);
|
|
244
180
|
}
|
|
245
|
-
if (
|
|
181
|
+
if (isSqliteDatabase(database)) {
|
|
246
182
|
return createSqliteAdapter(database);
|
|
247
183
|
}
|
|
248
|
-
throw new Error("Unsupported database input.
|
|
184
|
+
throw new Error("Unsupported database input. Pass a better-sqlite3-style database or a custom adapter.");
|
|
249
185
|
}
|
|
250
|
-
function
|
|
251
|
-
|
|
186
|
+
function normalizeEventType(type) {
|
|
187
|
+
const normalized = type.trim();
|
|
188
|
+
if (!normalized) {
|
|
189
|
+
throw new Error("Event type is required.");
|
|
190
|
+
}
|
|
191
|
+
return normalized;
|
|
192
|
+
}
|
|
193
|
+
function resolvePageViewRoute(route) {
|
|
194
|
+
if (route) {
|
|
195
|
+
return route;
|
|
196
|
+
}
|
|
197
|
+
if (typeof location === "undefined") {
|
|
198
|
+
return "/";
|
|
199
|
+
}
|
|
200
|
+
return `${location.pathname}${location.search}`;
|
|
252
201
|
}
|
|
253
202
|
function createLocallytics(config) {
|
|
254
|
-
const adapter =
|
|
255
|
-
const
|
|
256
|
-
|
|
203
|
+
const adapter = toDatabaseAdapter(config.database);
|
|
204
|
+
const sessionManager = createSessionManager(Boolean(config.sessionTracking));
|
|
205
|
+
let initialized = false;
|
|
257
206
|
let userId = config.userId;
|
|
258
|
-
|
|
259
|
-
|
|
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) {
|
|
207
|
+
async function ensureInitialized() {
|
|
208
|
+
if (initialized) {
|
|
273
209
|
return;
|
|
274
210
|
}
|
|
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
211
|
if (adapter.initialize) {
|
|
312
212
|
await adapter.initialize();
|
|
313
213
|
}
|
|
314
|
-
|
|
315
|
-
|
|
214
|
+
initialized = true;
|
|
215
|
+
}
|
|
216
|
+
async function track(type, metadata) {
|
|
217
|
+
await ensureInitialized();
|
|
218
|
+
return adapter.insertEvent({
|
|
219
|
+
type: normalizeEventType(type),
|
|
316
220
|
timestamp: Date.now(),
|
|
317
|
-
sessionId: getSessionId(),
|
|
221
|
+
sessionId: sessionManager.getSessionId(),
|
|
318
222
|
userId,
|
|
319
223
|
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;
|
|
224
|
+
});
|
|
327
225
|
}
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
};
|
|
226
|
+
async function listEvents(options) {
|
|
227
|
+
await ensureInitialized();
|
|
228
|
+
return adapter.listEvents(options);
|
|
342
229
|
}
|
|
343
230
|
return {
|
|
344
231
|
track,
|
|
345
232
|
async trackPageView(route) {
|
|
346
|
-
|
|
347
|
-
return track("page_view", { route: resolvedRoute });
|
|
348
|
-
},
|
|
349
|
-
query() {
|
|
350
|
-
const promise = runQuery(!isBrowser);
|
|
351
|
-
return createQueryResult(promise);
|
|
233
|
+
return track("page_view", { route: resolvePageViewRoute(route) });
|
|
352
234
|
},
|
|
235
|
+
listEvents,
|
|
353
236
|
setUserId(nextUserId) {
|
|
354
237
|
userId = nextUserId;
|
|
355
238
|
},
|
|
356
239
|
getUserId() {
|
|
357
240
|
return userId;
|
|
358
241
|
},
|
|
359
|
-
getSessionId
|
|
360
|
-
|
|
361
|
-
listeners.add(listener);
|
|
362
|
-
return () => {
|
|
363
|
-
listeners.delete(listener);
|
|
364
|
-
};
|
|
242
|
+
getSessionId() {
|
|
243
|
+
return sessionManager.getSessionId();
|
|
365
244
|
}
|
|
366
245
|
};
|
|
367
246
|
}
|
|
247
|
+
export {
|
|
248
|
+
createLocallytics,
|
|
249
|
+
SQLITE_CREATE_EVENTS_TABLE_SQL,
|
|
250
|
+
EVENTS_TABLE_NAME
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
//# debugId=8CAF93D1E8D8A8E664756E2164756E21
|
|
254
|
+
//# sourceMappingURL=index.js.map
|