@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/LICENSE +201 -201
- package/README.md +249 -249
- package/dist/index.d.mts +140 -106
- package/dist/index.d.ts +140 -106
- package/dist/index.js +379 -374
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +378 -374
- package/dist/index.mjs.map +1 -1
- package/package.json +68 -67
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
|
-
|
|
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
|
|
266
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
295
|
+
if (response.accessToken && response.user) {
|
|
584
296
|
const session = {
|
|
585
297
|
accessToken: response.accessToken,
|
|
586
298
|
user: response.user
|
|
587
299
|
};
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
|
489
|
+
* Returns profile information from the users table
|
|
788
490
|
*/
|
|
789
491
|
async getProfile(userId) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
return {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
831
|
-
|
|
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
|
-
"
|
|
836
|
-
|
|
837
|
-
"
|
|
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
|
|
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(
|
|
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,
|