krisspy-sdk 0.6.0 → 0.8.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/README.md CHANGED
@@ -17,14 +17,37 @@ pnpm add @krisspy/sdk
17
17
  ## Quick Start
18
18
 
19
19
  ```typescript
20
- import { createClient } from '@krisspy/sdk'
20
+ import { createClient } from 'krisspy-sdk'
21
21
 
22
22
  const krisspy = createClient({
23
23
  backendId: 'your-backend-id',
24
- apiKey: 'your-api-key', // optional for public access
24
+ apiKey: 'your-api-key',
25
25
  })
26
26
  ```
27
27
 
28
+ ### React Native / Expo
29
+
30
+ When using AsyncStorage, call `initialize()` before accessing auth state:
31
+
32
+ ```typescript
33
+ import { createClient } from 'krisspy-sdk'
34
+ import AsyncStorage from '@react-native-async-storage/async-storage'
35
+
36
+ const krisspy = createClient({
37
+ backendId: 'your-backend-id',
38
+ apiKey: 'your-api-key',
39
+ storage: AsyncStorage,
40
+ })
41
+
42
+ // Required — waits for cached session to load from AsyncStorage
43
+ await krisspy.initialize()
44
+
45
+ // Now safe to use
46
+ const user = krisspy.auth.user()
47
+ ```
48
+
49
+ On web (localStorage is synchronous), `initialize()` resolves instantly — calling it is optional but harmless.
50
+
28
51
  ## Authentication
29
52
 
30
53
  ### Sign Up
@@ -227,15 +250,19 @@ console.log('Products:', data)
227
250
  const krisspy = createClient({
228
251
  // Required
229
252
  backendId: 'your-backend-id',
253
+ apiKey: 'your-api-key',
230
254
 
231
255
  // Optional
232
256
  url: 'https://api.krisspy.ai', // Custom API URL
233
- apiKey: 'your-api-key', // For authenticated requests
257
+ storage: AsyncStorage, // React Native: session persistence
234
258
  headers: { // Custom headers
235
259
  'X-Custom-Header': 'value'
236
260
  },
237
261
  debug: true, // Enable debug logging
238
262
  })
