@insforge/sdk 1.0.1-refresh.9 → 1.0.1-schema.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/dist/index.js CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  HttpClient: () => HttpClient,
28
28
  InsForgeClient: () => InsForgeClient,
29
29
  InsForgeError: () => InsForgeError,
30
+ Realtime: () => Realtime,
30
31
  Storage: () => Storage,
31
32
  StorageBucket: () => StorageBucket,
32
33
  TokenManager: () => TokenManager,
@@ -111,7 +112,6 @@ var HttpClient = class {
111
112
  method,
112
113
  headers: requestHeaders,
113
114
  body: processedBody,
114
- credentials: "include",
115
115
  ...fetchOptions
116
116
  });
117
117
  if (response.status === 204) {
@@ -176,45 +176,8 @@ var HttpClient = class {
176
176
  // src/lib/token-manager.ts
177
177
  var TOKEN_KEY = "insforge-auth-token";
178
178
  var USER_KEY = "insforge-auth-user";
179
- var AUTH_FLAG_COOKIE = "isAuthenticated";
180
- var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
181
- function hasAuthCookie() {
182
- if (typeof document === "undefined") return false;
183
- return document.cookie.split(";").some(
184
- (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
185
- );
186
- }
187
- function setAuthCookie() {
188
- if (typeof document === "undefined") return;
189
- const maxAge = 7 * 24 * 60 * 60;
190
- document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
191
- }
192
- function clearAuthCookie() {
193
- if (typeof document === "undefined") return;
194
- document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
195
- }
196
- function getCsrfToken() {
197
- if (typeof document === "undefined") return null;
198
- const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
199
- if (!match) return null;
200
- return match.split("=")[1] || null;
201
- }
202
- function setCsrfToken(token) {
203
- if (typeof document === "undefined") return;
204
- const maxAge = 7 * 24 * 60 * 60;
205
- document.cookie = `${CSRF_TOKEN_COOKIE}=${token}; path=/; max-age=${maxAge}; SameSite=Lax`;
206
- }
207
- function clearCsrfToken() {
208
- if (typeof document === "undefined") return;
209
- document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
210
- }
211
179
  var TokenManager = class {
212
180
  constructor(storage) {
213
- // In-memory storage
214
- this.accessToken = null;
215
- this.user = null;
216
- // Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
217
- this._mode = "storage";
218
181
  if (storage) {
219
182
  this.storage = storage;
220
183
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -232,206 +195,35 @@ var TokenManager = class {
232
195
  };
233
196
  }
234
197
  }
235
- /**
236
- * Get current mode
237
- */
238
- get mode() {
239
- return this._mode;
240
- }
241
- /**
242
- * Set mode to memory (new backend with cookies + memory)
243
- */
244
- setMemoryMode() {
245
- if (this._mode === "storage") {
246
- this.storage.removeItem(TOKEN_KEY);
247
- this.storage.removeItem(USER_KEY);
248
- }
249
- this._mode = "memory";
250
- }
251
- /**
252
- * Set mode to storage (legacy backend with localStorage)
253
- * Also loads existing session from localStorage
254
- */
255
- setStorageMode() {
256
- this._mode = "storage";
257
- this.loadFromStorage();
198
+ saveSession(session) {
199
+ this.storage.setItem(TOKEN_KEY, session.accessToken);
200
+ this.storage.setItem(USER_KEY, JSON.stringify(session.user));
258
201
  }
259
- /**
260
- * Load session from localStorage
261
- */
262
- loadFromStorage() {
202
+ getSession() {
263
203
  const token = this.storage.getItem(TOKEN_KEY);
264
204
  const userStr = this.storage.getItem(USER_KEY);
265
- if (token && userStr) {
266
- try {
267
- this.accessToken = token;
268
- this.user = JSON.parse(userStr);
269
- } catch {
270
- this.clearSession();
271
- }
205
+ if (!token || !userStr) {
206
+ return null;
272
207
  }
273
- }
274
- /**
275
- * Save session (memory always, localStorage only in storage mode)
276
- */
277
- saveSession(session) {
278
- this.accessToken = session.accessToken;
279
- this.user = session.user;
280
- if (this._mode === "storage") {
281
- this.storage.setItem(TOKEN_KEY, session.accessToken);
282
- this.storage.setItem(USER_KEY, JSON.stringify(session.user));
208
+ try {
209
+ const user = JSON.parse(userStr);
210
+ return { accessToken: token, user };
211
+ } catch {
212
+ this.clearSession();
213
+ return null;
283
214
  }
284
215
  }
285
- /**
286
- * Get current session
287
- */
288
- getSession() {
289
- if (!this.accessToken || !this.user) return null;
290
- return {
291
- accessToken: this.accessToken,
292
- user: this.user
293
- };
294
- }
295
- /**
296
- * Get access token
297
- */
298
216
  getAccessToken() {
299
- return this.accessToken;
300
- }
301
- /**
302
- * Set access token
303
- */
304
- setAccessToken(token) {
305
- this.accessToken = token;
306
- if (this._mode === "storage") {
307
- this.storage.setItem(TOKEN_KEY, token);
308
- }
309
- }
310
- /**
311
- * Get user
312
- */
313
- getUser() {
314
- return this.user;
315
- }
316
- /**
317
- * Set user
318
- */
319
- setUser(user) {
320
- this.user = user;
321
- if (this._mode === "storage") {
322
- this.storage.setItem(USER_KEY, JSON.stringify(user));
323
- }
217
+ const token = this.storage.getItem(TOKEN_KEY);
218
+ return typeof token === "string" ? token : null;
324
219
  }
325
- /**
326
- * Clear session (both memory and localStorage)
327
- */
328
220
  clearSession() {
329
- this.accessToken = null;
330
- this.user = null;
331
221
  this.storage.removeItem(TOKEN_KEY);
332
222
  this.storage.removeItem(USER_KEY);
333
223
  }
334
- /**
335
- * Check if there's a session in localStorage (for legacy detection)
336
- */
337
- hasStoredSession() {
338
- const token = this.storage.getItem(TOKEN_KEY);
339
- return !!token;
340
- }
341
- };
342
-
343
- // src/modules/database-postgrest.ts
344
- var import_postgrest_js = require("@supabase/postgrest-js");
345
- function createInsForgePostgrestFetch(httpClient, tokenManager) {
346
- return async (input, init) => {
347
- const url = typeof input === "string" ? input : input.toString();
348
- const urlObj = new URL(url);
349
- const tableName = urlObj.pathname.slice(1);
350
- const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
351
- const token = tokenManager.getAccessToken();
352
- const httpHeaders = httpClient.getHeaders();
353
- const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
354
- const headers = new Headers(init?.headers);
355
- if (authToken && !headers.has("Authorization")) {
356
- headers.set("Authorization", `Bearer ${authToken}`);
357
- }
358
- const response = await fetch(insforgeUrl, {
359
- ...init,
360
- headers
361
- });
362
- return response;
363
- };
364
- }
365
- var Database = class {
366
- constructor(httpClient, tokenManager) {
367
- this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
368
- fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
369
- headers: {}
370
- });
371
- }
372
- /**
373
- * Create a query builder for a table
374
- *
375
- * @example
376
- * // Basic query
377
- * const { data, error } = await client.database
378
- * .from('posts')
379
- * .select('*')
380
- * .eq('user_id', userId);
381
- *
382
- * // With count (Supabase style!)
383
- * const { data, error, count } = await client.database
384
- * .from('posts')
385
- * .select('*', { count: 'exact' })
386
- * .range(0, 9);
387
- *
388
- * // Just get count, no data
389
- * const { count } = await client.database
390
- * .from('posts')
391
- * .select('*', { count: 'exact', head: true });
392
- *
393
- * // Complex queries with OR
394
- * const { data } = await client.database
395
- * .from('posts')
396
- * .select('*, users!inner(*)')
397
- * .or('status.eq.active,status.eq.pending');
398
- *
399
- * // All features work:
400
- * - Nested selects
401
- * - Foreign key expansion
402
- * - OR/AND/NOT conditions
403
- * - Count with head
404
- * - Range pagination
405
- * - Upserts
406
- */
407
- from(table) {
408
- return this.postgrest.from(table);
409
- }
410
224
  };
411
225
 
412
226
  // src/modules/auth.ts
413
- function convertDbProfileToCamelCase(dbProfile) {
414
- const result = {
415
- id: dbProfile.id
416
- };
417
- Object.keys(dbProfile).forEach((key) => {
418
- result[key] = dbProfile[key];
419
- if (key.includes("_")) {
420
- const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
421
- result[camelKey] = dbProfile[key];
422
- }
423
- });
424
- return result;
425
- }
426
- function convertCamelCaseToDbProfile(profile) {
427
- const dbProfile = {};
428
- Object.keys(profile).forEach((key) => {
429
- if (profile[key] === void 0) return;
430
- const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
431
- dbProfile[snakeKey] = profile[key];
432
- });
433
- return dbProfile;
434
- }
435
227
  function isHostedAuthEnvironment() {
436
228
  if (typeof window === "undefined") {
437
229
  return false;
@@ -449,82 +241,8 @@ var Auth = class {
449
241
  constructor(http, tokenManager) {
450
242
  this.http = http;
451
243
  this.tokenManager = tokenManager;
452
- this.database = new Database(http, tokenManager);
453
244
  this.detectAuthCallback();
454
245
  }
455
- /**
456
- * Restore session on app initialization
457
- *
458
- * @returns Object with isLoggedIn status
459
- *
460
- * @example
461
- * ```typescript
462
- * const client = new InsForgeClient({ baseUrl: '...' });
463
- * const { isLoggedIn } = await client.auth.restoreSession();
464
- *
465
- * if (isLoggedIn) {
466
- * const { data } = await client.auth.getCurrentUser();
467
- * }
468
- * ```
469
- */
470
- async restoreSession() {
471
- if (typeof window === "undefined") {
472
- return { isLoggedIn: false };
473
- }
474
- if (this.tokenManager.getAccessToken()) {
475
- return { isLoggedIn: true };
476
- }
477
- if (hasAuthCookie()) {
478
- try {
479
- const csrfToken = getCsrfToken();
480
- const response = await this.http.post(
481
- "/api/auth/refresh",
482
- {
483
- headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {}
484
- }
485
- );
486
- if (response.accessToken) {
487
- this.tokenManager.setMemoryMode();
488
- this.tokenManager.setAccessToken(response.accessToken);
489
- this.http.setAuthToken(response.accessToken);
490
- if (response.user) {
491
- this.tokenManager.setUser(response.user);
492
- }
493
- if (response.csrfToken) {
494
- setCsrfToken(response.csrfToken);
495
- }
496
- return { isLoggedIn: true };
497
- }
498
- } catch (error) {
499
- if (error instanceof InsForgeError) {
500
- if (error.statusCode === 404) {
501
- this.tokenManager.setStorageMode();
502
- const token = this.tokenManager.getAccessToken();
503
- if (token) {
504
- this.http.setAuthToken(token);
505
- return { isLoggedIn: true };
506
- }
507
- return { isLoggedIn: false };
508
- }
509
- if (error.statusCode === 401 || error.statusCode === 403) {
510
- clearAuthCookie();
511
- clearCsrfToken();
512
- return { isLoggedIn: false };
513
- }
514
- }
515
- return { isLoggedIn: false };
516
- }
517
- }
518
- if (this.tokenManager.hasStoredSession()) {
519
- this.tokenManager.setStorageMode();
520
- const token = this.tokenManager.getAccessToken();
521
- if (token) {
522
- this.http.setAuthToken(token);
523
- return { isLoggedIn: true };
524
- }
525
- }
526
- return { isLoggedIn: false };
527
- }
528
246
  /**
529
247
  * Automatically detect and handle OAuth callback parameters in the URL
530
248
  * This runs on initialization to seamlessly complete the OAuth flow
@@ -538,7 +256,6 @@ var Auth = class {
538
256
  const userId = params.get("user_id");
539
257
  const email = params.get("email");
540
258
  const name = params.get("name");
541
- const csrfToken = params.get("csrf_token");
542
259
  if (accessToken && userId && email) {
543
260
  const session = {
544
261
  accessToken,
@@ -553,18 +270,13 @@ var Auth = class {
553
270
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
554
271
  }
555
272
  };
556
- this.http.setAuthToken(accessToken);
557
273
  this.tokenManager.saveSession(session);
558
- setAuthCookie();
559
- if (csrfToken) {
560
- setCsrfToken(csrfToken);
561
- }
274
+ this.http.setAuthToken(accessToken);
562
275
  const url = new URL(window.location.href);
563
276
  url.searchParams.delete("access_token");
564
277
  url.searchParams.delete("user_id");
565
278
  url.searchParams.delete("email");
566
279
  url.searchParams.delete("name");
567
- url.searchParams.delete("csrf_token");
568
280
  if (params.has("error")) {
569
281
  url.searchParams.delete("error");
570
282
  }
@@ -580,17 +292,15 @@ var Auth = class {
580
292
  async signUp(request) {
581
293
  try {
582
294
  const response = await this.http.post("/api/auth/users", request);
583
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
295
+ if (response.accessToken && response.user) {
584
296
  const session = {
585
297
  accessToken: response.accessToken,
586
298
  user: response.user
587
299
  };
588
- this.tokenManager.saveSession(session);
589
- setAuthCookie();
590
- this.http.setAuthToken(response.accessToken);
591
- if (response.csrfToken) {
592
- setCsrfToken(response.csrfToken);
300
+ if (!isHostedAuthEnvironment()) {
301
+ this.tokenManager.saveSession(session);
593
302
  }
303
+ this.http.setAuthToken(response.accessToken);
594
304
  }
595
305
  return {
596
306
  data: response,
@@ -616,18 +326,21 @@ var Auth = class {
616
326
  async signInWithPassword(request) {
617
327
  try {
618
328
  const response = await this.http.post("/api/auth/sessions", request);
619
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
620
- const session = {
621
- accessToken: response.accessToken,
622
- user: response.user
623
- };
624
- this.tokenManager.saveSession(session);
625
- setAuthCookie();
626
- this.http.setAuthToken(response.accessToken);
627
- if (response.csrfToken) {
628
- setCsrfToken(response.csrfToken);
329
+ const session = {
330
+ accessToken: response.accessToken || "",
331
+ user: response.user || {
332
+ id: "",
333
+ email: "",
334
+ name: "",
335
+ emailVerified: false,
336
+ createdAt: "",
337
+ updatedAt: ""
629
338
  }
339
+ };
340
+ if (!isHostedAuthEnvironment()) {
341
+ this.tokenManager.saveSession(session);
630
342
  }
343
+ this.http.setAuthToken(response.accessToken || "");
631
344
  return {
632
345
  data: response,
633
346
  error: null
@@ -685,14 +398,8 @@ var Auth = class {
685
398
  */
686
399
  async signOut() {
687
400
  try {
688
- try {
689
- await this.http.post("/api/auth/logout");
690
- } catch {
691
- }
692
401
  this.tokenManager.clearSession();
693
402
  this.http.setAuthToken(null);
694
- clearAuthCookie();
695
- clearCsrfToken();
696
403
  return { error: null };
697
404
  } catch (error) {
698
405
  return {
@@ -753,14 +460,9 @@ var Auth = class {
753
460
  }
754
461
  this.http.setAuthToken(session.accessToken);
755
462
  const authResponse = await this.http.get("/api/auth/sessions/current");
756
- const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
757
- if (profileError && profileError.code !== "PGRST116") {
758
- return { data: null, error: profileError };
759
- }
760
463
  return {
761
464
  data: {
762
- user: authResponse.user,
763
- profile: profile ? convertDbProfileToCamelCase(profile) : null
465
+ user: authResponse.user
764
466
  },
765
467
  error: null
766
468
  };
@@ -784,17 +486,28 @@ var Auth = class {
784
486
  }
785
487
  /**
786
488
  * Get any user's profile by ID
787
- * Returns profile information from the users table (dynamic fields)
489
+ * Returns profile information from the users table
788
490
  */
789
491
  async getProfile(userId) {
790
- const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
791
- if (error && error.code === "PGRST116") {
792
- return { data: null, error: null };
793
- }
794
- if (data) {
795
- return { data: convertDbProfileToCamelCase(data), error: null };
492
+ try {
493
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
494
+ return {
495
+ data: response,
496
+ error: null
497
+ };
498
+ } catch (error) {
499
+ if (error instanceof InsForgeError) {
500
+ return { data: null, error };
501
+ }
502
+ return {
503
+ data: null,
504
+ error: new InsForgeError(
505
+ "An unexpected error occurred while fetching user profile",
506
+ 500,
507
+ "UNEXPECTED_ERROR"
508
+ )
509
+ };
796
510
  }
797
- return { data: null, error };
798
511
  }
799
512
  /**
800
513
  * Get the current session (only session data, no API call)
@@ -825,42 +538,31 @@ var Auth = class {
825
538
  /**
826
539
  * Set/Update the current user's profile
827
540
  * Updates profile information in the users table (supports any dynamic fields)
541
+ * Requires authentication
828
542
  */
829
543
  async setProfile(profile) {
830
- const session = this.tokenManager.getSession();
831
- if (!session?.accessToken) {
544
+ try {
545
+ const response = await this.http.patch(
546
+ "/api/auth/profiles/current",
547
+ { profile }
548
+ );
549
+ return {
550
+ data: response,
551
+ error: null
552
+ };
553
+ } catch (error) {
554
+ if (error instanceof InsForgeError) {
555
+ return { data: null, error };
556
+ }
832
557
  return {
833
558
  data: null,
834
559
  error: new InsForgeError(
835
- "No authenticated user found",
836
- 401,
837
- "UNAUTHENTICATED"
560
+ "An unexpected error occurred while updating user profile",
561
+ 500,
562
+ "UNEXPECTED_ERROR"
838
563
  )
839
564
  };
840
565
  }
841
- if (!session.user?.id) {
842
- const { data: data2, error: error2 } = await this.getCurrentUser();
843
- if (error2) {
844
- return { data: null, error: error2 };
845
- }
846
- if (data2?.user) {
847
- session.user = {
848
- id: data2.user.id,
849
- email: data2.user.email,
850
- name: "",
851
- emailVerified: false,
852
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
853
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
854
- };
855
- this.tokenManager.saveSession(session);
856
- }
857
- }
858
- const dbProfile = convertCamelCaseToDbProfile(profile);
859
- const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
860
- if (data) {
861
- return { data: convertDbProfileToCamelCase(data), error: null };
862
- }
863
- return { data: null, error };
864
566
  }
865
567
  /**
866
568
  * Send email verification (code or link based on config)
@@ -1014,17 +716,13 @@ var Auth = class {
1014
716
  "/api/auth/email/verify",
1015
717
  request
1016
718
  );
1017
- if (response.accessToken && !isHostedAuthEnvironment()) {
719
+ if (response.accessToken) {
1018
720
  const session = {
1019
721
  accessToken: response.accessToken,
1020
722
  user: response.user || {}
1021
723
  };
1022
724
  this.tokenManager.saveSession(session);
1023
725
  this.http.setAuthToken(response.accessToken);
1024
- setAuthCookie();
1025
- if (response.csrfToken) {
1026
- setCsrfToken(response.csrfToken);
1027
- }
1028
726
  }
1029
727
  return {
1030
728
  data: response,
@@ -1046,6 +744,75 @@ var Auth = class {
1046
744
  }
1047
745
  };
1048
746
 
747
+ // src/modules/database-postgrest.ts
748
+ var import_postgrest_js = require("@supabase/postgrest-js");
749
+ function createInsForgePostgrestFetch(httpClient, tokenManager) {
750
+ return async (input, init) => {
751
+ const url = typeof input === "string" ? input : input.toString();
752
+ const urlObj = new URL(url);
753
+ const tableName = urlObj.pathname.slice(1);
754
+ const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
755
+ const token = tokenManager.getAccessToken();
756
+ const httpHeaders = httpClient.getHeaders();
757
+ const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
758
+ const headers = new Headers(init?.headers);
759
+ if (authToken && !headers.has("Authorization")) {
760
+ headers.set("Authorization", `Bearer ${authToken}`);
761
+ }
762
+ const response = await fetch(insforgeUrl, {
763
+ ...init,
764
+ headers
765
+ });
766
+ return response;
767
+ };
768
+ }
769
+ var Database = class {
770
+ constructor(httpClient, tokenManager) {
771
+ this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
772
+ fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
773
+ headers: {}
774
+ });
775
+ }
776
+ /**
777
+ * Create a query builder for a table
778
+ *
779
+ * @example
780
+ * // Basic query
781
+ * const { data, error } = await client.database
782
+ * .from('posts')
783
+ * .select('*')
784
+ * .eq('user_id', userId);
785
+ *
786
+ * // With count (Supabase style!)
787
+ * const { data, error, count } = await client.database
788
+ * .from('posts')
789
+ * .select('*', { count: 'exact' })
790
+ * .range(0, 9);
791
+ *
792
+ * // Just get count, no data
793
+ * const { count } = await client.database
794
+ * .from('posts')
795
+ * .select('*', { count: 'exact', head: true });
796
+ *
797
+ * // Complex queries with OR
798
+ * const { data } = await client.database
799
+ * .from('posts')
800
+ * .select('*, users!inner(*)')
801
+ * .or('status.eq.active,status.eq.pending');
802
+ *
803
+ * // All features work:
804
+ * - Nested selects
805
+ * - Foreign key expansion
806
+ * - OR/AND/NOT conditions
807
+ * - Count with head
808
+ * - Range pagination
809
+ * - Upserts
810
+ */
811
+ from(table) {
812
+ return this.postgrest.from(table);
813
+ }
814
+ };
815
+
1049
816
  // src/modules/storage.ts
1050
817
  var StorageBucket = class {
1051
818
  constructor(bucketName, http) {
@@ -1567,6 +1334,239 @@ var Functions = class {
1567
1334
  }
1568
1335
  };
1569
1336
 
1337
+ // src/modules/realtime.ts
1338
+ var import_socket = require("socket.io-client");
1339
+ var CONNECT_TIMEOUT = 1e4;
1340
+ var Realtime = class {
1341
+ constructor(baseUrl, tokenManager) {
1342
+ this.socket = null;
1343
+ this.connectPromise = null;
1344
+ this.subscribedChannels = /* @__PURE__ */ new Set();
1345
+ this.eventListeners = /* @__PURE__ */ new Map();
1346
+ this.baseUrl = baseUrl;
1347
+ this.tokenManager = tokenManager;
1348
+ }
1349
+ notifyListeners(event, payload) {
1350
+ const listeners = this.eventListeners.get(event);
1351
+ if (!listeners) return;
1352
+ for (const cb of listeners) {
1353
+ try {
1354
+ cb(payload);
1355
+ } catch (err) {
1356
+ console.error(`Error in ${event} callback:`, err);
1357
+ }
1358
+ }
1359
+ }
1360
+ /**
1361
+ * Connect to the realtime server
1362
+ * @returns Promise that resolves when connected
1363
+ */
1364
+ connect() {
1365
+ if (this.socket?.connected) {
1366
+ return Promise.resolve();
1367
+ }
1368
+ if (this.connectPromise) {
1369
+ return this.connectPromise;
1370
+ }
1371
+ this.connectPromise = new Promise((resolve, reject) => {
1372
+ const session = this.tokenManager.getSession();
1373
+ const token = session?.accessToken;
1374
+ this.socket = (0, import_socket.io)(this.baseUrl, {
1375
+ transports: ["websocket"],
1376
+ auth: token ? { token } : void 0
1377
+ });
1378
+ let initialConnection = true;
1379
+ let timeoutId = null;
1380
+ const cleanup = () => {
1381
+ if (timeoutId) {
1382
+ clearTimeout(timeoutId);
1383
+ timeoutId = null;
1384
+ }
1385
+ };
1386
+ timeoutId = setTimeout(() => {
1387
+ if (initialConnection) {
1388
+ initialConnection = false;
1389
+ this.connectPromise = null;
1390
+ this.socket?.disconnect();
1391
+ this.socket = null;
1392
+ reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
1393
+ }
1394
+ }, CONNECT_TIMEOUT);
1395
+ this.socket.on("connect", () => {
1396
+ cleanup();
1397
+ for (const channel of this.subscribedChannels) {
1398
+ this.socket.emit("realtime:subscribe", { channel });
1399
+ }
1400
+ this.notifyListeners("connect");
1401
+ if (initialConnection) {
1402
+ initialConnection = false;
1403
+ this.connectPromise = null;
1404
+ resolve();
1405
+ }
1406
+ });
1407
+ this.socket.on("connect_error", (error) => {
1408
+ cleanup();
1409
+ this.notifyListeners("connect_error", error);
1410
+ if (initialConnection) {
1411
+ initialConnection = false;
1412
+ this.connectPromise = null;
1413
+ reject(error);
1414
+ }
1415
+ });
1416
+ this.socket.on("disconnect", (reason) => {
1417
+ this.notifyListeners("disconnect", reason);
1418
+ });
1419
+ this.socket.on("realtime:error", (error) => {
1420
+ this.notifyListeners("error", error);
1421
+ });
1422
+ this.socket.onAny((event, message) => {
1423
+ if (event === "realtime:error") return;
1424
+ this.notifyListeners(event, message);
1425
+ });
1426
+ });
1427
+ return this.connectPromise;
1428
+ }
1429
+ /**
1430
+ * Disconnect from the realtime server
1431
+ */
1432
+ disconnect() {
1433
+ if (this.socket) {
1434
+ this.socket.disconnect();
1435
+ this.socket = null;
1436
+ }
1437
+ this.subscribedChannels.clear();
1438
+ }
1439
+ /**
1440
+ * Check if connected to the realtime server
1441
+ */
1442
+ get isConnected() {
1443
+ return this.socket?.connected ?? false;
1444
+ }
1445
+ /**
1446
+ * Get the current connection state
1447
+ */
1448
+ get connectionState() {
1449
+ if (!this.socket) return "disconnected";
1450
+ if (this.socket.connected) return "connected";
1451
+ return "connecting";
1452
+ }
1453
+ /**
1454
+ * Get the socket ID (if connected)
1455
+ */
1456
+ get socketId() {
1457
+ return this.socket?.id;
1458
+ }
1459
+ /**
1460
+ * Subscribe to a channel
1461
+ *
1462
+ * Automatically connects if not already connected.
1463
+ *
1464
+ * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
1465
+ * @returns Promise with the subscription response
1466
+ */
1467
+ async subscribe(channel) {
1468
+ if (this.subscribedChannels.has(channel)) {
1469
+ return { ok: true, channel };
1470
+ }
1471
+ if (!this.socket?.connected) {
1472
+ try {
1473
+ await this.connect();
1474
+ } catch (error) {
1475
+ const message = error instanceof Error ? error.message : "Connection failed";
1476
+ return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
1477
+ }
1478
+ }
1479
+ return new Promise((resolve) => {
1480
+ this.socket.emit("realtime:subscribe", { channel }, (response) => {
1481
+ if (response.ok) {
1482
+ this.subscribedChannels.add(channel);
1483
+ }
1484
+ resolve(response);
1485
+ });
1486
+ });
1487
+ }
1488
+ /**
1489
+ * Unsubscribe from a channel (fire-and-forget)
1490
+ *
1491
+ * @param channel - Channel name to unsubscribe from
1492
+ */
1493
+ unsubscribe(channel) {
1494
+ this.subscribedChannels.delete(channel);
1495
+ if (this.socket?.connected) {
1496
+ this.socket.emit("realtime:unsubscribe", { channel });
1497
+ }
1498
+ }
1499
+ /**
1500
+ * Publish a message to a channel
1501
+ *
1502
+ * @param channel - Channel name
1503
+ * @param event - Event name
1504
+ * @param payload - Message payload
1505
+ */
1506
+ async publish(channel, event, payload) {
1507
+ if (!this.socket?.connected) {
1508
+ throw new Error("Not connected to realtime server. Call connect() first.");
1509
+ }
1510
+ this.socket.emit("realtime:publish", { channel, event, payload });
1511
+ }
1512
+ /**
1513
+ * Listen for events
1514
+ *
1515
+ * Reserved event names:
1516
+ * - 'connect' - Fired when connected to the server
1517
+ * - 'connect_error' - Fired when connection fails (payload: Error)
1518
+ * - 'disconnect' - Fired when disconnected (payload: reason string)
1519
+ * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
1520
+ *
1521
+ * All other events receive a `SocketMessage` payload with metadata.
1522
+ *
1523
+ * @param event - Event name to listen for
1524
+ * @param callback - Callback function when event is received
1525
+ */
1526
+ on(event, callback) {
1527
+ if (!this.eventListeners.has(event)) {
1528
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
1529
+ }
1530
+ this.eventListeners.get(event).add(callback);
1531
+ }
1532
+ /**
1533
+ * Remove a listener for a specific event
1534
+ *
1535
+ * @param event - Event name
1536
+ * @param callback - The callback function to remove
1537
+ */
1538
+ off(event, callback) {
1539
+ const listeners = this.eventListeners.get(event);
1540
+ if (listeners) {
1541
+ listeners.delete(callback);
1542
+ if (listeners.size === 0) {
1543
+ this.eventListeners.delete(event);
1544
+ }
1545
+ }
1546
+ }
1547
+ /**
1548
+ * Listen for an event only once, then automatically remove the listener
1549
+ *
1550
+ * @param event - Event name to listen for
1551
+ * @param callback - Callback function when event is received
1552
+ */
1553
+ once(event, callback) {
1554
+ const wrapper = (payload) => {
1555
+ this.off(event, wrapper);
1556
+ callback(payload);
1557
+ };
1558
+ this.on(event, wrapper);
1559
+ }
1560
+ /**
1561
+ * Get all currently subscribed channels
1562
+ *
1563
+ * @returns Array of channel names
1564
+ */
1565
+ getSubscribedChannels() {
1566
+ return Array.from(this.subscribedChannels);
1567
+ }
1568
+ };
1569
+
1570
1570
  // src/client.ts
1571
1571
  var InsForgeClient = class {
1572
1572
  constructor(config = {}) {
@@ -1584,11 +1584,15 @@ var InsForgeClient = class {
1584
1584
  if (existingSession?.accessToken) {
1585
1585
  this.http.setAuthToken(existingSession.accessToken);
1586
1586
  }
1587
- this.auth = new Auth(this.http, this.tokenManager);
1587
+ this.auth = new Auth(
1588
+ this.http,
1589
+ this.tokenManager
1590
+ );
1588
1591
  this.database = new Database(this.http, this.tokenManager);
1589
1592
  this.storage = new Storage(this.http);
1590
1593
  this.ai = new AI(this.http);
1591
1594
  this.functions = new Functions(this.http);
1595
+ this.realtime = new Realtime(this.http.baseUrl, this.tokenManager);
1592
1596
  }
1593
1597
  /**
1594
1598
  * Get the underlying HTTP client for custom requests
@@ -1626,6 +1630,7 @@ var index_default = InsForgeClient;
1626
1630
  HttpClient,
1627
1631
  InsForgeClient,
1628
1632
  InsForgeError,
1633
+ Realtime,
1629
1634
  Storage,
1630
1635
  StorageBucket,
1631
1636
  TokenManager,