@insforge/sdk 1.0.1-refresh.9 → 1.0.1-schema.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.mjs CHANGED
@@ -74,7 +74,6 @@ var HttpClient = class {
74
74
  method,
75
75
  headers: requestHeaders,
76
76
  body: processedBody,
77
- credentials: "include",
78
77
  ...fetchOptions
79
78
  });
80
79
  if (response.status === 204) {
@@ -139,45 +138,8 @@ var HttpClient = class {
139
138
  // src/lib/token-manager.ts
140
139
  var TOKEN_KEY = "insforge-auth-token";
141
140
  var USER_KEY = "insforge-auth-user";
142
- var AUTH_FLAG_COOKIE = "isAuthenticated";
143
- var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
144
- function hasAuthCookie() {
145
- if (typeof document === "undefined") return false;
146
- return document.cookie.split(";").some(
147
- (c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
148
- );
149
- }
150
- function setAuthCookie() {
151
- if (typeof document === "undefined") return;
152
- const maxAge = 7 * 24 * 60 * 60;
153
- document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
154
- }
155
- function clearAuthCookie() {
156
- if (typeof document === "undefined") return;
157
- document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
158
- }
159
- function getCsrfToken() {
160
- if (typeof document === "undefined") return null;
161
- const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
162
- if (!match) return null;
163
- return match.split("=")[1] || null;
164
- }
165
- function setCsrfToken(token) {
166
- if (typeof document === "undefined") return;
167
- const maxAge = 7 * 24 * 60 * 60;
168
- document.cookie = `${CSRF_TOKEN_COOKIE}=${token}; path=/; max-age=${maxAge}; SameSite=Lax`;
169
- }
170
- function clearCsrfToken() {
171
- if (typeof document === "undefined") return;
172
- document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
173
- }
174
141
  var TokenManager = class {
175
142
  constructor(storage) {
176
- // In-memory storage
177
- this.accessToken = null;
178
- this.user = null;
179
- // Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
180
- this._mode = "storage";
181
143
  if (storage) {
182
144
  this.storage = storage;
183
145
  } else if (typeof window !== "undefined" && window.localStorage) {
@@ -195,206 +157,35 @@ var TokenManager = class {
195
157
  };
196
158
  }
197
159
  }
198
- /**
199
- * Get current mode
200
- */
201
- get mode() {
202
- return this._mode;
203
- }
204
- /**
205
- * Set mode to memory (new backend with cookies + memory)
206
- */
207
- setMemoryMode() {
208
- if (this._mode === "storage") {
209
- this.storage.removeItem(TOKEN_KEY);
210
- this.storage.removeItem(USER_KEY);
211
- }
212
- this._mode = "memory";
213
- }
214
- /**
215
- * Set mode to storage (legacy backend with localStorage)
216
- * Also loads existing session from localStorage
217
- */
218
- setStorageMode() {
219
- this._mode = "storage";
220
- this.loadFromStorage();
160
+ saveSession(session) {
161
+ this.storage.setItem(TOKEN_KEY, session.accessToken);
162
+ this.storage.setItem(USER_KEY, JSON.stringify(session.user));
221
163
  }
222
- /**
223
- * Load session from localStorage
224
- */
225
- loadFromStorage() {
164
+ getSession() {
226
165
  const token = this.storage.getItem(TOKEN_KEY);
227
166
  const userStr = this.storage.getItem(USER_KEY);
228
- if (token && userStr) {
229
- try {
230
- this.accessToken = token;
231
- this.user = JSON.parse(userStr);
232
- } catch {
233
- this.clearSession();
234
- }
167
+ if (!token || !userStr) {
168
+ return null;
235
169
  }
236
- }
237
- /**
238
- * Save session (memory always, localStorage only in storage mode)
239
- */
240
- saveSession(session) {
241
- this.accessToken = session.accessToken;
242
- this.user = session.user;
243
- if (this._mode === "storage") {
244
- this.storage.setItem(TOKEN_KEY, session.accessToken);
245
- this.storage.setItem(USER_KEY, JSON.stringify(session.user));
170
+ try {
171
+ const user = JSON.parse(userStr);
172
+ return { accessToken: token, user };
173
+ } catch {
174
+ this.clearSession();
175
+ return null;
246
176
  }
247
177
  }
248
- /**
249
- * Get current session
250
- */
251
- getSession() {
252
- if (!this.accessToken || !this.user) return null;
253
- return {
254
- accessToken: this.accessToken,
255
- user: this.user
256
- };
257
- }
258
- /**
259
- * Get access token
260
- */
261
178
  getAccessToken() {
262
- return this.accessToken;
263
- }
264
- /**
265
- * Set access token
266
- */
267
- setAccessToken(token) {
268
- this.accessToken = token;
269
- if (this._mode === "storage") {
270
- this.storage.setItem(TOKEN_KEY, token);
271
- }
272
- }
273
- /**
274
- * Get user
275
- */
276
- getUser() {
277
- return this.user;
278
- }
279
- /**
280
- * Set user
281
- */
282
- setUser(user) {
283
- this.user = user;
284
- if (this._mode === "storage") {
285
- this.storage.setItem(USER_KEY, JSON.stringify(user));
286
- }
179
+ const token = this.storage.getItem(TOKEN_KEY);
180
+ return typeof token === "string" ? token : null;
287
181
  }
288
- /**
289
- * Clear session (both memory and localStorage)
290
- */
291
182
  clearSession() {
292
- this.accessToken = null;
293
- this.user = null;
294
183
  this.storage.removeItem(TOKEN_KEY);
295
184
  this.storage.removeItem(USER_KEY);
296
185
  }
297
- /**
298
- * Check if there's a session in localStorage (for legacy detection)
299
- */
300
- hasStoredSession() {
301
- const token = this.storage.getItem(TOKEN_KEY);
302
- return !!token;
303
- }
304
- };
305
-
306
- // src/modules/database-postgrest.ts
307
- import { PostgrestClient } from "@supabase/postgrest-js";
308
- function createInsForgePostgrestFetch(httpClient, tokenManager) {
309
- return async (input, init) => {
310
- const url = typeof input === "string" ? input : input.toString();
311
- const urlObj = new URL(url);
312
- const tableName = urlObj.pathname.slice(1);
313
- const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
314
- const token = tokenManager.getAccessToken();
315
- const httpHeaders = httpClient.getHeaders();
316
- const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
317
- const headers = new Headers(init?.headers);
318
- if (authToken && !headers.has("Authorization")) {
319
- headers.set("Authorization", `Bearer ${authToken}`);
320
- }
321
- const response = await fetch(insforgeUrl, {
322
- ...init,
323
- headers
324
- });
325
- return response;
326
- };
327
- }
328
- var Database = class {
329
- constructor(httpClient, tokenManager) {
330
- this.postgrest = new PostgrestClient("http://dummy", {
331
- fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
332
- headers: {}
333
- });
334
- }
335
- /**
336
- * Create a query builder for a table
337
- *
338
- * @example
339
- * // Basic query
340
- * const { data, error } = await client.database
341
- * .from('posts')
342
- * .select('*')
343
- * .eq('user_id', userId);
344
- *
345
- * // With count (Supabase style!)
346
- * const { data, error, count } = await client.database
347
- * .from('posts')
348
- * .select('*', { count: 'exact' })
349
- * .range(0, 9);
350
- *
351
- * // Just get count, no data
352
- * const { count } = await client.database
353
- * .from('posts')
354
- * .select('*', { count: 'exact', head: true });
355
- *
356
- * // Complex queries with OR
357
- * const { data } = await client.database
358
- * .from('posts')
359
- * .select('*, users!inner(*)')
360
- * .or('status.eq.active,status.eq.pending');
361
- *
362
- * // All features work:
363
- * - Nested selects
364
- * - Foreign key expansion
365
- * - OR/AND/NOT conditions
366
- * - Count with head
367
- * - Range pagination
368
- * - Upserts
369
- */
370
- from(table) {
371
- return this.postgrest.from(table);
372
- }
373
186
  };
374
187
 
375
188
  // src/modules/auth.ts
376
- function convertDbProfileToCamelCase(dbProfile) {
377
- const result = {
378
- id: dbProfile.id
379
- };
380
- Object.keys(dbProfile).forEach((key) => {
381
- result[key] = dbProfile[key];
382
- if (key.includes("_")) {
383
- const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
384
- result[camelKey] = dbProfile[key];
385
- }
386
- });
387
- return result;
388
- }
389
- function convertCamelCaseToDbProfile(profile) {
390
- const dbProfile = {};
391
- Object.keys(profile).forEach((key) => {
392
- if (profile[key] === void 0) return;
393
- const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
394
- dbProfile[snakeKey] = profile[key];
395
- });
396
- return dbProfile;
397
- }
398
189
  function isHostedAuthEnvironment() {
399
190
  if (typeof window === "undefined") {
400
191
  return false;
@@ -412,82 +203,8 @@ var Auth = class {
412
203
  constructor(http, tokenManager) {
413
204
  this.http = http;
414
205
  this.tokenManager = tokenManager;
415
- this.database = new Database(http, tokenManager);
416
206
  this.detectAuthCallback();
417
207
  }
418
- /**
419
- * Restore session on app initialization
420
- *
421
- * @returns Object with isLoggedIn status
422
- *
423
- * @example
424
- * ```typescript
425
- * const client = new InsForgeClient({ baseUrl: '...' });
426
- * const { isLoggedIn } = await client.auth.restoreSession();
427
- *
428
- * if (isLoggedIn) {
429
- * const { data } = await client.auth.getCurrentUser();
430
- * }
431
- * ```
432
- */
433
- async restoreSession() {
434
- if (typeof window === "undefined") {
435
- return { isLoggedIn: false };
436
- }
437
- if (this.tokenManager.getAccessToken()) {
438
- return { isLoggedIn: true };
439
- }
440
- if (hasAuthCookie()) {
441
- try {
442
- const csrfToken = getCsrfToken();
443
- const response = await this.http.post(
444
- "/api/auth/refresh",
445
- {
446
- headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {}
447
- }
448
- );
449
- if (response.accessToken) {
450
- this.tokenManager.setMemoryMode();
451
- this.tokenManager.setAccessToken(response.accessToken);
452
- this.http.setAuthToken(response.accessToken);
453
- if (response.user) {
454
- this.tokenManager.setUser(response.user);
455
- }
456
- if (response.csrfToken) {
457
- setCsrfToken(response.csrfToken);
458
- }
459
- return { isLoggedIn: true };
460
- }
461
- } catch (error) {
462
- if (error instanceof InsForgeError) {
463
- if (error.statusCode === 404) {
464
- this.tokenManager.setStorageMode();
465
- const token = this.tokenManager.getAccessToken();
466
- if (token) {
467
- this.http.setAuthToken(token);
468
- return { isLoggedIn: true };
469
- }
470
- return { isLoggedIn: false };
471
- }
472
- if (error.statusCode === 401 || error.statusCode === 403) {
473
- clearAuthCookie();
474
- clearCsrfToken();
475
- return { isLoggedIn: false };
476
- }
477
- }
478
- return { isLoggedIn: false };
479
- }
480
- }
481
- if (this.tokenManager.hasStoredSession()) {
482
- this.tokenManager.setStorageMode();
483
- const token = this.tokenManager.getAccessToken();
484
- if (token) {
485
- this.http.setAuthToken(token);
486
- return { isLoggedIn: true };
487
- }
488
- }
489
- return { isLoggedIn: false };
490
- }
491
208
  /**
492
209
  * Automatically detect and handle OAuth callback parameters in the URL
493
210
  * This runs on initialization to seamlessly complete the OAuth flow
@@ -501,7 +218,6 @@ var Auth = class {
501
218
  const userId = params.get("user_id");
502
219
  const email = params.get("email");
503
220
  const name = params.get("name");
504
- const csrfToken = params.get("csrf_token");
505
221
  if (accessToken && userId && email) {
506
222
  const session = {
507
223
  accessToken,
@@ -516,18 +232,13 @@ var Auth = class {
516
232
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
517
233
  }
518
234
  };
519
- this.http.setAuthToken(accessToken);
520
235
  this.tokenManager.saveSession(session);
521
- setAuthCookie();
522
- if (csrfToken) {
523
- setCsrfToken(csrfToken);
524
- }
236
+ this.http.setAuthToken(accessToken);
525
237
  const url = new URL(window.location.href);
526
238
  url.searchParams.delete("access_token");
527
239
  url.searchParams.delete("user_id");
528
240
  url.searchParams.delete("email");
529
241
  url.searchParams.delete("name");
530
- url.searchParams.delete("csrf_token");
531
242
  if (params.has("error")) {
532
243
  url.searchParams.delete("error");
533
244
  }
@@ -543,17 +254,15 @@ var Auth = class {
543
254
  async signUp(request) {
544
255
  try {
545
256
  const response = await this.http.post("/api/auth/users", request);
546
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
257
+ if (response.accessToken && response.user) {
547
258
  const session = {
548
259
  accessToken: response.accessToken,
549
260
  user: response.user
550
261
  };
551
- this.tokenManager.saveSession(session);
552
- setAuthCookie();
553
- this.http.setAuthToken(response.accessToken);
554
- if (response.csrfToken) {
555
- setCsrfToken(response.csrfToken);
262
+ if (!isHostedAuthEnvironment()) {
263
+ this.tokenManager.saveSession(session);
556
264
  }
265
+ this.http.setAuthToken(response.accessToken);
557
266
  }
558
267
  return {
559
268
  data: response,
@@ -579,18 +288,21 @@ var Auth = class {
579
288
  async signInWithPassword(request) {
580
289
  try {
581
290
  const response = await this.http.post("/api/auth/sessions", request);
582
- if (response.accessToken && response.user && !isHostedAuthEnvironment()) {
583
- const session = {
584
- accessToken: response.accessToken,
585
- user: response.user
586
- };
587
- this.tokenManager.saveSession(session);
588
- setAuthCookie();
589
- this.http.setAuthToken(response.accessToken);
590
- if (response.csrfToken) {
591
- setCsrfToken(response.csrfToken);
291
+ const session = {
292
+ accessToken: response.accessToken || "",
293
+ user: response.user || {
294
+ id: "",
295
+ email: "",
296
+ name: "",
297
+ emailVerified: false,
298
+ createdAt: "",
299
+ updatedAt: ""
592
300
  }
301
+ };
302
+ if (!isHostedAuthEnvironment()) {
303
+ this.tokenManager.saveSession(session);
593
304
  }
305
+ this.http.setAuthToken(response.accessToken || "");
594
306
  return {
595
307
  data: response,
596
308
  error: null
@@ -648,14 +360,8 @@ var Auth = class {
648
360
  */
649
361
  async signOut() {
650
362
  try {
651
- try {
652
- await this.http.post("/api/auth/logout");
653
- } catch {
654
- }
655
363
  this.tokenManager.clearSession();
656
364
  this.http.setAuthToken(null);
657
- clearAuthCookie();
658
- clearCsrfToken();
659
365
  return { error: null };
660
366
  } catch (error) {
661
367
  return {
@@ -716,14 +422,9 @@ var Auth = class {
716
422
  }
717
423
  this.http.setAuthToken(session.accessToken);
718
424
  const authResponse = await this.http.get("/api/auth/sessions/current");
719
- const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
720
- if (profileError && profileError.code !== "PGRST116") {
721
- return { data: null, error: profileError };
722
- }
723
425
  return {
724
426
  data: {
725
- user: authResponse.user,
726
- profile: profile ? convertDbProfileToCamelCase(profile) : null
427
+ user: authResponse.user
727
428
  },
728
429
  error: null
729
430
  };
@@ -747,17 +448,28 @@ var Auth = class {
747
448
  }
748
449
  /**
749
450
  * Get any user's profile by ID
750
- * Returns profile information from the users table (dynamic fields)
451
+ * Returns profile information from the users table
751
452
  */
752
453
  async getProfile(userId) {
753
- const { data, error } = await this.database.from("users").select("*").eq("id", userId).single();
754
- if (error && error.code === "PGRST116") {
755
- return { data: null, error: null };
756
- }
757
- if (data) {
758
- return { data: convertDbProfileToCamelCase(data), error: null };
454
+ try {
455
+ const response = await this.http.get(`/api/auth/profiles/${userId}`);
456
+ return {
457
+ data: response,
458
+ error: null
459
+ };
460
+ } catch (error) {
461
+ if (error instanceof InsForgeError) {
462
+ return { data: null, error };
463
+ }
464
+ return {
465
+ data: null,
466
+ error: new InsForgeError(
467
+ "An unexpected error occurred while fetching user profile",
468
+ 500,
469
+ "UNEXPECTED_ERROR"
470
+ )
471
+ };
759
472
  }
760
- return { data: null, error };
761
473
  }
762
474
  /**
763
475
  * Get the current session (only session data, no API call)
@@ -788,42 +500,31 @@ var Auth = class {
788
500
  /**
789
501
  * Set/Update the current user's profile
790
502
  * Updates profile information in the users table (supports any dynamic fields)
503
+ * Requires authentication
791
504
  */
792
505
  async setProfile(profile) {
793
- const session = this.tokenManager.getSession();
794
- if (!session?.accessToken) {
506
+ try {
507
+ const response = await this.http.patch(
508
+ "/api/auth/profiles/current",
509
+ { profile }
510
+ );
511
+ return {
512
+ data: response,
513
+ error: null
514
+ };
515
+ } catch (error) {
516
+ if (error instanceof InsForgeError) {
517
+ return { data: null, error };
518
+ }
795
519
  return {
796
520
  data: null,
797
521
  error: new InsForgeError(
798
- "No authenticated user found",
799
- 401,
800
- "UNAUTHENTICATED"
522
+ "An unexpected error occurred while updating user profile",
523
+ 500,
524
+ "UNEXPECTED_ERROR"
801
525
  )
802
526
  };
803
527
  }
804
- if (!session.user?.id) {
805
- const { data: data2, error: error2 } = await this.getCurrentUser();
806
- if (error2) {
807
- return { data: null, error: error2 };
808
- }
809
- if (data2?.user) {
810
- session.user = {
811
- id: data2.user.id,
812
- email: data2.user.email,
813
- name: "",
814
- emailVerified: false,
815
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
816
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
817
- };
818
- this.tokenManager.saveSession(session);
819
- }
820
- }
821
- const dbProfile = convertCamelCaseToDbProfile(profile);
822
- const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
823
- if (data) {
824
- return { data: convertDbProfileToCamelCase(data), error: null };
825
- }
826
- return { data: null, error };
827
528
  }
828
529
  /**
829
530
  * Send email verification (code or link based on config)
@@ -977,17 +678,13 @@ var Auth = class {
977
678
  "/api/auth/email/verify",
978
679
  request
979
680
  );
980
- if (response.accessToken && !isHostedAuthEnvironment()) {
681
+ if (response.accessToken) {
981
682
  const session = {
982
683
  accessToken: response.accessToken,
983
684
  user: response.user || {}
984
685
  };
985
686
  this.tokenManager.saveSession(session);
986
687
  this.http.setAuthToken(response.accessToken);
987
- setAuthCookie();
988
- if (response.csrfToken) {
989
- setCsrfToken(response.csrfToken);
990
- }
991
688
  }
992
689
  return {
993
690
  data: response,
@@ -1009,6 +706,75 @@ var Auth = class {
1009
706
  }
1010
707
  };
1011
708
 
709
+ // src/modules/database-postgrest.ts
710
+ import { PostgrestClient } from "@supabase/postgrest-js";
711
+ function createInsForgePostgrestFetch(httpClient, tokenManager) {
712
+ return async (input, init) => {
713
+ const url = typeof input === "string" ? input : input.toString();
714
+ const urlObj = new URL(url);
715
+ const tableName = urlObj.pathname.slice(1);
716
+ const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
717
+ const token = tokenManager.getAccessToken();
718
+ const httpHeaders = httpClient.getHeaders();
719
+ const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
720
+ const headers = new Headers(init?.headers);
721
+ if (authToken && !headers.has("Authorization")) {
722
+ headers.set("Authorization", `Bearer ${authToken}`);
723
+ }
724
+ const response = await fetch(insforgeUrl, {
725
+ ...init,
726
+ headers
727
+ });
728
+ return response;
729
+ };
730
+ }
731
+ var Database = class {
732
+ constructor(httpClient, tokenManager) {
733
+ this.postgrest = new PostgrestClient("http://dummy", {
734
+ fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
735
+ headers: {}
736
+ });
737
+ }
738
+ /**
739
+ * Create a query builder for a table
740
+ *
741
+ * @example
742
+ * // Basic query
743
+ * const { data, error } = await client.database
744
+ * .from('posts')
745
+ * .select('*')
746
+ * .eq('user_id', userId);
747
+ *
748
+ * // With count (Supabase style!)
749
+ * const { data, error, count } = await client.database
750
+ * .from('posts')
751
+ * .select('*', { count: 'exact' })
752
+ * .range(0, 9);
753
+ *
754
+ * // Just get count, no data
755
+ * const { count } = await client.database
756
+ * .from('posts')
757
+ * .select('*', { count: 'exact', head: true });
758
+ *
759
+ * // Complex queries with OR
760
+ * const { data } = await client.database
761
+ * .from('posts')
762
+ * .select('*, users!inner(*)')
763
+ * .or('status.eq.active,status.eq.pending');
764
+ *
765
+ * // All features work:
766
+ * - Nested selects
767
+ * - Foreign key expansion
768
+ * - OR/AND/NOT conditions
769
+ * - Count with head
770
+ * - Range pagination
771
+ * - Upserts
772
+ */
773
+ from(table) {
774
+ return this.postgrest.from(table);
775
+ }
776
+ };
777
+
1012
778
  // src/modules/storage.ts
1013
779
  var StorageBucket = class {
1014
780
  constructor(bucketName, http) {
@@ -1530,6 +1296,239 @@ var Functions = class {
1530
1296
  }
1531
1297
  };
1532
1298
 
1299
+ // src/modules/realtime.ts
1300
+ import { io } from "socket.io-client";
1301
+ var CONNECT_TIMEOUT = 1e4;
1302
+ var Realtime = class {
1303
+ constructor(baseUrl, tokenManager) {
1304
+ this.socket = null;
1305
+ this.connectPromise = null;
1306
+ this.subscribedChannels = /* @__PURE__ */ new Set();
1307
+ this.eventListeners = /* @__PURE__ */ new Map();
1308
+ this.baseUrl = baseUrl;
1309
+ this.tokenManager = tokenManager;
1310
+ }
1311
+ notifyListeners(event, payload) {
1312
+ const listeners = this.eventListeners.get(event);
1313
+ if (!listeners) return;
1314
+ for (const cb of listeners) {
1315
+ try {
1316
+ cb(payload);
1317
+ } catch (err) {
1318
+ console.error(`Error in ${event} callback:`, err);
1319
+ }
1320
+ }
1321
+ }
1322
+ /**
1323
+ * Connect to the realtime server
1324
+ * @returns Promise that resolves when connected
1325
+ */
1326
+ connect() {
1327
+ if (this.socket?.connected) {
1328
+ return Promise.resolve();
1329
+ }
1330
+ if (this.connectPromise) {
1331
+ return this.connectPromise;
1332
+ }
1333
+ this.connectPromise = new Promise((resolve, reject) => {
1334
+ const session = this.tokenManager.getSession();
1335
+ const token = session?.accessToken;
1336
+ this.socket = io(this.baseUrl, {
1337
+ transports: ["websocket"],
1338
+ auth: token ? { token } : void 0
1339
+ });
1340
+ let initialConnection = true;
1341
+ let timeoutId = null;
1342
+ const cleanup = () => {
1343
+ if (timeoutId) {
1344
+ clearTimeout(timeoutId);
1345
+ timeoutId = null;
1346
+ }
1347
+ };
1348
+ timeoutId = setTimeout(() => {
1349
+ if (initialConnection) {
1350
+ initialConnection = false;
1351
+ this.connectPromise = null;
1352
+ this.socket?.disconnect();
1353
+ this.socket = null;
1354
+ reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
1355
+ }
1356
+ }, CONNECT_TIMEOUT);
1357
+ this.socket.on("connect", () => {
1358
+ cleanup();
1359
+ for (const channel of this.subscribedChannels) {
1360
+ this.socket.emit("realtime:subscribe", { channel });
1361
+ }
1362
+ this.notifyListeners("connect");
1363
+ if (initialConnection) {
1364
+ initialConnection = false;
1365
+ this.connectPromise = null;
1366
+ resolve();
1367
+ }
1368
+ });
1369
+ this.socket.on("connect_error", (error) => {
1370
+ cleanup();
1371
+ this.notifyListeners("connect_error", error);
1372
+ if (initialConnection) {
1373
+ initialConnection = false;
1374
+ this.connectPromise = null;
1375
+ reject(error);
1376
+ }
1377
+ });
1378
+ this.socket.on("disconnect", (reason) => {
1379
+ this.notifyListeners("disconnect", reason);
1380
+ });
1381
+ this.socket.on("realtime:error", (error) => {
1382
+ this.notifyListeners("error", error);
1383
+ });
1384
+ this.socket.onAny((event, message) => {
1385
+ if (event === "realtime:error") return;
1386
+ this.notifyListeners(event, message);
1387
+ });
1388
+ });
1389
+ return this.connectPromise;
1390
+ }
1391
+ /**
1392
+ * Disconnect from the realtime server
1393
+ */
1394
+ disconnect() {
1395
+ if (this.socket) {
1396
+ this.socket.disconnect();
1397
+ this.socket = null;
1398
+ }
1399
+ this.subscribedChannels.clear();
1400
+ }
1401
+ /**
1402
+ * Check if connected to the realtime server
1403
+ */
1404
+ get isConnected() {
1405
+ return this.socket?.connected ?? false;
1406
+ }
1407
+ /**
1408
+ * Get the current connection state
1409
+ */
1410
+ get connectionState() {
1411
+ if (!this.socket) return "disconnected";
1412
+ if (this.socket.connected) return "connected";
1413
+ return "connecting";
1414
+ }
1415
+ /**
1416
+ * Get the socket ID (if connected)
1417
+ */
1418
+ get socketId() {
1419
+ return this.socket?.id;
1420
+ }
1421
+ /**
1422
+ * Subscribe to a channel
1423
+ *
1424
+ * Automatically connects if not already connected.
1425
+ *
1426
+ * @param channel - Channel name (e.g., 'orders:123', 'broadcast')
1427
+ * @returns Promise with the subscription response
1428
+ */
1429
+ async subscribe(channel) {
1430
+ if (this.subscribedChannels.has(channel)) {
1431
+ return { ok: true, channel };
1432
+ }
1433
+ if (!this.socket?.connected) {
1434
+ try {
1435
+ await this.connect();
1436
+ } catch (error) {
1437
+ const message = error instanceof Error ? error.message : "Connection failed";
1438
+ return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
1439
+ }
1440
+ }
1441
+ return new Promise((resolve) => {
1442
+ this.socket.emit("realtime:subscribe", { channel }, (response) => {
1443
+ if (response.ok) {
1444
+ this.subscribedChannels.add(channel);
1445
+ }
1446
+ resolve(response);
1447
+ });
1448
+ });
1449
+ }
1450
+ /**
1451
+ * Unsubscribe from a channel (fire-and-forget)
1452
+ *
1453
+ * @param channel - Channel name to unsubscribe from
1454
+ */
1455
+ unsubscribe(channel) {
1456
+ this.subscribedChannels.delete(channel);
1457
+ if (this.socket?.connected) {
1458
+ this.socket.emit("realtime:unsubscribe", { channel });
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Publish a message to a channel
1463
+ *
1464
+ * @param channel - Channel name
1465
+ * @param event - Event name
1466
+ * @param payload - Message payload
1467
+ */
1468
+ async publish(channel, event, payload) {
1469
+ if (!this.socket?.connected) {
1470
+ throw new Error("Not connected to realtime server. Call connect() first.");
1471
+ }
1472
+ this.socket.emit("realtime:publish", { channel, event, payload });
1473
+ }
1474
+ /**
1475
+ * Listen for events
1476
+ *
1477
+ * Reserved event names:
1478
+ * - 'connect' - Fired when connected to the server
1479
+ * - 'connect_error' - Fired when connection fails (payload: Error)
1480
+ * - 'disconnect' - Fired when disconnected (payload: reason string)
1481
+ * - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
1482
+ *
1483
+ * All other events receive a `SocketMessage` payload with metadata.
1484
+ *
1485
+ * @param event - Event name to listen for
1486
+ * @param callback - Callback function when event is received
1487
+ */
1488
+ on(event, callback) {
1489
+ if (!this.eventListeners.has(event)) {
1490
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
1491
+ }
1492
+ this.eventListeners.get(event).add(callback);
1493
+ }
1494
+ /**
1495
+ * Remove a listener for a specific event
1496
+ *
1497
+ * @param event - Event name
1498
+ * @param callback - The callback function to remove
1499
+ */
1500
+ off(event, callback) {
1501
+ const listeners = this.eventListeners.get(event);
1502
+ if (listeners) {
1503
+ listeners.delete(callback);
1504
+ if (listeners.size === 0) {
1505
+ this.eventListeners.delete(event);
1506
+ }
1507
+ }
1508
+ }
1509
+ /**
1510
+ * Listen for an event only once, then automatically remove the listener
1511
+ *
1512
+ * @param event - Event name to listen for
1513
+ * @param callback - Callback function when event is received
1514
+ */
1515
+ once(event, callback) {
1516
+ const wrapper = (payload) => {
1517
+ this.off(event, wrapper);
1518
+ callback(payload);
1519
+ };
1520
+ this.on(event, wrapper);
1521
+ }
1522
+ /**
1523
+ * Get all currently subscribed channels
1524
+ *
1525
+ * @returns Array of channel names
1526
+ */
1527
+ getSubscribedChannels() {
1528
+ return Array.from(this.subscribedChannels);
1529
+ }
1530
+ };
1531
+
1533
1532
  // src/client.ts
1534
1533
  var InsForgeClient = class {
1535
1534
  constructor(config = {}) {
@@ -1547,11 +1546,15 @@ var InsForgeClient = class {
1547
1546
  if (existingSession?.accessToken) {
1548
1547
  this.http.setAuthToken(existingSession.accessToken);
1549
1548
  }
1550
- this.auth = new Auth(this.http, this.tokenManager);
1549
+ this.auth = new Auth(
1550
+ this.http,
1551
+ this.tokenManager
1552
+ );
1551
1553
  this.database = new Database(this.http, this.tokenManager);
1552
1554
  this.storage = new Storage(this.http);
1553
1555
  this.ai = new AI(this.http);
1554
1556
  this.functions = new Functions(this.http);
1557
+ this.realtime = new Realtime(this.http.baseUrl, this.tokenManager);
1555
1558
  }
1556
1559
  /**
1557
1560
  * Get the underlying HTTP client for custom requests
@@ -1588,6 +1591,7 @@ export {
1588
1591
  HttpClient,
1589
1592
  InsForgeClient,
1590
1593
  InsForgeError,
1594
+ Realtime,
1591
1595
  Storage,
1592
1596
  StorageBucket,
1593
1597
  TokenManager,