263
+
264
+ // React Native only — wait for cached session
265
+ await krisspy.initialize()
239
266
  ```
240
267
 
241
268
  ## License
package/dist/index.d.mts CHANGED
@@ -238,7 +238,16 @@ declare class KrisspyAuth {
238
238
  private listeners;
239
239
  private refreshInterval?;
240
240
  private storage;
241
+ /** Resolves when session restoration is complete (including async storage) */
242
+ private _initialized;
243
+ private _resolveInit;
241
244
  constructor(http: HttpClient, backendId: string, storage?: StorageAdapter);
245
+ /**
246
+ * Wait until session has been restored from storage.
247
+ * On web (localStorage), this resolves immediately.
248
+ * On React Native (AsyncStorage), this waits for the async read to complete.
249
+ */
250
+ initialize(): Promise<void>;
242
251
  /**
243
252
  * Get current session from storage
244
253
  */
@@ -818,6 +827,114 @@ declare class KrisspyAnalytics {
818
827
  private setupNavigationTracking;
819
828
  }
820
829
 
830
+ /**
831
+ * Notifications Module - Expo Push Notifications for Krisspy backends
832
+ * Compatible with Expo Go, EAS builds, and bare React Native.
833
+ */
834
+
835
+ interface PushToken {
836
+ id: string;
837
+ token: string;
838
+ platform?: string;
839
+ device_id?: string;
840
+ created_at: string;
841
+ updated_at: string;
842
+ }
843
+ interface NotificationTicket {
844
+ id?: string;
845
+ status: 'ok' | 'error';
846
+ message?: string;
847
+ details?: {
848
+ error?: string;
849
+ };
850
+ }
851
+ interface SendNotificationParams {
852
+ /** Target user ID (required for send, omit for broadcast) */
853
+ userId?: string;
854
+ /** Notification title */
855
+ title: string;
856
+ /** Notification body text */
857
+ body: string;
858
+ /** Custom data payload (available in notification handler) */
859
+ data?: Record<string, any>;
860
+ /** Sound: 'default' or null to disable */
861
+ sound?: 'default' | null;
862
+ /** iOS badge count */
863
+ badge?: number;
864
+ /** Priority: 'default', 'normal', or 'high' */
865
+ priority?: 'default' | 'normal' | 'high';
866
+ }
867
+ declare class KrisspyNotifications {
868
+ private http;
869
+ private backendId;
870
+ constructor(http: HttpClient, backendId: string);
871
+ /**
872
+ * Register a push token for the current user.
873
+ * Call this after getting the ExpoPushToken from expo-notifications.
874
+ *
875
+ * @example
876
+ * import * as Notifications from 'expo-notifications'
877
+ * const { data: token } = await Notifications.getExpoPushTokenAsync()
878
+ * await krisspy.notifications.register(token, 'ios')
879
+ */
880
+ register(token: string, platform?: 'ios' | 'android' | 'web'): Promise<{
881
+ data: PushToken | null;
882
+ error: KrisspyError | null;
883
+ }>;
884
+ /**
885
+ * Unregister a push token (e.g., on sign out or when user disables notifications).
886
+ */
887
+ unregister(token: string): Promise<{
888
+ error: KrisspyError | null;
889
+ }>;
890
+ /**
891
+ * List all push tokens registered for the current user.
892
+ */
893
+ getTokens(): Promise<{
894
+ data: PushToken[] | null;
895
+ error: KrisspyError | null;
896
+ }>;
897
+ /**
898
+ * Send a push notification to a specific user.
899
+ * Requires service key (server-side only).
900
+ *
901
+ * @example
902
+ * // In an Azure Function / serverless function:
903
+ * const krisspy = createClient({ backendId: '...', apiKey: '...', serviceKey: process.env.SERVICE_KEY })
904
+ * await krisspy.notifications.send({
905
+ * userId: 'user123',
906
+ * title: 'New message',
907
+ * body: 'You have a new message from Alice',
908
+ * data: { screen: 'chat', chatId: '456' },
909
+ * })
910
+ */
911
+ send(params: SendNotificationParams & {
912
+ userId: string;
913
+ }): Promise<{
914
+ data: {
915
+ tickets: NotificationTicket[];
916
+ } | null;
917
+ error: KrisspyError | null;
918
+ }>;
919
+ /**
920
+ * Broadcast a push notification to all registered users.
921
+ * Requires service key (server-side only).
922
+ *
923
+ * @example
924
+ * await krisspy.notifications.broadcast({
925
+ * title: 'App Update',
926
+ * body: 'Version 2.0 is now available!',
927
+ * })
928
+ */
929
+ broadcast(params: SendNotificationParams): Promise<{
930
+ data: {
931
+ tickets: NotificationTicket[];
932
+ count: number;
933
+ } | null;
934
+ error: KrisspyError | null;
935
+ }>;
936
+ }
937
+
821
938
  /**
822
939
  * Query Builder - Supabase-style fluent API for database queries
823
940
  */
@@ -972,9 +1089,21 @@ declare class KrisspyClient {
972
1089
  private _storage;
973
1090
  private _realtime;
974
1091
  private _analytics;
1092
+ private _notifications;
975
1093
  private dataMode;
976
1094
  private debug;
977
1095
  constructor(options: KrisspyClientOptions);
1096
+ /**
1097
+ * Wait until the client is fully initialized (session restored from storage).
1098
+ * **Required on React Native / Expo** when using AsyncStorage.
1099
+ * On web (localStorage), this resolves instantly.
1100
+ *
1101
+ * @example
1102
+ * const krisspy = createClient({ backendId: '...', apiKey: '...' });
1103
+ * await krisspy.initialize(); // wait for cached session to load
1104
+ * const user = krisspy.auth.user(); // now safe to use
1105
+ */
1106
+ initialize(): Promise<void>;
978
1107
  /**
979
1108
  * Auth module for user authentication
980
1109
  *
@@ -1037,6 +1166,27 @@ declare class KrisspyClient {
1037
1166
  * krisspy.analytics.destroy()
1038
1167
  */
1039
1168
  get analytics(): KrisspyAnalytics;
1169
+ /**
1170
+ * Notifications module for Expo push notifications
1171
+ *
1172
+ * @example
1173
+ * // Register push token (client-side, after getting token from expo-notifications)
1174
+ * await krisspy.notifications.register(expoPushToken, 'ios')
1175
+ *
1176
+ * // Unregister on sign out
1177
+ * await krisspy.notifications.unregister(expoPushToken)
1178
+ *
1179
+ * // Send to user (server-side, requires serviceKey)
1180
+ * await krisspy.notifications.send({
1181
+ * userId: 'user123',
1182
+ * title: 'Hello',
1183
+ * body: 'You have a new message',
1184
+ * })
1185
+ *
1186
+ * // Broadcast to all (server-side, requires serviceKey)
1187
+ * await krisspy.notifications.broadcast({ title: 'Update', body: 'v2.0 released!' })
1188
+ */
1189
+ get notifications(): KrisspyNotifications;
1040
1190
  /**
1041
1191
  * Create a realtime channel for subscribing to database changes
1042
1192
  *
@@ -1213,12 +1363,20 @@ declare class KrisspyClient {
1213
1363
  * @returns KrisspyClient instance
1214
1364
  *
1215
1365
  * @example
1366
+ * // Web (localStorage is sync — works immediately)
1367
+ * const krisspy = createClient({
1368
+ * backendId: 'abc123',
1369
+ * apiKey: 'your-api-key',
1370
+ * })
1371
+ *
1372
+ * // React Native / Expo (AsyncStorage is async — call initialize())
1216
1373
  * const krisspy = createClient({
1217
1374
  * backendId: 'abc123',
1218
1375
  * apiKey: 'your-api-key',
1219
- * url: 'https://krisspy-cloud-backend.thankfulhill-66fc9e74.westeurope.azurecontainerapps.io', // optional
1376
+ * storage: AsyncStorage,
1220
1377
  * })
1378
+ * await krisspy.initialize() // wait for cached session to load
1221
1379
  */
1222
1380
  declare function createClient(options: KrisspyClientOptions): KrisspyClient;
1223
1381
 
1224
- 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, type StorageAdapter, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient, isBrowser, isReactNative };
1382
+ 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, KrisspyNotifications, KrisspyRealtime, KrisspyStorage, type MutationResponse, type NotificationTicket, type OAuthProvider, type OrderBy, type PostgresChangesConfig, type PushToken, QueryBuilder, type QueryOptions, type QueryResponse, RealtimeChannel, type RealtimeEvent, type RealtimePayload, type SendNotificationParams, type Session, type SignInCredentials, type SignUpCredentials, type SingleResponse, type StorageAdapter, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient, isBrowser, isReactNative };
package/dist/index.d.ts CHANGED
@@ -238,7 +238,16 @@ declare class KrisspyAuth {
238
238
  private listeners;
239
239
  private refreshInterval?;
240
240
  private storage;
241
+ /** Resolves when session restoration is complete (including async storage) */
242
+ private _initialized;
243
+ private _resolveInit;
241
244
  constructor(http: HttpClient, backendId: string, storage?: StorageAdapter);
245
+ /**
246
+ * Wait until session has been restored from storage.
247
+ * On web (localStorage), this resolves immediately.
248
+ * On React Native (AsyncStorage), this waits for the async read to complete.
249
+ */
250
+ initialize(): Promise<void>;
242
251
  /**
243
252
  * Get current session from storage
244
253
  */
@@ -818,6 +827,114 @@ declare class KrisspyAnalytics {
818
827
  private setupNavigationTracking;
819
828
  }
820
829
 
830
+ /**
831
+ * Notifications Module - Expo Push Notifications for Krisspy backends
832
+ * Compatible with Expo Go, EAS builds, and bare React Native.
833
+ */
834
+
835
+ interface PushToken {
836
+ id: string;
837
+ token: string;
838
+ platform?: string;
839
+ device_id?: string;
840
+ created_at: string;
841
+ updated_at: string;
842
+ }
843
+ interface NotificationTicket {
844
+ id?: string;
845
+ status: 'ok' | 'error';
846
+ message?: string;
847
+ details?: {
848
+ error?: string;
849
+ };
850
+ }
851
+ interface SendNotificationParams {
852
+ /** Target user ID (required for send, omit for broadcast) */
853
+ userId?: string;
854
+ /** Notification title */
855
+ title: string;
856
+ /** Notification body text */
857
+ body: string;
858
+ /** Custom data payload (available in notification handler) */
859
+ data?: Record<string, any>;
860
+ /** Sound: 'default' or null to disable */
861
+ sound?: 'default' | null;
862
+ /** iOS badge count */
863
+ badge?: number;
864
+ /** Priority: 'default', 'normal', or 'high' */
865
+ priority?: 'default' | 'normal' | 'high';
866
+ }
867
+ declare class KrisspyNotifications {
868
+ private http;
869
+ private backendId;
870
+ constructor(http: HttpClient, backendId: string);
871
+ /**
872
+ * Register a push token for the current user.
873
+ * Call this after getting the ExpoPushToken from expo-notifications.
874
+ *
875
+ * @example
876
+ * import * as Notifications from 'expo-notifications'
877
+ * const { data: token } = await Notifications.getExpoPushTokenAsync()
878
+ * await krisspy.notifications.register(token, 'ios')
879
+ */
880
+ register(token: string, platform?: 'ios' | 'android' | 'web'): Promise<{
881
+ data: PushToken | null;
882
+ error: KrisspyError | null;
883
+ }>;
884
+ /**
885
+ * Unregister a push token (e.g., on sign out or when user disables notifications).
886
+ */
887
+ unregister(token: string): Promise<{
888
+ error: KrisspyError | null;
889
+ }>;
890
+ /**
891
+ * List all push tokens registered for the current user.
892
+ */
893
+ getTokens(): Promise<{
894
+ data: PushToken[] | null;
895
+ error: KrisspyError | null;
896
+ }>;
897
+ /**
898
+ * Send a push notification to a specific user.
899
+ * Requires service key (server-side only).
900
+ *
901
+ * @example
902
+ * // In an Azure Function / serverless function:
903
+ * const krisspy = createClient({ backendId: '...', apiKey: '...', serviceKey: process.env.SERVICE_KEY })
904
+ * await krisspy.notifications.send({
905
+ * userId: 'user123',
906
+ * title: 'New message',
907
+ * body: 'You have a new message from Alice',
908
+ * data: { screen: 'chat', chatId: '456' },
909
+ * })
910
+ */
911
+ send(params: SendNotificationParams & {
912
+ userId: string;
913
+ }): Promise<{
914
+ data: {
915
+ tickets: NotificationTicket[];
916
+ } | null;
917
+ error: KrisspyError | null;
918
+ }>;
919
+ /**
920
+ * Broadcast a push notification to all registered users.
921
+ * Requires service key (server-side only).
922
+ *
923
+ * @example
924
+ * await krisspy.notifications.broadcast({
925
+ * title: 'App Update',
926
+ * body: 'Version 2.0 is now available!',
927
+ * })
928
+ */
929
+ broadcast(params: SendNotificationParams): Promise<{
930
+ data: {
931
+ tickets: NotificationTicket[];
932
+ count: number;
933
+ } | null;
934
+ error: KrisspyError | null;
935
+ }>;
936
+ }
937
+
821
938
  /**
822
939
  * Query Builder - Supabase-style fluent API for database queries
823
940
  */
@@ -972,9 +1089,21 @@ declare class KrisspyClient {
972
1089
  private _storage;
973
1090
  private _realtime;
974
1091
  private _analytics;
1092
+ private _notifications;
975
1093
  private dataMode;
976
1094
  private debug;
977
1095
  constructor(options: KrisspyClientOptions);
1096
+ /**
1097
+ * Wait until the client is fully initialized (session restored from storage).
1098
+ * **Required on React Native / Expo** when using AsyncStorage.
1099
+ * On web (localStorage), this resolves instantly.
1100
+ *
1101
+ * @example
1102
+ * const krisspy = createClient({ backendId: '...', apiKey: '...' });
1103
+ * await krisspy.initialize(); // wait for cached session to load
1104
+ * const user = krisspy.auth.user(); // now safe to use
1105
+ */
1106
+ initialize(): Promise<void>;
978
1107
  /**
979
1108
  * Auth module for user authentication
980
1109
  *
@@ -1037,6 +1166,27 @@ declare class KrisspyClient {
1037
1166
  * krisspy.analytics.destroy()
1038
1167
  */
1039
1168
  get analytics(): KrisspyAnalytics;
1169
+ /**
1170
+ * Notifications module for Expo push notifications
1171
+ *
1172
+ * @example
1173
+ * // Register push token (client-side, after getting token from expo-notifications)
1174
+ * await krisspy.notifications.register(expoPushToken, 'ios')
1175
+ *
1176
+ * // Unregister on sign out
1177
+ * await krisspy.notifications.unregister(expoPushToken)
1178
+ *
1179
+ * // Send to user (server-side, requires serviceKey)
1180
+ * await krisspy.notifications.send({
1181
+ * userId: 'user123',
1182
+ * title: 'Hello',
1183
+ * body: 'You have a new message',
1184
+ * })
1185
+ *
1186
+ * // Broadcast to all (server-side, requires serviceKey)
1187
+ * await krisspy.notifications.broadcast({ title: 'Update', body: 'v2.0 released!' })
1188
+ */
1189
+ get notifications(): KrisspyNotifications;
1040
1190
  /**
1041
1191
  * Create a realtime channel for subscribing to database changes
1042
1192
  *
@@ -1213,12 +1363,20 @@ declare class KrisspyClient {
1213
1363
  * @returns KrisspyClient instance
1214
1364
  *
1215
1365
  * @example
1366
+ * // Web (localStorage is sync — works immediately)
1367
+ * const krisspy = createClient({
1368
+ * backendId: 'abc123',
1369
+ * apiKey: 'your-api-key',
1370
+ * })
1371
+ *
1372
+ * // React Native / Expo (AsyncStorage is async — call initialize())
1216
1373
  * const krisspy = createClient({
1217
1374
  * backendId: 'abc123',
1218
1375
  * apiKey: 'your-api-key',
1219
- * url: 'https://krisspy-cloud-backend.thankfulhill-66fc9e74.westeurope.azurecontainerapps.io', // optional
1376
+ * storage: AsyncStorage,
1220
1377
  * })
1378
+ * await krisspy.initialize() // wait for cached session to load
1221
1379
  */
1222
1380
  declare function createClient(options: KrisspyClientOptions): KrisspyClient;
1223
1381
 
1224
- 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, type StorageAdapter, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient, isBrowser, isReactNative };
1382
+ 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, KrisspyNotifications, KrisspyRealtime, KrisspyStorage, type MutationResponse, type NotificationTicket, type OAuthProvider, type OrderBy, type PostgresChangesConfig, type PushToken, QueryBuilder, type QueryOptions, type QueryResponse, RealtimeChannel, type RealtimeEvent, type RealtimePayload, type SendNotificationParams, type Session, type SignInCredentials, type SignUpCredentials, type SingleResponse, type StorageAdapter, StorageBucket, type UploadAndLinkOptions, type UploadAndLinkResponse, type User, createClient, isBrowser, isReactNative };
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  KrisspyAnalytics: () => KrisspyAnalytics,
42
42
  KrisspyAuth: () => KrisspyAuth,
43
43
  KrisspyClient: () => KrisspyClient,
44
+ KrisspyNotifications: () => KrisspyNotifications,
44
45
  KrisspyRealtime: () => KrisspyRealtime,
45
46
  KrisspyStorage: () => KrisspyStorage,
46
47
  QueryBuilder: () => QueryBuilder,
@@ -273,8 +274,19 @@ var KrisspyAuth = class {
273
274
  this.http = http;
274
275
  this.backendId = backendId;
275
276
  this.storage = resolveStorage(storage);
277
+ this._initialized = new Promise((resolve) => {
278
+ this._resolveInit = resolve;
279
+ });
276
280
  this.restoreSession();
277
281
  }
282
+ /**
283
+ * Wait until session has been restored from storage.
284
+ * On web (localStorage), this resolves immediately.
285
+ * On React Native (AsyncStorage), this waits for the async read to complete.
286
+ */
287
+ async initialize() {
288
+ return this._initialized;
289
+ }
278
290
  /**
279
291
  * Get current session from storage
280
292
  */
@@ -285,12 +297,18 @@ var KrisspyAuth = class {
285
297
  result.then((stored) => {
286
298
  if (stored) this.hydrateSession(stored);
287
299
  }).catch(() => {
300
+ }).finally(() => {
301
+ this._resolveInit();
288
302
  });
289
- } else if (result) {
290
- this.hydrateSession(result);
303
+ } else {
304
+ if (result) {
305
+ this.hydrateSession(result);
306
+ }
307
+ this._resolveInit();
291
308
  }
292
309
  } catch (err) {
293
310
  console.warn("[Krisspy Auth] Failed to restore session:", err);
311
+ this._resolveInit();
294
312
  }
295
313
  }
296
314
  hydrateSession(stored) {
@@ -1454,6 +1472,96 @@ var KrisspyAnalytics = class {
1454
1472
  }
1455
1473
  };
1456
1474
 
1475
+ // src/notifications.ts
1476
+ var KrisspyNotifications = class {
1477
+ constructor(http, backendId) {
1478
+ this.http = http;
1479
+ this.backendId = backendId;
1480
+ }
1481
+ // ── Client-side methods (app user JWT) ──
1482
+ /**
1483
+ * Register a push token for the current user.
1484
+ * Call this after getting the ExpoPushToken from expo-notifications.
1485
+ *
1486
+ * @example
1487
+ * import * as Notifications from 'expo-notifications'
1488
+ * const { data: token } = await Notifications.getExpoPushTokenAsync()
1489
+ * await krisspy.notifications.register(token, 'ios')
1490
+ */
1491
+ async register(token, platform) {
1492
+ var _a, _b;
1493
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/token`;
1494
+ const response = await this.http.post(path, { token, platform });
1495
+ if (response.error) {
1496
+ return { data: null, error: response.error };
1497
+ }
1498
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : null, error: null };
1499
+ }
1500
+ /**
1501
+ * Unregister a push token (e.g., on sign out or when user disables notifications).
1502
+ */
1503
+ async unregister(token) {
1504
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/unregister`;
1505
+ const response = await this.http.post(path, { token });
1506
+ return { error: response.error };
1507
+ }
1508
+ /**
1509
+ * List all push tokens registered for the current user.
1510
+ */
1511
+ async getTokens() {
1512
+ var _a, _b;
1513
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/tokens`;
1514
+ const response = await this.http.get(path);
1515
+ if (response.error) {
1516
+ return { data: null, error: response.error };
1517
+ }
1518
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : [], error: null };
1519
+ }
1520
+ // ── Server-side methods (service key required) ──
1521
+ /**
1522
+ * Send a push notification to a specific user.
1523
+ * Requires service key (server-side only).
1524
+ *
1525
+ * @example
1526
+ * // In an Azure Function / serverless function:
1527
+ * const krisspy = createClient({ backendId: '...', apiKey: '...', serviceKey: process.env.SERVICE_KEY })
1528
+ * await krisspy.notifications.send({
1529
+ * userId: 'user123',
1530
+ * title: 'New message',
1531
+ * body: 'You have a new message from Alice',
1532
+ * data: { screen: 'chat', chatId: '456' },
1533
+ * })
1534
+ */
1535
+ async send(params) {
1536
+ var _a, _b;
1537
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/send`;
1538
+ const response = await this.http.post(path, params);
1539
+ if (response.error) {
1540
+ return { data: null, error: response.error };
1541
+ }
1542
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : { tickets: [] }, error: null };
1543
+ }
1544
+ /**
1545
+ * Broadcast a push notification to all registered users.
1546
+ * Requires service key (server-side only).
1547
+ *
1548
+ * @example
1549
+ * await krisspy.notifications.broadcast({
1550
+ * title: 'App Update',
1551
+ * body: 'Version 2.0 is now available!',
1552
+ * })
1553
+ */
1554
+ async broadcast(params) {
1555
+ var _a, _b;
1556
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/send-broadcast`;
1557
+ const response = await this.http.post(path, params);
1558
+ if (response.error) {
1559
+ return { data: null, error: response.error };
1560
+ }
1561
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : { tickets: [], count: 0 }, error: null };
1562
+ }
1563
+ };
1564
+
1457
1565
  // src/query-builder.ts
