@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.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
|
-
|
|
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
|
|
229
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
257
|
+
if (response.accessToken && response.user) {
|
|
547
258
|
const session = {
|
|
548
259
|
accessToken: response.accessToken,
|
|
549
260
|
user: response.user
|
|
550
261
|
};
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
|
451
|
+
* Returns profile information from the users table
|
|
751
452
|
*/
|
|
752
453
|
async getProfile(userId) {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
return {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
-
|
|
794
|
-
|
|
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
|
-
"
|
|
799
|
-
|
|
800
|
-
"
|
|
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
|
|
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(
|
|
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,
|