krisspy-sdk 0.3.1 → 0.4.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/dist/index.d.mts CHANGED
@@ -6,8 +6,12 @@ interface KrisspyClientOptions {
6
6
  url?: string;
7
7
  /** Backend ID for this client */
8
8
  backendId: string;
9
- /** API key or access token */
10
- apiKey?: string;
9
+ /**
10
+ * Public API key for the backend (required)
11
+ * This key can be safely exposed in frontend code.
12
+ * Get it from your backend settings in the Krisspy dashboard.
13
+ */
14
+ anonKey: string;
11
15
  /** Custom headers to include in all requests */
12
16
  headers?: Record<string, string>;
13
17
  /** Enable debug logging */
@@ -695,6 +699,89 @@ declare class KrisspyRealtime {
695
699
  private log;
696
700
  }
697
701
 
702
+ /**
703
+ * Krisspy Analytics - Lightweight event tracking module
704
+ *
705
+ * Tracks page views, sessions, custom events. Sends batched events
706
+ * to the analytics ingest endpoint. Works with SPAs (intercepts pushState).
707
+ *
708
+ * @example
709
+ * const krisspy = createClient({ backendId: '...', anonKey: '...' })
710
+ * krisspy.analytics.init({ autoTrackPageViews: true })
711
+ * krisspy.analytics.track('button_click', { label: 'signup' })
712
+ */
713
+
714
+ interface AnalyticsInitOptions {
715
+ /** Auto-track page views on init + navigation (default: true) */
716
+ autoTrackPageViews?: boolean;
717
+ /** Auto-track SPA navigation via pushState/popstate (default: true) */
718
+ autoTrackNavigation?: boolean;
719
+ /** Flush interval in ms (default: 5000) */
720
+ flushInterval?: number;
721
+ /** Enable debug logging (default: false) */
722
+ debug?: boolean;
723
+ }
724
+ interface AnalyticsEvent {
725
+ eventName: string;
726
+ sessionId: string;
727
+ userId?: string;
728
+ properties?: Record<string, any>;
729
+ timestamp: string;
730
+ }
731
+ declare class KrisspyAnalytics {
732
+ private http;
733
+ private backendId;
734
+ private queue;
735
+ private sessionId;
736
+ private userId;
737
+ private userTraits;
738
+ private initialized;
739
+ private flushTimer;
740
+ private options;
741
+ private originalPushState;
742
+ private popstateHandler;
743
+ private unloadHandler;
744
+ constructor(http: HttpClient, backendId: string);
745
+ /**
746
+ * Initialize analytics tracking
747
+ *
748
+ * @example
749
+ * krisspy.analytics.init()
750
+ * krisspy.analytics.init({ autoTrackPageViews: true, flushInterval: 3000 })
751
+ */
752
+ init(options?: AnalyticsInitOptions): void;
753
+ /**
754
+ * Track a custom event
755
+ *
756
+ * @example
757
+ * krisspy.analytics.track('button_click', { label: 'signup', variant: 'blue' })
758
+ * krisspy.analytics.track('purchase', { amount: 29.99, plan: 'pro' })
759
+ */
760
+ track(eventName: string, properties?: Record<string, any>): void;
761
+ /**
762
+ * Identify a user (attach userId + traits to all future events)
763
+ *
764
+ * @example
765
+ * krisspy.analytics.identify('user_123', { name: 'John', plan: 'pro' })
766
+ */
767
+ identify(userId: string, traits?: Record<string, any>): void;
768
+ /**
769
+ * Flush event queue to the server (async)
770
+ */
771
+ flush(): Promise<void>;
772
+ /**
773
+ * Synchronous flush using sendBeacon (for page unload)
774
+ */
775
+ private flushSync;
776
+ /**
777
+ * Stop tracking and cleanup all listeners/timers
778
+ */
779
+ destroy(): void;
780
+ private getOrCreateSessionId;
781
+ private getIngestUrl;
782
+ private setupNavigationTracking;
783
+ }
784
+
698
785
  /**
699
786
  * Query Builder - Supabase-style fluent API for database queries
700
787
  */
@@ -846,6 +933,7 @@ declare class KrisspyClient {
846
933
  private _auth;
847
934
  private _storage;
848
935
  private _realtime;
936
+ private _analytics;
849
937
  private useRLS;
850
938
  private debug;
851
939
  constructor(options: KrisspyClientOptions);
@@ -894,6 +982,23 @@ declare class KrisspyClient {
894
982
  * const { error } = await krisspy.storage.from('uploads').remove(['old.jpg'])
895
983
  */
896
984
  get storage(): KrisspyStorage;
985
+ /**
986
+ * Analytics module for event tracking
987
+ *
988
+ * @example
989
+ * // Initialize tracking
990
+ * krisspy.analytics.init()
991
+ *
992
+ * // Track custom events
993
+ * krisspy.analytics.track('button_click', { label: 'signup' })
994
+ *
995
+ * // Identify a user
996
+ * krisspy.analytics.identify('user_123', { plan: 'pro' })
997
+ *
998
+ * // Cleanup
999
+ * krisspy.analytics.destroy()
1000
+ */
1001
+ get analytics(): KrisspyAnalytics;
897
1002
  /**
898
1003
  * Create a realtime channel for subscribing to database changes
899
1004
  *
@@ -1029,7 +1134,7 @@ declare class KrisspyClient {
1029
1134
  *
1030
1135
  * const krisspy = createClient({
1031
1136
  * backendId: 'your-backend-id',
1032
- * apiKey: 'your-api-key', // optional
1137
+ * anonKey: 'your-anon-key', // from backend settings
1033
1138
  * })
1034
1139
  *
1035
1140
  * // Auth
@@ -1071,10 +1176,10 @@ declare class KrisspyClient {
1071
1176
  * @example
1072
1177
  * const krisspy = createClient({
1073
1178
  * backendId: 'abc123',
1074
- * apiKey: 'your-api-key',
1179
+ * anonKey: 'your-anon-key',
1075
1180
  * url: 'https://api.krisspy.ai', // optional
1076
1181
  * })
1077
1182
  */
1078
1183
  declare function createClient(options: KrisspyClientOptions): KrisspyClient;
1079
1184
 
1080
- export { type AuthChangeEvent, type AuthResponse, type ChannelState, type FileObject, type FileUploadOptions, type Filter, type FilterOperator, type FunctionInvokeOptions, type FunctionResponse, HttpClient, KrisspyAuth, KrisspyClient, type KrisspyClientOptions, type KrisspyError, KrisspyRealtime, KrisspyStorage, type MutationResponse, type OAuthProvider, type OrderBy, type PostgresChangesConfig, QueryBuilder, type QueryOptions, type QueryResponse, RealtimeChannel, type RealtimeEvent, type RealtimePayload, type Session, type SignInCredentials, type SignUpCredentials, type SingleResponse, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient };
1185
+ export { type AnalyticsEvent, type AnalyticsInitOptions, type AuthChangeEvent, type AuthResponse, type ChannelState, type FileObject, type FileUploadOptions, type Filter, type FilterOperator, type FunctionInvokeOptions, type FunctionResponse, HttpClient, KrisspyAnalytics, KrisspyAuth, KrisspyClient, type KrisspyClientOptions, type KrisspyError, KrisspyRealtime, KrisspyStorage, type MutationResponse, type OAuthProvider, type OrderBy, type PostgresChangesConfig, QueryBuilder, type QueryOptions, type QueryResponse, RealtimeChannel, type RealtimeEvent, type RealtimePayload, type Session, type SignInCredentials, type SignUpCredentials, type SingleResponse, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient };
package/dist/index.d.ts CHANGED
@@ -6,8 +6,12 @@ interface KrisspyClientOptions {
6
6
  url?: string;
7
7
  /** Backend ID for this client */
8
8
  backendId: string;
9
- /** API key or access token */
10
- apiKey?: string;
9
+ /**
10
+ * Public API key for the backend (required)
11
+ * This key can be safely exposed in frontend code.
12
+ * Get it from your backend settings in the Krisspy dashboard.
13
+ */
14
+ anonKey: string;
11
15
  /** Custom headers to include in all requests */
12
16
  headers?: Record<string, string>;
13
17
  /** Enable debug logging */
@@ -695,6 +699,89 @@ declare class KrisspyRealtime {
695
699
  private log;
696
700
  }
697
701
 
702
+ /**
703
+ * Krisspy Analytics - Lightweight event tracking module
704
+ *
705
+ * Tracks page views, sessions, custom events. Sends batched events
706
+ * to the analytics ingest endpoint. Works with SPAs (intercepts pushState).
707
+ *
708
+ * @example
709
+ * const krisspy = createClient({ backendId: '...', anonKey: '...' })
710
+ * krisspy.analytics.init({ autoTrackPageViews: true })
711
+ * krisspy.analytics.track('button_click', { label: 'signup' })
712
+ */
713
+
714
+ interface AnalyticsInitOptions {
715
+ /** Auto-track page views on init + navigation (default: true) */
716
+ autoTrackPageViews?: boolean;
717
+ /** Auto-track SPA navigation via pushState/popstate (default: true) */
718
+ autoTrackNavigation?: boolean;
719
+ /** Flush interval in ms (default: 5000) */
720
+ flushInterval?: number;
721
+ /** Enable debug logging (default: false) */
722
+ debug?: boolean;
723
+ }
724
+ interface AnalyticsEvent {
725
+ eventName: string;
726
+ sessionId: string;
727
+ userId?: string;
728
+ properties?: Record<string, any>;
729
+ timestamp: string;
730
+ }
731
+ declare class KrisspyAnalytics {
732
+ private http;
733
+ private backendId;
734
+ private queue;
735
+ private sessionId;
736
+ private userId;
737
+ private userTraits;
738
+ private initialized;
739
+ private flushTimer;
740
+ private options;
741
+ private originalPushState;
742
+ private popstateHandler;
743
+ private unloadHandler;
744
+ constructor(http: HttpClient, backendId: string);
745
+ /**
746
+ * Initialize analytics tracking
747
+ *
748
+ * @example
749
+ * krisspy.analytics.init()
750
+ * krisspy.analytics.init({ autoTrackPageViews: true, flushInterval: 3000 })
751
+ */
752
+ init(options?: AnalyticsInitOptions): void;
753
+ /**
754
+ * Track a custom event
755
+ *
756
+ * @example
757
+ * krisspy.analytics.track('button_click', { label: 'signup', variant: 'blue' })
758
+ * krisspy.analytics.track('purchase', { amount: 29.99, plan: 'pro' })
759
+ */
760
+ track(eventName: string, properties?: Record<string, any>): void;
761
+ /**
762
+ * Identify a user (attach userId + traits to all future events)
763
+ *
764
+ * @example
765
+ * krisspy.analytics.identify('user_123', { name: 'John', plan: 'pro' })
766
+ */
767
+ identify(userId: string, traits?: Record<string, any>): void;
768
+ /**
769
+ * Flush event queue to the server (async)
770
+ */
771
+ flush(): Promise<void>;
772
+ /**
773
+ * Synchronous flush using sendBeacon (for page unload)
774
+ */
775
+ private flushSync;
776
+ /**
777
+ * Stop tracking and cleanup all listeners/timers
778
+ */
779
+ destroy(): void;
780
+ private getOrCreateSessionId;
781
+ private getIngestUrl;
782
+ private setupNavigationTracking;
783
+ }
784
+
698
785
  /**
699
786
  * Query Builder - Supabase-style fluent API for database queries
700
787
  */
@@ -846,6 +933,7 @@ declare class KrisspyClient {
846
933
  private _auth;
847
934
  private _storage;
848
935
  private _realtime;
936
+ private _analytics;
849
937
  private useRLS;
850
938
  private debug;
851
939
  constructor(options: KrisspyClientOptions);
@@ -894,6 +982,23 @@ declare class KrisspyClient {
894
982
  * const { error } = await krisspy.storage.from('uploads').remove(['old.jpg'])
895
983
  */
896
984
  get storage(): KrisspyStorage;
985
+ /**
986
+ * Analytics module for event tracking
987
+ *
988
+ * @example
989
+ * // Initialize tracking
990
+ * krisspy.analytics.init()
991
+ *
992
+ * // Track custom events
993
+ * krisspy.analytics.track('button_click', { label: 'signup' })
994
+ *
995
+ * // Identify a user
996
+ * krisspy.analytics.identify('user_123', { plan: 'pro' })
997
+ *
998
+ * // Cleanup
999
+ * krisspy.analytics.destroy()
1000
+ */
1001
+ get analytics(): KrisspyAnalytics;
897
1002
  /**
898
1003
  * Create a realtime channel for subscribing to database changes
899
1004
  *
@@ -1029,7 +1134,7 @@ declare class KrisspyClient {
1029
1134
  *
1030
1135
  * const krisspy = createClient({
1031
1136
  * backendId: 'your-backend-id',
1032
- * apiKey: 'your-api-key', // optional
1137
+ * anonKey: 'your-anon-key', // from backend settings
1033
1138
  * })
1034
1139
  *
1035
1140
  * // Auth
@@ -1071,10 +1176,10 @@ declare class KrisspyClient {
1071
1176
  * @example
1072
1177
  * const krisspy = createClient({
1073
1178
  * backendId: 'abc123',
1074
- * apiKey: 'your-api-key',
1179
+ * anonKey: 'your-anon-key',
1075
1180
  * url: 'https://api.krisspy.ai', // optional
1076
1181
  * })
1077
1182
  */
1078
1183
  declare function createClient(options: KrisspyClientOptions): KrisspyClient;
1079
1184
 
1080
- export { type AuthChangeEvent, type AuthResponse, type ChannelState, type FileObject, type FileUploadOptions, type Filter, type FilterOperator, type FunctionInvokeOptions, type FunctionResponse, HttpClient, KrisspyAuth, KrisspyClient, type KrisspyClientOptions, type KrisspyError, KrisspyRealtime, KrisspyStorage, type MutationResponse, type OAuthProvider, type OrderBy, type PostgresChangesConfig, QueryBuilder, type QueryOptions, type QueryResponse, RealtimeChannel, type RealtimeEvent, type RealtimePayload, type Session, type SignInCredentials, type SignUpCredentials, type SingleResponse, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient };
1185
+ export { type AnalyticsEvent, type AnalyticsInitOptions, type AuthChangeEvent, type AuthResponse, type ChannelState, type FileObject, type FileUploadOptions, type Filter, type FilterOperator, type FunctionInvokeOptions, type FunctionResponse, HttpClient, KrisspyAnalytics, KrisspyAuth, KrisspyClient, type KrisspyClientOptions, type KrisspyError, KrisspyRealtime, KrisspyStorage, type MutationResponse, type OAuthProvider, type OrderBy, type PostgresChangesConfig, QueryBuilder, type QueryOptions, type QueryResponse, RealtimeChannel, type RealtimeEvent, type RealtimePayload, type Session, type SignInCredentials, type SignUpCredentials, type SingleResponse, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  HttpClient: () => HttpClient,
24
+ KrisspyAnalytics: () => KrisspyAnalytics,
24
25
  KrisspyAuth: () => KrisspyAuth,
25
26
  KrisspyClient: () => KrisspyClient,
26
27
  KrisspyRealtime: () => KrisspyRealtime,
@@ -1144,6 +1145,192 @@ var KrisspyRealtime = class {
1144
1145
  }
1145
1146
  };
1146
1147
 
1148
+ // src/analytics.ts
1149
+ var KrisspyAnalytics = class {
1150
+ constructor(http, backendId) {
1151
+ this.queue = [];
1152
+ this.sessionId = null;
1153
+ this.userId = null;
1154
+ this.userTraits = {};
1155
+ this.initialized = false;
1156
+ this.flushTimer = null;
1157
+ this.options = {};
1158
+ this.originalPushState = null;
1159
+ this.popstateHandler = null;
1160
+ this.unloadHandler = null;
1161
+ this.http = http;
1162
+ this.backendId = backendId;
1163
+ }
1164
+ /**
1165
+ * Initialize analytics tracking
1166
+ *
1167
+ * @example
1168
+ * krisspy.analytics.init()
1169
+ * krisspy.analytics.init({ autoTrackPageViews: true, flushInterval: 3000 })
1170
+ */
1171
+ init(options) {
1172
+ if (this.initialized) return;
1173
+ if (typeof window === "undefined") return;
1174
+ this.options = {
1175
+ autoTrackPageViews: true,
1176
+ autoTrackNavigation: true,
1177
+ flushInterval: 5e3,
1178
+ debug: false,
1179
+ ...options
1180
+ };
1181
+ this.sessionId = this.getOrCreateSessionId();
1182
+ this.initialized = true;
1183
+ if (this.options.autoTrackPageViews) {
1184
+ this.track("session_start", {});
1185
+ this.track("page_view", { url: location.href, referrer: document.referrer });
1186
+ }
1187
+ if (this.options.autoTrackNavigation) {
1188
+ this.setupNavigationTracking();
1189
+ }
1190
+ this.unloadHandler = () => {
1191
+ this.track("session_end", {});
1192
+ this.flushSync();
1193
+ };
1194
+ window.addEventListener("beforeunload", this.unloadHandler);
1195
+ this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval);
1196
+ if (this.options.debug) {
1197
+ console.log("[Krisspy Analytics] Initialized", { backendId: this.backendId, sessionId: this.sessionId });
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Track a custom event
1202
+ *
1203
+ * @example
1204
+ * krisspy.analytics.track('button_click', { label: 'signup', variant: 'blue' })
1205
+ * krisspy.analytics.track('purchase', { amount: 29.99, plan: 'pro' })
1206
+ */
1207
+ track(eventName, properties) {
1208
+ if (!this.initialized || typeof window === "undefined") return;
1209
+ const event = {
1210
+ eventName,
1211
+ sessionId: this.sessionId,
1212
+ userId: this.userId || void 0,
1213
+ properties: {
1214
+ ...properties,
1215
+ ...this.userId ? { _user_traits: this.userTraits } : {}
1216
+ },
1217
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1218
+ };
1219
+ this.queue.push(event);
1220
+ if (this.options.debug) {
1221
+ console.log("[Krisspy Analytics] Tracked:", eventName, properties);
1222
+ }
1223
+ }
1224
+ /**
1225
+ * Identify a user (attach userId + traits to all future events)
1226
+ *
1227
+ * @example
1228
+ * krisspy.analytics.identify('user_123', { name: 'John', plan: 'pro' })
1229
+ */
1230
+ identify(userId, traits) {
1231
+ this.userId = userId;
1232
+ this.userTraits = traits || {};
1233
+ if (this.initialized) {
1234
+ this.track("identify", { userId, ...traits });
1235
+ }
1236
+ }
1237
+ /**
1238
+ * Flush event queue to the server (async)
1239
+ */
1240
+ async flush() {
1241
+ if (!this.queue.length) return;
1242
+ const events = this.queue.splice(0, 50);
1243
+ const payload = { appId: this.backendId, events };
1244
+ try {
1245
+ await this.http.post("/api/v1/analytics/events", payload);
1246
+ if (this.options.debug) {
1247
+ console.log(`[Krisspy Analytics] Flushed ${events.length} events`);
1248
+ }
1249
+ } catch {
1250
+ this.queue.unshift(...events);
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Synchronous flush using sendBeacon (for page unload)
1255
+ */
1256
+ flushSync() {
1257
+ if (!this.queue.length) return;
1258
+ const events = this.queue.splice(0, 50);
1259
+ const payload = JSON.stringify({ appId: this.backendId, events });
1260
+ try {
1261
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
1262
+ const blob = new Blob([payload], { type: "application/json" });
1263
+ navigator.sendBeacon(this.getIngestUrl(), blob);
1264
+ } else {
1265
+ fetch(this.getIngestUrl(), {
1266
+ method: "POST",
1267
+ headers: { "Content-Type": "application/json" },
1268
+ body: payload,
1269
+ keepalive: true
1270
+ }).catch(() => {
1271
+ });
1272
+ }
1273
+ } catch {
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Stop tracking and cleanup all listeners/timers
1278
+ */
1279
+ destroy() {
1280
+ if (!this.initialized) return;
1281
+ this.flushSync();
1282
+ if (this.flushTimer) {
1283
+ clearInterval(this.flushTimer);
1284
+ this.flushTimer = null;
1285
+ }
1286
+ if (this.originalPushState) {
1287
+ history.pushState = this.originalPushState;
1288
+ this.originalPushState = null;
1289
+ }
1290
+ if (this.popstateHandler) {
1291
+ window.removeEventListener("popstate", this.popstateHandler);
1292
+ this.popstateHandler = null;
1293
+ }
1294
+ if (this.unloadHandler) {
1295
+ window.removeEventListener("beforeunload", this.unloadHandler);
1296
+ this.unloadHandler = null;
1297
+ }
1298
+ this.initialized = false;
1299
+ this.queue = [];
1300
+ if (this.options.debug) {
1301
+ console.log("[Krisspy Analytics] Destroyed");
1302
+ }
1303
+ }
1304
+ // ── Private helpers ──────────────────────────────────────────────────────
1305
+ getOrCreateSessionId() {
1306
+ const key = "_ka_sid";
1307
+ try {
1308
+ const existing = localStorage.getItem(key);
1309
+ if (existing) return existing;
1310
+ const id = Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
1311
+ localStorage.setItem(key, id);
1312
+ return id;
1313
+ } catch {
1314
+ return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
1315
+ }
1316
+ }
1317
+ getIngestUrl() {
1318
+ return `${this.http.baseUrl}/api/v1/analytics/events`;
1319
+ }
1320
+ setupNavigationTracking() {
1321
+ this.originalPushState = history.pushState;
1322
+ const self = this;
1323
+ history.pushState = function(...args) {
1324
+ self.originalPushState.apply(history, args);
1325
+ self.track("page_view", { url: location.href });
1326
+ };
1327
+ this.popstateHandler = () => {
1328
+ this.track("page_view", { url: location.href });
1329
+ };
1330
+ window.addEventListener("popstate", this.popstateHandler);
1331
+ }
1332
+ };
1333
+
1147
1334
  // src/query-builder.ts
1148
1335
  var QueryBuilder = class {
1149
1336
  constructor(http, backendId, tableName, useRLS = true) {
@@ -1431,13 +1618,14 @@ var KrisspyClient = class {
1431
1618
  baseUrl: this.baseUrl,
1432
1619
  headers: {
1433
1620
  ...options.headers,
1434
- ...options.apiKey ? { "Authorization": `Bearer ${options.apiKey}` } : {}
1621
+ "apikey": options.anonKey
1435
1622
  },
1436
1623
  debug: options.debug
1437
1624
  });
1438
1625
  this._auth = new KrisspyAuth(this.http, this.backendId);
1439
1626
  this._storage = new KrisspyStorage(this.http, this.backendId);
1440
1627
  this._realtime = new KrisspyRealtime(this.baseUrl, this.backendId, this.debug);
1628
+ this._analytics = new KrisspyAnalytics(this.http, this.backendId);
1441
1629
  this._auth.onAuthStateChange((event) => {
1442
1630
  if (event === "SIGNED_IN") {
1443
1631
  const session = this._auth.session();
@@ -1498,6 +1686,25 @@ var KrisspyClient = class {
1498
1686
  get storage() {
1499
1687
  return this._storage;
1500
1688
  }
1689
+ /**
1690
+ * Analytics module for event tracking
1691
+ *
1692
+ * @example
1693
+ * // Initialize tracking
1694
+ * krisspy.analytics.init()
1695
+ *
1696
+ * // Track custom events
1697
+ * krisspy.analytics.track('button_click', { label: 'signup' })
1698
+ *
1699
+ * // Identify a user
1700
+ * krisspy.analytics.identify('user_123', { plan: 'pro' })
1701
+ *
1702
+ * // Cleanup
1703
+ * krisspy.analytics.destroy()
1704
+ */
1705
+ get analytics() {
1706
+ return this._analytics;
1707
+ }
1501
1708
  /**
1502
1709
  * Create a realtime channel for subscribing to database changes
1503
1710
  *
@@ -1666,6 +1873,7 @@ function createClient(options) {
1666
1873
  // Annotate the CommonJS export names for ESM import in node:
1667
1874
  0 && (module.exports = {
1668
1875
  HttpClient,
1876
+ KrisspyAnalytics,
1669
1877
  KrisspyAuth,
1670
1878
  KrisspyClient,
1671
1879
  KrisspyRealtime,
package/dist/index.mjs CHANGED
@@ -1110,6 +1110,192 @@ var KrisspyRealtime = class {
1110
1110
  }
1111
1111
  };
1112
1112
 
1113
+ // src/analytics.ts
1114
+ var KrisspyAnalytics = class {
1115
+ constructor(http, backendId) {
1116
+ this.queue = [];
1117
+ this.sessionId = null;
1118
+ this.userId = null;
1119
+ this.userTraits = {};
1120
+ this.initialized = false;
1121
+ this.flushTimer = null;
1122
+ this.options = {};
1123
+ this.originalPushState = null;
1124
+ this.popstateHandler = null;
1125
+ this.unloadHandler = null;
1126
+ this.http = http;
1127
+ this.backendId = backendId;
1128
+ }
1129
+ /**
1130
+ * Initialize analytics tracking
1131
+ *
1132
+ * @example
1133
+ * krisspy.analytics.init()
1134
+ * krisspy.analytics.init({ autoTrackPageViews: true, flushInterval: 3000 })
1135
+ */
1136
+ init(options) {
1137
+ if (this.initialized) return;
1138
+ if (typeof window === "undefined") return;
1139
+ this.options = {
1140
+ autoTrackPageViews: true,
1141
+ autoTrackNavigation: true,
1142
+ flushInterval: 5e3,
1143
+ debug: false,
1144
+ ...options
1145
+ };
1146
+ this.sessionId = this.getOrCreateSessionId();
1147
+ this.initialized = true;
1148
+ if (this.options.autoTrackPageViews) {
1149
+ this.track("session_start", {});
1150
+ this.track("page_view", { url: location.href, referrer: document.referrer });
1151
+ }
1152
+ if (this.options.autoTrackNavigation) {
1153
+ this.setupNavigationTracking();
1154
+ }
1155
+ this.unloadHandler = () => {
1156
+ this.track("session_end", {});
1157
+ this.flushSync();
1158
+ };
1159
+ window.addEventListener("beforeunload", this.unloadHandler);
1160
+ this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval);
1161
+ if (this.options.debug) {
1162
+ console.log("[Krisspy Analytics] Initialized", { backendId: this.backendId, sessionId: this.sessionId });
1163
+ }
1164
+ }
1165
+ /**
1166
+ * Track a custom event
1167
+ *
1168
+ * @example
1169
+ * krisspy.analytics.track('button_click', { label: 'signup', variant: 'blue' })
1170
+ * krisspy.analytics.track('purchase', { amount: 29.99, plan: 'pro' })
1171
+ */
1172
+ track(eventName, properties) {
1173
+ if (!this.initialized || typeof window === "undefined") return;
1174
+ const event = {
1175
+ eventName,
1176
+ sessionId: this.sessionId,
1177
+ userId: this.userId || void 0,
1178
+ properties: {
1179
+ ...properties,
1180
+ ...this.userId ? { _user_traits: this.userTraits } : {}
1181
+ },
1182
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1183
+ };
1184
+ this.queue.push(event);
1185
+ if (this.options.debug) {
1186
+ console.log("[Krisspy Analytics] Tracked:", eventName, properties);
1187
+ }
1188
+ }
1189
+ /**
1190
+ * Identify a user (attach userId + traits to all future events)
1191
+ *
1192
+ * @example
1193
+ * krisspy.analytics.identify('user_123', { name: 'John', plan: 'pro' })
1194
+ */
1195
+ identify(userId, traits) {
1196
+ this.userId = userId;
1197
+ this.userTraits = traits || {};
1198
+ if (this.initialized) {
1199
+ this.track("identify", { userId, ...traits });
1200
+ }
1201
+ }
1202
+ /**
1203
+ * Flush event queue to the server (async)
1204
+ */
1205
+ async flush() {
1206
+ if (!this.queue.length) return;
1207
+ const events = this.queue.splice(0, 50);
1208
+ const payload = { appId: this.backendId, events };
1209
+ try {
1210
+ await this.http.post("/api/v1/analytics/events", payload);
1211
+ if (this.options.debug) {
1212
+ console.log(`[Krisspy Analytics] Flushed ${events.length} events`);
1213
+ }
1214
+ } catch {
1215
+ this.queue.unshift(...events);
1216
+ }
1217
+ }
1218
+ /**
1219
+ * Synchronous flush using sendBeacon (for page unload)
1220
+ */
1221
+ flushSync() {
1222
+ if (!this.queue.length) return;
1223
+ const events = this.queue.splice(0, 50);
1224
+ const payload = JSON.stringify({ appId: this.backendId, events });
1225
+ try {
1226
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
1227
+ const blob = new Blob([payload], { type: "application/json" });
1228
+ navigator.sendBeacon(this.getIngestUrl(), blob);
1229
+ } else {
1230
+ fetch(this.getIngestUrl(), {
1231
+ method: "POST",
1232
+ headers: { "Content-Type": "application/json" },
1233
+ body: payload,
1234
+ keepalive: true
1235
+ }).catch(() => {
1236
+ });
1237
+ }
1238
+ } catch {
1239
+ }
1240
+ }
1241
+ /**
1242
+ * Stop tracking and cleanup all listeners/timers
1243
+ */
1244
+ destroy() {
1245
+ if (!this.initialized) return;
1246
+ this.flushSync();
1247
+ if (this.flushTimer) {
1248
+ clearInterval(this.flushTimer);
1249
+ this.flushTimer = null;
1250
+ }
1251
+ if (this.originalPushState) {
1252
+ history.pushState = this.originalPushState;
1253
+ this.originalPushState = null;
1254
+ }
1255
+ if (this.popstateHandler) {
1256
+ window.removeEventListener("popstate", this.popstateHandler);
1257
+ this.popstateHandler = null;
1258
+ }
1259
+ if (this.unloadHandler) {
1260
+ window.removeEventListener("beforeunload", this.unloadHandler);
1261
+ this.unloadHandler = null;
1262
+ }
1263
+ this.initialized = false;
1264
+ this.queue = [];
1265
+ if (this.options.debug) {
1266
+ console.log("[Krisspy Analytics] Destroyed");
1267
+ }
1268
+ }
1269
+ // ── Private helpers ──────────────────────────────────────────────────────
1270
+ getOrCreateSessionId() {
1271
+ const key = "_ka_sid";
1272
+ try {
1273
+ const existing = localStorage.getItem(key);
1274
+ if (existing) return existing;
1275
+ const id = Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
1276
+ localStorage.setItem(key, id);
1277
+ return id;
1278
+ } catch {
1279
+ return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
1280
+ }
1281
+ }
1282
+ getIngestUrl() {
1283
+ return `${this.http.baseUrl}/api/v1/analytics/events`;
1284
+ }
1285
+ setupNavigationTracking() {
1286
+ this.originalPushState = history.pushState;
1287
+ const self = this;
1288
+ history.pushState = function(...args) {
1289
+ self.originalPushState.apply(history, args);
1290
+ self.track("page_view", { url: location.href });
1291
+ };
1292
+ this.popstateHandler = () => {
1293
+ this.track("page_view", { url: location.href });
1294
+ };
1295
+ window.addEventListener("popstate", this.popstateHandler);
1296
+ }
1297
+ };
1298
+
1113
1299
  // src/query-builder.ts
1114
1300
  var QueryBuilder = class {
1115
1301
  constructor(http, backendId, tableName, useRLS = true) {
@@ -1397,13 +1583,14 @@ var KrisspyClient = class {
1397
1583
  baseUrl: this.baseUrl,
1398
1584
  headers: {
1399
1585
  ...options.headers,
1400
- ...options.apiKey ? { "Authorization": `Bearer ${options.apiKey}` } : {}
1586
+ "apikey": options.anonKey
1401
1587
  },
1402
1588
  debug: options.debug
1403
1589
  });
1404
1590
  this._auth = new KrisspyAuth(this.http, this.backendId);
1405
1591
  this._storage = new KrisspyStorage(this.http, this.backendId);
1406
1592
  this._realtime = new KrisspyRealtime(this.baseUrl, this.backendId, this.debug);
1593
+ this._analytics = new KrisspyAnalytics(this.http, this.backendId);
1407
1594
  this._auth.onAuthStateChange((event) => {
1408
1595
  if (event === "SIGNED_IN") {
1409
1596
  const session = this._auth.session();
@@ -1464,6 +1651,25 @@ var KrisspyClient = class {
1464
1651
  get storage() {
1465
1652
  return this._storage;
1466
1653
  }
1654
+ /**
1655
+ * Analytics module for event tracking
1656
+ *
1657
+ * @example
1658
+ * // Initialize tracking
1659
+ * krisspy.analytics.init()
1660
+ *
1661
+ * // Track custom events
1662
+ * krisspy.analytics.track('button_click', { label: 'signup' })
1663
+ *
1664
+ * // Identify a user
1665
+ * krisspy.analytics.identify('user_123', { plan: 'pro' })
1666
+ *
1667
+ * // Cleanup
1668
+ * krisspy.analytics.destroy()
1669
+ */
1670
+ get analytics() {
1671
+ return this._analytics;
1672
+ }
1467
1673
  /**
1468
1674
  * Create a realtime channel for subscribing to database changes
1469
1675
  *
@@ -1631,6 +1837,7 @@ function createClient(options) {
1631
1837
  }
1632
1838
  export {
1633
1839
  HttpClient,
1840
+ KrisspyAnalytics,
1634
1841
  KrisspyAuth,
1635
1842
  KrisspyClient,
1636
1843
  KrisspyRealtime,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "krisspy-sdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Krisspy Cloud SDK - Database, Auth, Storage, and Functions for your apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",