1458
1566
  var QueryBuilder = class {
1459
1567
  constructor(http, backendId, tableName, dataMode = "rls") {
@@ -1769,6 +1877,7 @@ var KrisspyClient = class {
1769
1877
  this._storage = new KrisspyStorage(this.http, this.backendId);
1770
1878
  this._realtime = new KrisspyRealtime(this.baseUrl, this.backendId, this.debug);
1771
1879
  this._analytics = new KrisspyAnalytics(this.http, this.backendId);
1880
+ this._notifications = new KrisspyNotifications(this.http, this.backendId);
1772
1881
  this._auth.onAuthStateChange((event) => {
1773
1882
  if (event === "SIGNED_IN") {
1774
1883
  const session = this._auth.session();
@@ -1780,6 +1889,19 @@ var KrisspyClient = class {
1780
1889
  }
1781
1890
  });
1782
1891
  }
1892
+ /**
1893
+ * Wait until the client is fully initialized (session restored from storage).
1894
+ * **Required on React Native / Expo** when using AsyncStorage.
1895
+ * On web (localStorage), this resolves instantly.
1896
+ *
1897
+ * @example
1898
+ * const krisspy = createClient({ backendId: '...', apiKey: '...' });
1899
+ * await krisspy.initialize(); // wait for cached session to load
1900
+ * const user = krisspy.auth.user(); // now safe to use
1901
+ */
1902
+ async initialize() {
1903
+ await this._auth.initialize();
1904
+ }
1783
1905
  /**
1784
1906
  * Auth module for user authentication
1785
1907
  *
@@ -1848,6 +1970,29 @@ var KrisspyClient = class {
1848
1970
  get analytics() {
1849
1971
  return this._analytics;
1850
1972
  }
1973
+ /**
1974
+ * Notifications module for Expo push notifications
1975
+ *
1976
+ * @example
1977
+ * // Register push token (client-side, after getting token from expo-notifications)
1978
+ * await krisspy.notifications.register(expoPushToken, 'ios')
1979
+ *
1980
+ * // Unregister on sign out
1981
+ * await krisspy.notifications.unregister(expoPushToken)
1982
+ *
1983
+ * // Send to user (server-side, requires serviceKey)
1984
+ * await krisspy.notifications.send({
1985
+ * userId: 'user123',
1986
+ * title: 'Hello',
1987
+ * body: 'You have a new message',
1988
+ * })
1989
+ *
1990
+ * // Broadcast to all (server-side, requires serviceKey)
1991
+ * await krisspy.notifications.broadcast({ title: 'Update', body: 'v2.0 released!' })
1992
+ */
1993
+ get notifications() {
1994
+ return this._notifications;
1995
+ }
1851
1996
  /**
1852
1997
  * Create a realtime channel for subscribing to database changes
1853
1998
  *
package/dist/index.mjs CHANGED
@@ -239,8 +239,19 @@ var KrisspyAuth = class {
239
239
  this.http = http;
240
240
  this.backendId = backendId;
241
241
  this.storage = resolveStorage(storage);
242
+ this._initialized = new Promise((resolve) => {
243
+ this._resolveInit = resolve;
244
+ });
242
245
  this.restoreSession();
243
246
  }
247
+ /**
248
+ * Wait until session has been restored from storage.
249
+ * On web (localStorage), this resolves immediately.
250
+ * On React Native (AsyncStorage), this waits for the async read to complete.
251
+ */
252
+ async initialize() {
253
+ return this._initialized;
254
+ }
244
255
  /**
245
256
  * Get current session from storage
246
257
  */
@@ -251,12 +262,18 @@ var KrisspyAuth = class {
251
262
  result.then((stored) => {
252
263
  if (stored) this.hydrateSession(stored);
253
264
  }).catch(() => {
265
+ }).finally(() => {
266
+ this._resolveInit();
254
267
  });
255
- } else if (result) {
256
- this.hydrateSession(result);
268
+ } else {
269
+ if (result) {
270
+ this.hydrateSession(result);
271
+ }
272
+ this._resolveInit();
257
273
  }
258
274
  } catch (err) {
259
275
  console.warn("[Krisspy Auth] Failed to restore session:", err);
276
+ this._resolveInit();
260
277
  }
261
278
  }
262
279
  hydrateSession(stored) {
@@ -1420,6 +1437,96 @@ var KrisspyAnalytics = class {
1420
1437
  }
1421
1438
  };
1422
1439
 
1440
+ // src/notifications.ts
1441
+ var KrisspyNotifications = class {
1442
+ constructor(http, backendId) {
1443
+ this.http = http;
1444
+ this.backendId = backendId;
1445
+ }
1446
+ // ── Client-side methods (app user JWT) ──
1447
+ /**
1448
+ * Register a push token for the current user.
1449
+ * Call this after getting the ExpoPushToken from expo-notifications.
1450
+ *
1451
+ * @example
1452
+ * import * as Notifications from 'expo-notifications'
1453
+ * const { data: token } = await Notifications.getExpoPushTokenAsync()
1454
+ * await krisspy.notifications.register(token, 'ios')
1455
+ */
1456
+ async register(token, platform) {
1457
+ var _a, _b;
1458
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/token`;
1459
+ const response = await this.http.post(path, { token, platform });
1460
+ if (response.error) {
1461
+ return { data: null, error: response.error };
1462
+ }
1463
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : null, error: null };
1464
+ }
1465
+ /**
1466
+ * Unregister a push token (e.g., on sign out or when user disables notifications).
1467
+ */
1468
+ async unregister(token) {
1469
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/unregister`;
1470
+ const response = await this.http.post(path, { token });
1471
+ return { error: response.error };
1472
+ }
1473
+ /**
1474
+ * List all push tokens registered for the current user.
1475
+ */
1476
+ async getTokens() {
1477
+ var _a, _b;
1478
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/tokens`;
1479
+ const response = await this.http.get(path);
1480
+ if (response.error) {
1481
+ return { data: null, error: response.error };
1482
+ }
1483
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : [], error: null };
1484
+ }
1485
+ // ── Server-side methods (service key required) ──
1486
+ /**
1487
+ * Send a push notification to a specific user.
1488
+ * Requires service key (server-side only).
1489
+ *
1490
+ * @example
1491
+ * // In an Azure Function / serverless function:
1492
+ * const krisspy = createClient({ backendId: '...', apiKey: '...', serviceKey: process.env.SERVICE_KEY })
1493
+ * await krisspy.notifications.send({
1494
+ * userId: 'user123',
1495
+ * title: 'New message',
1496
+ * body: 'You have a new message from Alice',
1497
+ * data: { screen: 'chat', chatId: '456' },
1498
+ * })
1499
+ */
1500
+ async send(params) {
1501
+ var _a, _b;
1502
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/send`;
1503
+ const response = await this.http.post(path, params);
1504
+ if (response.error) {
1505
+ return { data: null, error: response.error };
1506
+ }
1507
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : { tickets: [] }, error: null };
1508
+ }
1509
+ /**
1510
+ * Broadcast a push notification to all registered users.
1511
+ * Requires service key (server-side only).
1512
+ *
1513
+ * @example
1514
+ * await krisspy.notifications.broadcast({
1515
+ * title: 'App Update',
1516
+ * body: 'Version 2.0 is now available!',
1517
+ * })
1518
+ */
1519
+ async broadcast(params) {
1520
+ var _a, _b;
1521
+ const path = `/api/v1/cloud-backends/${this.backendId}/notifications/send-broadcast`;
1522
+ const response = await this.http.post(path, params);
1523
+ if (response.error) {
1524
+ return { data: null, error: response.error };
1525
+ }
1526
+ return { data: (_b = (_a = response.data) == null ? void 0 : _a.data) != null ? _b : { tickets: [], count: 0 }, error: null };
1527
+ }
1528
+ };
1529
+
1423
1530
  // src/query-builder.ts
