@insforge/sdk 1.0.1-refresh.8 → 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 -335
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +378 -335
- 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,29 +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
|
-
function hasAuthCookie() {
|
|
144
|
-
if (typeof document === "undefined") return false;
|
|
145
|
-
return document.cookie.split(";").some(
|
|
146
|
-
(c) => c.trim().startsWith(`${AUTH_FLAG_COOKIE}=`)
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
function setAuthCookie() {
|
|
150
|
-
if (typeof document === "undefined") return;
|
|
151
|
-
const maxAge = 7 * 24 * 60 * 60;
|
|
152
|
-
document.cookie = `${AUTH_FLAG_COOKIE}=true; path=/; max-age=${maxAge}; SameSite=Lax`;
|
|
153
|
-
}
|
|
154
|
-
function clearAuthCookie() {
|
|
155
|
-
if (typeof document === "undefined") return;
|
|
156
|
-
document.cookie = `${AUTH_FLAG_COOKIE}=; path=/; max-age=0; SameSite=Lax`;
|
|
157
|
-
}
|
|
158
141
|
var TokenManager = class {
|
|
159
142
|
constructor(storage) {
|
|
160
|
-
// In-memory storage
|
|
161
|
-
this.accessToken = null;
|
|
162
|
-
this.user = null;
|
|
163
|
-
// Mode: 'memory' (new backend) or 'storage' (legacy backend, default)
|
|
164
|
-
this._mode = "storage";
|
|
165
143
|
if (storage) {
|
|
166
144
|
this.storage = storage;
|
|
167
145
|
} else if (typeof window !== "undefined" && window.localStorage) {
|
|
@@ -179,206 +157,35 @@ var TokenManager = class {
|
|
|
179
157
|
};
|
|
180
158
|
}
|
|
181
159
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
get mode() {
|
|
186
|
-
return this._mode;
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Set mode to memory (new backend with cookies + memory)
|
|
190
|
-
*/
|
|
191
|
-
setMemoryMode() {
|
|
192
|
-
if (this._mode === "storage") {
|
|
193
|
-
this.storage.removeItem(TOKEN_KEY);
|
|
194
|
-
this.storage.removeItem(USER_KEY);
|
|
195
|
-
}
|
|
196
|
-
this._mode = "memory";
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Set mode to storage (legacy backend with localStorage)
|
|
200
|
-
* Also loads existing session from localStorage
|
|
201
|
-
*/
|
|
202
|
-
setStorageMode() {
|
|
203
|
-
this._mode = "storage";
|
|
204
|
-
this.loadFromStorage();
|
|
160
|
+
saveSession(session) {
|
|
161
|
+
this.storage.setItem(TOKEN_KEY, session.accessToken);
|
|
162
|
+
this.storage.setItem(USER_KEY, JSON.stringify(session.user));
|
|
205
163
|
}
|
|
206
|
-
|
|
207
|
-
* Load session from localStorage
|
|
208
|
-
*/
|
|
209
|
-
loadFromStorage() {
|
|
164
|
+
getSession() {
|
|
210
165
|
const token = this.storage.getItem(TOKEN_KEY);
|
|
211
166
|
const userStr = this.storage.getItem(USER_KEY);
|
|
212
|
-
if (token
|
|
213
|
-
|
|
214
|
-
this.accessToken = token;
|
|
215
|
-
this.user = JSON.parse(userStr);
|
|
216
|
-
} catch {
|
|
217
|
-
this.clearSession();
|
|
218
|
-
}
|
|
167
|
+
if (!token || !userStr) {
|
|
168
|
+
return null;
|
|
219
169
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.user = session.user;
|
|
227
|
-
if (this._mode === "storage") {
|
|
228
|
-
this.storage.setItem(TOKEN_KEY, session.accessToken);
|
|
229
|
-
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;
|
|
230
176
|
}
|
|
231
177
|
}
|
|
232
|
-
/**
|
|
233
|
-
* Get current session
|
|
234
|
-
*/
|
|
235
|
-
getSession() {
|
|
236
|
-
if (!this.accessToken || !this.user) return null;
|
|
237
|
-
return {
|
|
238
|
-
accessToken: this.accessToken,
|
|
239
|
-
user: this.user
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Get access token
|
|
244
|
-
*/
|
|
245
178
|
getAccessToken() {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Set access token
|
|
250
|
-
*/
|
|
251
|
-
setAccessToken(token) {
|
|
252
|
-
this.accessToken = token;
|
|
253
|
-
if (this._mode === "storage") {
|
|
254
|
-
this.storage.setItem(TOKEN_KEY, token);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Get user
|
|
259
|
-
*/
|
|
260
|
-
getUser() {
|
|
261
|
-
return this.user;
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Set user
|
|
265
|
-
*/
|
|
266
|
-
setUser(user) {
|
|
267
|
-
this.user = user;
|
|
268
|
-
if (this._mode === "storage") {
|
|
269
|
-
this.storage.setItem(USER_KEY, JSON.stringify(user));
|
|
270
|
-
}
|
|
179
|
+
const token = this.storage.getItem(TOKEN_KEY);
|
|
180
|
+
return typeof token === "string" ? token : null;
|
|
271
181
|
}
|
|
272
|
-
/**
|
|
273
|
-
* Clear session (both memory and localStorage)
|
|
274
|
-
*/
|
|
275
182
|
clearSession() {
|
|
276
|
-
this.accessToken = null;
|
|
277
|
-
this.user = null;
|
|
278
183
|
this.storage.removeItem(TOKEN_KEY);
|
|
279
184
|
this.storage.removeItem(USER_KEY);
|
|
280
185
|
}
|
|
281
|
-
/**
|
|
282
|
-
* Check if there's a session in localStorage (for legacy detection)
|
|
283
|
-
*/
|
|
284
|
-
hasStoredSession() {
|
|
285
|
-
const token = this.storage.getItem(TOKEN_KEY);
|
|
286
|
-
return !!token;
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
// src/modules/database-postgrest.ts
|
|
291
|
-
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
292
|
-
function createInsForgePostgrestFetch(httpClient, tokenManager) {
|
|
293
|
-
return async (input, init) => {
|
|
294
|
-
const url = typeof input === "string" ? input : input.toString();
|
|
295
|
-
const urlObj = new URL(url);
|
|
296
|
-
const tableName = urlObj.pathname.slice(1);
|
|
297
|
-
const insforgeUrl = `${httpClient.baseUrl}/api/database/records/${tableName}${urlObj.search}`;
|
|
298
|
-
const token = tokenManager.getAccessToken();
|
|
299
|
-
const httpHeaders = httpClient.getHeaders();
|
|
300
|
-
const authToken = token || httpHeaders["Authorization"]?.replace("Bearer ", "");
|
|
301
|
-
const headers = new Headers(init?.headers);
|
|
302
|
-
if (authToken && !headers.has("Authorization")) {
|
|
303
|
-
headers.set("Authorization", `Bearer ${authToken}`);
|
|
304
|
-
}
|
|
305
|
-
const response = await fetch(insforgeUrl, {
|
|
306
|
-
...init,
|
|
307
|
-
headers
|
|
308
|
-
});
|
|
309
|
-
return response;
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
var Database = class {
|
|
313
|
-
constructor(httpClient, tokenManager) {
|
|
314
|
-
this.postgrest = new PostgrestClient("http://dummy", {
|
|
315
|
-
fetch: createInsForgePostgrestFetch(httpClient, tokenManager),
|
|
316
|
-
headers: {}
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Create a query builder for a table
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* // Basic query
|
|
324
|
-
* const { data, error } = await client.database
|
|
325
|
-
* .from('posts')
|
|
326
|
-
* .select('*')
|
|
327
|
-
* .eq('user_id', userId);
|
|
328
|
-
*
|
|
329
|
-
* // With count (Supabase style!)
|
|
330
|
-
* const { data, error, count } = await client.database
|
|
331
|
-
* .from('posts')
|
|
332
|
-
* .select('*', { count: 'exact' })
|
|
333
|
-
* .range(0, 9);
|
|
334
|
-
*
|
|
335
|
-
* // Just get count, no data
|
|
336
|
-
* const { count } = await client.database
|
|
337
|
-
* .from('posts')
|
|
338
|
-
* .select('*', { count: 'exact', head: true });
|
|
339
|
-
*
|
|
340
|
-
* // Complex queries with OR
|
|
341
|
-
* const { data } = await client.database
|
|
342
|
-
* .from('posts')
|
|
343
|
-
* .select('*, users!inner(*)')
|
|
344
|
-
* .or('status.eq.active,status.eq.pending');
|
|
345
|
-
*
|
|
346
|
-
* // All features work:
|
|
347
|
-
* - Nested selects
|
|
348
|
-
* - Foreign key expansion
|
|
349
|
-
* - OR/AND/NOT conditions
|
|
350
|
-
* - Count with head
|
|
351
|
-
* - Range pagination
|
|
352
|
-
* - Upserts
|
|
353
|
-
*/
|
|
354
|
-
from(table) {
|
|
355
|
-
return this.postgrest.from(table);
|
|
356
|
-
}
|
|
357
186
|
};
|
|
358
187
|
|
|
359
188
|
// src/modules/auth.ts
|
|
360
|
-
function convertDbProfileToCamelCase(dbProfile) {
|
|
361
|
-
const result = {
|
|
362
|
-
id: dbProfile.id
|
|
363
|
-
};
|
|
364
|
-
Object.keys(dbProfile).forEach((key) => {
|
|
365
|
-
result[key] = dbProfile[key];
|
|
366
|
-
if (key.includes("_")) {
|
|
367
|
-
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
368
|
-
result[camelKey] = dbProfile[key];
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
function convertCamelCaseToDbProfile(profile) {
|
|
374
|
-
const dbProfile = {};
|
|
375
|
-
Object.keys(profile).forEach((key) => {
|
|
376
|
-
if (profile[key] === void 0) return;
|
|
377
|
-
const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
378
|
-
dbProfile[snakeKey] = profile[key];
|
|
379
|
-
});
|
|
380
|
-
return dbProfile;
|
|
381
|
-
}
|
|
382
189
|
function isHostedAuthEnvironment() {
|
|
383
190
|
if (typeof window === "undefined") {
|
|
384
191
|
return false;
|
|
@@ -396,74 +203,8 @@ var Auth = class {
|
|
|
396
203
|
constructor(http, tokenManager) {
|
|
397
204
|
this.http = http;
|
|
398
205
|
this.tokenManager = tokenManager;
|
|
399
|
-
this.database = new Database(http, tokenManager);
|
|
400
206
|
this.detectAuthCallback();
|
|
401
207
|
}
|
|
402
|
-
/**
|
|
403
|
-
* Restore session on app initialization
|
|
404
|
-
*
|
|
405
|
-
* @returns Object with isLoggedIn status
|
|
406
|
-
*
|
|
407
|
-
* @example
|
|
408
|
-
* ```typescript
|
|
409
|
-
* const client = new InsForgeClient({ baseUrl: '...' });
|
|
410
|
-
* const { isLoggedIn } = await client.auth.restoreSession();
|
|
411
|
-
*
|
|
412
|
-
* if (isLoggedIn) {
|
|
413
|
-
* const { data } = await client.auth.getCurrentUser();
|
|
414
|
-
* }
|
|
415
|
-
* ```
|
|
416
|
-
*/
|
|
417
|
-
async restoreSession() {
|
|
418
|
-
if (typeof window === "undefined") {
|
|
419
|
-
return { isLoggedIn: false };
|
|
420
|
-
}
|
|
421
|
-
if (this.tokenManager.getAccessToken()) {
|
|
422
|
-
return { isLoggedIn: true };
|
|
423
|
-
}
|
|
424
|
-
if (hasAuthCookie()) {
|
|
425
|
-
try {
|
|
426
|
-
const response = await this.http.post(
|
|
427
|
-
"/api/auth/refresh"
|
|
428
|
-
);
|
|
429
|
-
if (response.accessToken) {
|
|
430
|
-
this.tokenManager.setMemoryMode();
|
|
431
|
-
this.tokenManager.setAccessToken(response.accessToken);
|
|
432
|
-
this.http.setAuthToken(response.accessToken);
|
|
433
|
-
if (response.user) {
|
|
434
|
-
this.tokenManager.setUser(response.user);
|
|
435
|
-
}
|
|
436
|
-
return { isLoggedIn: true };
|
|
437
|
-
}
|
|
438
|
-
} catch (error) {
|
|
439
|
-
if (error instanceof InsForgeError) {
|
|
440
|
-
if (error.statusCode === 404) {
|
|
441
|
-
this.tokenManager.setStorageMode();
|
|
442
|
-
const token = this.tokenManager.getAccessToken();
|
|
443
|
-
if (token) {
|
|
444
|
-
this.http.setAuthToken(token);
|
|
445
|
-
return { isLoggedIn: true };
|
|
446
|
-
}
|
|
447
|
-
return { isLoggedIn: false };
|
|
448
|
-
}
|
|
449
|
-
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
450
|
-
clearAuthCookie();
|
|
451
|
-
return { isLoggedIn: false };
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return { isLoggedIn: false };
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (this.tokenManager.hasStoredSession()) {
|
|
458
|
-
this.tokenManager.setStorageMode();
|
|
459
|
-
const token = this.tokenManager.getAccessToken();
|
|
460
|
-
if (token) {
|
|
461
|
-
this.http.setAuthToken(token);
|
|
462
|
-
return { isLoggedIn: true };
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return { isLoggedIn: false };
|
|
466
|
-
}
|
|
467
208
|
/**
|
|
468
209
|
* Automatically detect and handle OAuth callback parameters in the URL
|
|
469
210
|
* This runs on initialization to seamlessly complete the OAuth flow
|
|
@@ -491,9 +232,8 @@ var Auth = class {
|
|
|
491
232
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
492
233
|
}
|
|
493
234
|
};
|
|
494
|
-
this.http.setAuthToken(accessToken);
|
|
495
235
|
this.tokenManager.saveSession(session);
|
|
496
|
-
|
|
236
|
+
this.http.setAuthToken(accessToken);
|
|
497
237
|
const url = new URL(window.location.href);
|
|
498
238
|
url.searchParams.delete("access_token");
|
|
499
239
|
url.searchParams.delete("user_id");
|
|
@@ -514,13 +254,14 @@ var Auth = class {
|
|
|
514
254
|
async signUp(request) {
|
|
515
255
|
try {
|
|
516
256
|
const response = await this.http.post("/api/auth/users", request);
|
|
517
|
-
if (response.accessToken && response.user
|
|
257
|
+
if (response.accessToken && response.user) {
|
|
518
258
|
const session = {
|
|
519
259
|
accessToken: response.accessToken,
|
|
520
260
|
user: response.user
|
|
521
261
|
};
|
|
522
|
-
|
|
523
|
-
|
|
262
|
+
if (!isHostedAuthEnvironment()) {
|
|
263
|
+
this.tokenManager.saveSession(session);
|
|
264
|
+
}
|
|
524
265
|
this.http.setAuthToken(response.accessToken);
|
|
525
266
|
}
|
|
526
267
|
return {
|
|
@@ -547,15 +288,21 @@ var Auth = class {
|
|
|
547
288
|
async signInWithPassword(request) {
|
|
548
289
|
try {
|
|
549
290
|
const response = await this.http.post("/api/auth/sessions", request);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
291
|
+
const session = {
|
|
292
|
+
accessToken: response.accessToken || "",
|
|
293
|
+
user: response.user || {
|
|
294
|
+
id: "",
|
|
295
|
+
email: "",
|
|
296
|
+
name: "",
|
|
297
|
+
emailVerified: false,
|
|
298
|
+
createdAt: "",
|
|
299
|
+
updatedAt: ""
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
if (!isHostedAuthEnvironment()) {
|
|
555
303
|
this.tokenManager.saveSession(session);
|
|
556
|
-
setAuthCookie();
|
|
557
|
-
this.http.setAuthToken(response.accessToken);
|
|
558
304
|
}
|
|
305
|
+
this.http.setAuthToken(response.accessToken || "");
|
|
559
306
|
return {
|
|
560
307
|
data: response,
|
|
561
308
|
error: null
|
|
@@ -613,13 +360,8 @@ var Auth = class {
|
|
|
613
360
|
*/
|
|
614
361
|
async signOut() {
|
|
615
362
|
try {
|
|
616
|
-
try {
|
|
617
|
-
await this.http.post("/api/auth/logout");
|
|
618
|
-
} catch {
|
|
619
|
-
}
|
|
620
363
|
this.tokenManager.clearSession();
|
|
621
364
|
this.http.setAuthToken(null);
|
|
622
|
-
clearAuthCookie();
|
|
623
365
|
return { error: null };
|
|
624
366
|
} catch (error) {
|
|
625
367
|
return {
|
|
@@ -680,14 +422,9 @@ var Auth = class {
|
|
|
680
422
|
}
|
|
681
423
|
this.http.setAuthToken(session.accessToken);
|
|
682
424
|
const authResponse = await this.http.get("/api/auth/sessions/current");
|
|
683
|
-
const { data: profile, error: profileError } = await this.database.from("users").select("*").eq("id", authResponse.user.id).single();
|
|
684
|
-
if (profileError && profileError.code !== "PGRST116") {
|
|
685
|
-
return { data: null, error: profileError };
|
|
686
|
-
}
|
|
687
425
|
return {
|
|
688
426
|
data: {
|
|
689
|
-
user: authResponse.user
|
|
690
|
-
profile: profile ? convertDbProfileToCamelCase(profile) : null
|
|
427
|
+
user: authResponse.user
|
|
691
428
|
},
|
|
692
429
|
error: null
|
|
693
430
|
};
|
|
@@ -711,17 +448,28 @@ var Auth = class {
|
|
|
711
448
|
}
|
|
712
449
|
/**
|
|
713
450
|
* Get any user's profile by ID
|
|
714
|
-
* Returns profile information from the users table
|
|
451
|
+
* Returns profile information from the users table
|
|
715
452
|
*/
|
|
716
453
|
async getProfile(userId) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
return {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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
|
+
};
|
|
723
472
|
}
|
|
724
|
-
return { data: null, error };
|
|
725
473
|
}
|
|
726
474
|
/**
|
|
727
475
|
* Get the current session (only session data, no API call)
|
|
@@ -752,42 +500,31 @@ var Auth = class {
|
|
|
752
500
|
/**
|
|
753
501
|
* Set/Update the current user's profile
|
|
754
502
|
* Updates profile information in the users table (supports any dynamic fields)
|
|
503
|
+
* Requires authentication
|
|
755
504
|
*/
|
|
756
505
|
async setProfile(profile) {
|
|
757
|
-
|
|
758
|
-
|
|
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
|
+
}
|
|
759
519
|
return {
|
|
760
520
|
data: null,
|
|
761
521
|
error: new InsForgeError(
|
|
762
|
-
"
|
|
763
|
-
|
|
764
|
-
"
|
|
522
|
+
"An unexpected error occurred while updating user profile",
|
|
523
|
+
500,
|
|
524
|
+
"UNEXPECTED_ERROR"
|
|
765
525
|
)
|
|
766
526
|
};
|
|
767
527
|
}
|
|
768
|
-
if (!session.user?.id) {
|
|
769
|
-
const { data: data2, error: error2 } = await this.getCurrentUser();
|
|
770
|
-
if (error2) {
|
|
771
|
-
return { data: null, error: error2 };
|
|
772
|
-
}
|
|
773
|
-
if (data2?.user) {
|
|
774
|
-
session.user = {
|
|
775
|
-
id: data2.user.id,
|
|
776
|
-
email: data2.user.email,
|
|
777
|
-
name: "",
|
|
778
|
-
emailVerified: false,
|
|
779
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
780
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
781
|
-
};
|
|
782
|
-
this.tokenManager.saveSession(session);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
const dbProfile = convertCamelCaseToDbProfile(profile);
|
|
786
|
-
const { data, error } = await this.database.from("users").update(dbProfile).eq("id", session.user.id).select().single();
|
|
787
|
-
if (data) {
|
|
788
|
-
return { data: convertDbProfileToCamelCase(data), error: null };
|
|
789
|
-
}
|
|
790
|
-
return { data: null, error };
|
|
791
528
|
}
|
|
792
529
|
/**
|
|
793
530
|
* Send email verification (code or link based on config)
|
|
@@ -941,14 +678,13 @@ var Auth = class {
|
|
|
941
678
|
"/api/auth/email/verify",
|
|
942
679
|
request
|
|
943
680
|
);
|
|
944
|
-
if (response.accessToken
|
|
681
|
+
if (response.accessToken) {
|
|
945
682
|
const session = {
|
|
946
683
|
accessToken: response.accessToken,
|
|
947
684
|
user: response.user || {}
|
|
948
685
|
};
|
|
949
686
|
this.tokenManager.saveSession(session);
|
|
950
687
|
this.http.setAuthToken(response.accessToken);
|
|
951
|
-
setAuthCookie();
|
|
952
688
|
}
|
|
953
689
|
return {
|
|
954
690
|
data: response,
|
|
@@ -970,6 +706,75 @@ var Auth = class {
|
|
|
970
706
|
}
|
|
971
707
|
};
|
|
972
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
|
+
|
|
973
778
|
// src/modules/storage.ts
|
|
974
779
|
var StorageBucket = class {
|
|
975
780
|
constructor(bucketName, http) {
|
|
@@ -1491,6 +1296,239 @@ var Functions = class {
|
|
|
1491
1296
|
}
|
|
1492
1297
|
};
|
|
1493
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
|
+
|
|
1494
1532
|
// src/client.ts
|
|
1495
1533
|
var InsForgeClient = class {
|
|
1496
1534
|
constructor(config = {}) {
|
|
@@ -1508,11 +1546,15 @@ var InsForgeClient = class {
|
|
|
1508
1546
|
if (existingSession?.accessToken) {
|
|
1509
1547
|
this.http.setAuthToken(existingSession.accessToken);
|
|
1510
1548
|
}
|
|
1511
|
-
this.auth = new Auth(
|
|
1549
|
+
this.auth = new Auth(
|
|
1550
|
+
this.http,
|
|
1551
|
+
this.tokenManager
|
|
1552
|
+
);
|
|
1512
1553
|
this.database = new Database(this.http, this.tokenManager);
|
|
1513
1554
|
this.storage = new Storage(this.http);
|
|
1514
1555
|
this.ai = new AI(this.http);
|
|
1515
1556
|
this.functions = new Functions(this.http);
|
|
1557
|
+
this.realtime = new Realtime(this.http.baseUrl, this.tokenManager);
|
|
1516
1558
|
}
|
|
1517
1559
|
/**
|
|
1518
1560
|
* Get the underlying HTTP client for custom requests
|
|
@@ -1549,6 +1591,7 @@ export {
|
|
|
1549
1591
|
HttpClient,
|
|
1550
1592
|
InsForgeClient,
|
|
1551
1593
|
InsForgeError,
|
|
1594
|
+
Realtime,
|
|
1552
1595
|
Storage,
|
|
1553
1596
|
StorageBucket,
|
|
1554
1597
|
TokenManager,
|