1424
1531
  var QueryBuilder = class {
1425
1532
  constructor(http, backendId, tableName, dataMode = "rls") {
@@ -1735,6 +1842,7 @@ var KrisspyClient = class {
1735
1842
  this._storage = new KrisspyStorage(this.http, this.backendId);
1736
1843
  this._realtime = new KrisspyRealtime(this.baseUrl, this.backendId, this.debug);
1737
1844
  this._analytics = new KrisspyAnalytics(this.http, this.backendId);
1845
+ this._notifications = new KrisspyNotifications(this.http, this.backendId);
1738
1846
  this._auth.onAuthStateChange((event) => {
1739
1847
  if (event === "SIGNED_IN") {
1740
1848
  const session = this._auth.session();
@@ -1746,6 +1854,19 @@ var KrisspyClient = class {
1746
1854
  }
1747
1855
  });
1748
1856
  }
1857
+ /**
1858
+ * Wait until the client is fully initialized (session restored from storage).
1859
+ * **Required on React Native / Expo** when using AsyncStorage.
1860
+ * On web (localStorage), this resolves instantly.
1861
+ *
1862
+ * @example
1863
+ * const krisspy = createClient({ backendId: '...', apiKey: '...' });
1864
+ * await krisspy.initialize(); // wait for cached session to load
1865
+ * const user = krisspy.auth.user(); // now safe to use
1866
+ */
1867
+ async initialize() {
1868
+ await this._auth.initialize();
1869
+ }
1749
1870
  /**
1750
1871
  * Auth module for user authentication
1751
1872
  *
@@ -1814,6 +1935,29 @@ var KrisspyClient = class {
1814
1935
  get analytics() {
1815
1936
  return this._analytics;
1816
1937
  }
1938
+ /**
1939
+ * Notifications module for Expo push notifications
1940
+ *
1941
+ * @example
1942
+ * // Register push token (client-side, after getting token from expo-notifications)
1943
+ * await krisspy.notifications.register(expoPushToken, 'ios')
1944
+ *
1945
+ * // Unregister on sign out
1946
+ * await krisspy.notifications.unregister(expoPushToken)
1947
+ *
1948
+ * // Send to user (server-side, requires serviceKey)
1949
+ * await krisspy.notifications.send({
1950
+ * userId: 'user123',
1951
+ * title: 'Hello',
1952
+ * body: 'You have a new message',
1953
+ * })
1954
+ *
1955
+ * // Broadcast to all (server-side, requires serviceKey)
1956
+ * await krisspy.notifications.broadcast({ title: 'Update', body: 'v2.0 released!' })
1957
+ */
1958
+ get notifications() {
1959
+ return this._notifications;
1960
+ }
1817
1961
  /**
1818
1962
  * Create a realtime channel for subscribing to database changes
1819
1963
  *
@@ -2014,6 +2158,7 @@ export {
2014
2158
  KrisspyAnalytics,
2015
2159
  KrisspyAuth,
2016
2160
  KrisspyClient,
2161
+ KrisspyNotifications,
2017
2162
  KrisspyRealtime,
2018
2163
  KrisspyStorage,
2019
2164
  QueryBuilder,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "krisspy-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
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",