@resultdev/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,20 +1,17 @@
1
- import {
2
- ERROR_CODES,
3
- oAuthProvidersSchema
4
- } from "./chunk-Y7VCTXPN.js";
5
-
6
- // ../../node_modules/.bun/@insforge+sdk@1.4.4/node_modules/@insforge/sdk/dist/index.mjs
7
- import { PostgrestClient } from "@supabase/postgrest-js";
8
- var InsForgeError = class _InsForgeError extends Error {
1
+ // src/types.ts
2
+ var ResultError = class _ResultError extends Error {
3
+ statusCode;
4
+ error;
5
+ nextActions;
9
6
  constructor(message, statusCode, error, nextActions) {
10
7
  super(message);
11
- this.name = "InsForgeError";
8
+ this.name = "ResultError";
12
9
  this.statusCode = statusCode;
13
10
  this.error = error;
14
11
  this.nextActions = nextActions;
15
12
  }
16
13
  static fromApiError(apiError) {
17
- return new _InsForgeError(
14
+ return new _ResultError(
18
15
  apiError.message,
19
16
  apiError.statusCode,
20
17
  apiError.error,
@@ -22,7 +19,54 @@ var InsForgeError = class _InsForgeError extends Error {
22
19
  );
23
20
  }
24
21
  };
25
- var SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie", "set-cookie"];
22
+
23
+ // src/lib/jwt.ts
24
+ function decodeBase64Url(input) {
25
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
26
+ const padded = normalized.padEnd(
27
+ normalized.length + (4 - normalized.length % 4) % 4,
28
+ "="
29
+ );
30
+ const binary = atob(padded);
31
+ const bytes2 = Uint8Array.from(binary, (char) => char.charCodeAt(0));
32
+ return new TextDecoder().decode(bytes2);
33
+ }
34
+ function getJwtExpiration(token) {
35
+ if (!token) {
36
+ return null;
37
+ }
38
+ const [, payload] = token.split(".");
39
+ if (!payload) {
40
+ return null;
41
+ }
42
+ try {
43
+ const parsed = JSON.parse(decodeBase64Url(payload));
44
+ if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
45
+ return null;
46
+ }
47
+ return new Date(parsed.exp * 1e3);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
53
+ if (!token) {
54
+ return false;
55
+ }
56
+ const expires = getJwtExpiration(token);
57
+ if (!expires) {
58
+ return true;
59
+ }
60
+ return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
61
+ }
62
+
63
+ // src/lib/logger.ts
64
+ var SENSITIVE_HEADERS = [
65
+ "authorization",
66
+ "x-api-key",
67
+ "cookie",
68
+ "set-cookie"
69
+ ];
26
70
  var SENSITIVE_BODY_KEYS = [
27
71
  "password",
28
72
  "token",
@@ -97,6 +141,9 @@ function formatBody(body) {
97
141
  }
98
142
  }
99
143
  var Logger = class {
144
+ /** Whether debug logging is currently enabled */
145
+ enabled;
146
+ customLog;
100
147
  /**
101
148
  * Creates a new Logger instance.
102
149
  * @param debug - Set to true to enable console logging, or pass a custom log function
@@ -119,7 +166,7 @@ var Logger = class {
119
166
  if (!this.enabled) {
120
167
  return;
121
168
  }
122
- const formatted = `[InsForge Debug] ${message}`;
169
+ const formatted = `[Result Debug] ${message}`;
123
170
  if (this.customLog) {
124
171
  this.customLog(formatted, ...args);
125
172
  } else {
@@ -135,7 +182,7 @@ var Logger = class {
135
182
  if (!this.enabled) {
136
183
  return;
137
184
  }
138
- const formatted = `[InsForge Debug] ${message}`;
185
+ const formatted = `[Result Debug] ${message}`;
139
186
  if (this.customLog) {
140
187
  this.customLog(formatted, ...args);
141
188
  } else {
@@ -151,7 +198,7 @@ var Logger = class {
151
198
  if (!this.enabled) {
152
199
  return;
153
200
  }
154
- const formatted = `[InsForge Debug] ${message}`;
201
+ const formatted = `[Result Debug] ${message}`;
155
202
  if (this.customLog) {
156
203
  this.customLog(formatted, ...args);
157
204
  } else {
@@ -176,7 +223,7 @@ var Logger = class {
176
223
  }
177
224
  const formattedBody = formatBody(sanitizeBody(body));
178
225
  if (formattedBody) {
179
- const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
226
+ const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
180
227
  parts.push(` Body: ${truncated}`);
181
228
  }
182
229
  this.log(parts.join("\n"));
@@ -197,7 +244,7 @@ var Logger = class {
197
244
  const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
198
245
  const formattedBody = formatBody(sanitizeBody(body));
199
246
  if (formattedBody) {
200
- const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
247
+ const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
201
248
  parts.push(` Body: ${truncated}`);
202
249
  }
203
250
  if (status >= 400) {
@@ -207,12 +254,14 @@ var Logger = class {
207
254
  }
208
255
  }
209
256
  };
257
+
258
+ // src/lib/token-manager.ts
210
259
  var AuthChangeEvent = {
211
260
  SIGNED_IN: "signedIn",
212
261
  SIGNED_OUT: "signedOut",
213
262
  TOKEN_REFRESHED: "tokenRefreshed"
214
263
  };
215
- var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
264
+ var CSRF_TOKEN_COOKIE = "result_csrf_token";
216
265
  function getCsrfToken() {
217
266
  if (typeof document === "undefined") {
218
267
  return null;
@@ -239,11 +288,10 @@ function clearCsrfToken() {
239
288
  document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
240
289
  }
241
290
  var TokenManager = class {
242
- constructor() {
243
- this.accessToken = null;
244
- this.user = null;
245
- this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
246
- }
291
+ // In-memory storage
292
+ accessToken = null;
293
+ user = null;
294
+ authStateChangeCallbacks = /* @__PURE__ */ new Map();
247
295
  /**
248
296
  * Save session in memory
249
297
  */
@@ -321,41 +369,8 @@ var TokenManager = class {
321
369
  }
322
370
  }
323
371
  };
324
- function decodeBase64Url(input) {
325
- const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
326
- const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
327
- const binary = atob(padded);
328
- const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
329
- return new TextDecoder().decode(bytes);
330
- }
331
- function getJwtExpiration(token) {
332
- if (!token) {
333
- return null;
334
- }
335
- const [, payload] = token.split(".");
336
- if (!payload) {
337
- return null;
338
- }
339
- try {
340
- const parsed = JSON.parse(decodeBase64Url(payload));
341
- if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
342
- return null;
343
- }
344
- return new Date(parsed.exp * 1e3);
345
- } catch {
346
- return null;
347
- }
348
- }
349
- function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
350
- if (!token) {
351
- return false;
352
- }
353
- const expires = getJwtExpiration(token);
354
- if (!expires) {
355
- return true;
356
- }
357
- return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
358
- }
372
+
373
+ // src/lib/http-client.ts
359
374
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
360
375
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
361
376
  var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
@@ -385,7 +400,7 @@ async function parseResponse(response) {
385
400
  data = await response.text();
386
401
  }
387
402
  } catch (parseErr) {
388
- throw new InsForgeError(
403
+ throw new ResultError(
389
404
  `Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
390
405
  response.status,
391
406
  response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
@@ -393,8 +408,8 @@ async function parseResponse(response) {
393
408
  }
394
409
  if (!response.ok) {
395
410
  if (data && typeof data === "object" && "error" in data) {
396
- data.statusCode ?? (data.statusCode = data.status ?? response.status);
397
- const error = InsForgeError.fromApiError(data);
411
+ data.statusCode ??= data.status ?? response.status;
412
+ const error = ResultError.fromApiError(data);
398
413
  Object.keys(data).forEach((key) => {
399
414
  if (key !== "error" && key !== "message" && key !== "statusCode") {
400
415
  error[key] = data[key];
@@ -402,7 +417,7 @@ async function parseResponse(response) {
402
417
  });
403
418
  throw error;
404
419
  }
405
- throw new InsForgeError(
420
+ throw new ResultError(
406
421
  `Request failed: ${response.statusText}`,
407
422
  response.status,
408
423
  "REQUEST_FAILED"
@@ -411,6 +426,20 @@ async function parseResponse(response) {
411
426
  return data;
412
427
  }
413
428
  var HttpClient = class {
429
+ baseUrl;
430
+ fetch;
431
+ config;
432
+ defaultHeaders;
433
+ anonKey;
434
+ userToken = null;
435
+ logger;
436
+ isRefreshing = false;
437
+ refreshPromise = null;
438
+ tokenManager;
439
+ refreshToken = null;
440
+ timeout;
441
+ retryCount;
442
+ retryDelay;
414
443
  /**
415
444
  * Creates a new HttpClient instance.
416
445
  * @param config - SDK configuration including baseUrl, timeout, retry settings, and fetch implementation.
@@ -418,10 +447,6 @@ var HttpClient = class {
418
447
  * @param logger - Optional logger instance for request/response debugging.
419
448
  */
420
449
  constructor(config, tokenManager, logger) {
421
- this.userToken = null;
422
- this.isRefreshing = false;
423
- this.refreshPromise = null;
424
- this.refreshToken = null;
425
450
  this.config = config;
426
451
  this.baseUrl = config.baseUrl || "http://localhost:7130";
427
452
  this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
@@ -469,7 +494,7 @@ var HttpClient = class {
469
494
  * @returns Delay in milliseconds.
470
495
  */
471
496
  computeRetryDelay(attempt) {
472
- const base = this.retryDelay * Math.pow(2, attempt - 1);
497
+ const base = this.retryDelay * 2 ** (attempt - 1);
473
498
  const jitter = base * (0.85 + Math.random() * 0.3);
474
499
  return Math.round(jitter);
475
500
  }
@@ -477,19 +502,29 @@ var HttpClient = class {
477
502
  return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
478
503
  }
479
504
  async fetchWithRetry(args) {
480
- const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
505
+ const {
506
+ method,
507
+ url,
508
+ headers,
509
+ body,
510
+ fetchOptions,
511
+ callerSignal,
512
+ maxAttempts
513
+ } = args;
481
514
  let lastError;
482
515
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
483
516
  if (attempt > 0) {
484
517
  const delay = this.computeRetryDelay(attempt);
485
- this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
518
+ this.logger.warn(
519
+ `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
520
+ );
486
521
  if (callerSignal?.aborted) {
487
522
  throw callerSignal.reason;
488
523
  }
489
524
  await new Promise((resolve, reject) => {
490
525
  const onAbort = () => {
491
526
  clearTimeout(timer2);
492
- reject(callerSignal.reason);
527
+ reject(callerSignal?.reason);
493
528
  };
494
529
  const timer2 = setTimeout(() => {
495
530
  if (callerSignal) {
@@ -507,13 +542,13 @@ var HttpClient = class {
507
542
  if (this.timeout > 0 || callerSignal) {
508
543
  controller = new AbortController();
509
544
  if (this.timeout > 0) {
510
- timer = setTimeout(() => controller.abort(), this.timeout);
545
+ timer = setTimeout(() => controller?.abort(), this.timeout);
511
546
  }
512
547
  if (callerSignal) {
513
548
  if (callerSignal.aborted) {
514
549
  controller.abort(callerSignal.reason);
515
550
  } else {
516
- const onCallerAbort = () => controller.abort(callerSignal.reason);
551
+ const onCallerAbort = () => controller?.abort(callerSignal.reason);
517
552
  callerSignal.addEventListener("abort", onCallerAbort, {
518
553
  once: true
519
554
  });
@@ -540,7 +575,7 @@ var HttpClient = class {
540
575
  clearTimeout(timer);
541
576
  }
542
577
  await response.body?.cancel();
543
- lastError = new InsForgeError(
578
+ lastError = new ResultError(
544
579
  `Server error: ${response.status} ${response.statusText}`,
545
580
  response.status,
546
581
  "SERVER_ERROR"
@@ -556,8 +591,8 @@ var HttpClient = class {
556
591
  clearTimeout(timer);
557
592
  }
558
593
  if (err?.name === "AbortError") {
559
- if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
560
- throw new InsForgeError(
594
+ if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
595
+ throw new ResultError(
561
596
  `Request timed out after ${this.timeout}ms`,
562
597
  408,
563
598
  "REQUEST_TIMEOUT"
@@ -569,14 +604,18 @@ var HttpClient = class {
569
604
  lastError = err;
570
605
  continue;
571
606
  }
572
- throw new InsForgeError(
607
+ throw new ResultError(
573
608
  `Network request failed: ${err?.message || "Unknown error"}`,
574
609
  0,
575
610
  "NETWORK_ERROR"
576
611
  );
577
612
  }
578
613
  }
579
- throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
614
+ throw lastError || new ResultError(
615
+ "Request failed after all retry attempts",
616
+ 0,
617
+ "NETWORK_ERROR"
618
+ );
580
619
  }
581
620
  /**
582
621
  * Performs an HTTP request with automatic retry and timeout handling.
@@ -586,7 +625,7 @@ var HttpClient = class {
586
625
  * @param path - API path relative to the base URL.
587
626
  * @param options - Optional request configuration including headers, body, and query params.
588
627
  * @returns Parsed response data.
589
- * @throws {InsForgeError} On timeout, network failure, or HTTP error responses.
628
+ * @throws {ResultError} On timeout, network failure, or HTTP error responses.
590
629
  */
591
630
  async handleRequest(method, path, options = {}, tokenOverride) {
592
631
  const {
@@ -606,14 +645,14 @@ var HttpClient = class {
606
645
  };
607
646
  const authToken = tokenOverride ?? this.userToken ?? this.anonKey;
608
647
  if (authToken) {
609
- requestHeaders["Authorization"] = `Bearer ${authToken}`;
648
+ requestHeaders.Authorization = `Bearer ${authToken}`;
610
649
  }
611
650
  const processedBody = serializeBody(method, body, requestHeaders);
612
651
  const setRequestHeader = (key, value) => {
613
652
  if (key.toLowerCase() === "authorization") {
614
- delete requestHeaders["Authorization"];
615
- delete requestHeaders["authorization"];
616
- requestHeaders["Authorization"] = value;
653
+ delete requestHeaders.Authorization;
654
+ delete requestHeaders.authorization;
655
+ requestHeaders.Authorization = value;
617
656
  return;
618
657
  }
619
658
  requestHeaders[key] = value;
@@ -645,7 +684,7 @@ var HttpClient = class {
645
684
  try {
646
685
  data = await parseResponse(response);
647
686
  } catch (err) {
648
- if (err instanceof InsForgeError) {
687
+ if (err instanceof ResultError) {
649
688
  this.logger.logResponse(
650
689
  method,
651
690
  url,
@@ -656,15 +695,31 @@ var HttpClient = class {
656
695
  }
657
696
  throw err;
658
697
  }
659
- this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
698
+ this.logger.logResponse(
699
+ method,
700
+ url,
701
+ response.status,
702
+ Date.now() - startTime,
703
+ data
704
+ );
660
705
  return data;
661
706
  }
662
707
  async request(method, path, options = {}) {
663
708
  const tokenUsed = this.userToken;
664
709
  try {
665
- return await this.handleRequest(method, path, { ...options }, tokenUsed);
710
+ return await this.handleRequest(
711
+ method,
712
+ path,
713
+ { ...options },
714
+ tokenUsed
715
+ );
666
716
  } catch (error) {
667
- if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
717
+ if (!(error instanceof ResultError) || !this.shouldRefreshAccessToken(
718
+ error.statusCode,
719
+ error.error,
720
+ tokenUsed,
721
+ options
722
+ )) {
668
723
  throw error;
669
724
  }
670
725
  if (tokenUsed !== this.userToken) {
@@ -684,7 +739,7 @@ var HttpClient = class {
684
739
  try {
685
740
  await this.refreshAndSaveSession();
686
741
  } catch (error2) {
687
- if (error2 instanceof InsForgeError && (error2.statusCode === 401 || error2.statusCode === 403)) {
742
+ if (error2 instanceof ResultError && (error2.statusCode === 401 || error2.statusCode === 403)) {
688
743
  this.clearAuthSession();
689
744
  }
690
745
  throw error2;
@@ -749,7 +804,12 @@ var HttpClient = class {
749
804
  callerSignal,
750
805
  maxAttempts
751
806
  });
752
- this.logger.logResponse(method, url, response.status, Date.now() - startTime);
807
+ this.logger.logResponse(
808
+ method,
809
+ url,
810
+ response.status,
811
+ Date.now() - startTime
812
+ );
753
813
  let errorCode = null;
754
814
  if (response.status === 401) {
755
815
  try {
@@ -763,7 +823,12 @@ var HttpClient = class {
763
823
  } catch {
764
824
  }
765
825
  }
766
- if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
826
+ if (!this.shouldRefreshAccessToken(
827
+ response.status,
828
+ errorCode,
829
+ tokenUsed,
830
+ options
831
+ )) {
767
832
  return response;
768
833
  }
769
834
  if (tokenUsed !== this.userToken) {
@@ -782,7 +847,7 @@ var HttpClient = class {
782
847
  try {
783
848
  newTokenData = await this.refreshAndSaveSession();
784
849
  } catch (error) {
785
- if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403)) {
850
+ if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403)) {
786
851
  this.clearAuthSession();
787
852
  }
788
853
  throw error;
@@ -827,7 +892,7 @@ var HttpClient = class {
827
892
  const headers = { ...this.defaultHeaders };
828
893
  const authToken = this.userToken || this.anonKey;
829
894
  if (authToken) {
830
- headers["Authorization"] = `Bearer ${authToken}`;
895
+ headers.Authorization = `Bearer ${authToken}`;
831
896
  }
832
897
  return headers;
833
898
  }
@@ -871,7 +936,7 @@ var HttpClient = class {
871
936
  const refreshed = await this.refreshAndSaveSession();
872
937
  return refreshed.accessToken;
873
938
  } catch (error) {
874
- if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
939
+ if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
875
940
  this.clearAuthSession();
876
941
  }
877
942
  throw error;
@@ -880,7 +945,10 @@ var HttpClient = class {
880
945
  async refreshAndSaveSession() {
881
946
  const newTokenData = await this.refreshAccessToken();
882
947
  this.setAuthToken(newTokenData.accessToken);
883
- this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
948
+ this.tokenManager.saveSession(
949
+ newTokenData,
950
+ AuthChangeEvent.TOKEN_REFRESHED
951
+ );
884
952
  if (newTokenData.csrfToken) {
885
953
  setCsrfToken(newTokenData.csrfToken);
886
954
  }
@@ -896,1328 +964,1404 @@ var HttpClient = class {
896
964
  clearCsrfToken();
897
965
  }
898
966
  };
899
- var PKCE_VERIFIER_KEY = "insforge_pkce_verifier";
900
- async function getWebCrypto() {
901
- const webCrypto = globalThis.crypto;
902
- if (typeof webCrypto?.getRandomValues === "function" && webCrypto.subtle) {
903
- return webCrypto;
904
- }
905
- if (typeof process !== "undefined" && process.versions?.node) {
906
- const { webcrypto } = await import("crypto");
907
- return webcrypto;
908
- }
909
- throw new Error("Web Crypto API is not available in this environment");
910
- }
911
- function base64UrlEncode(buffer) {
912
- const base64 = btoa(String.fromCharCode(...buffer));
913
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
914
- }
915
- async function generateCodeVerifier() {
916
- const webCrypto = await getWebCrypto();
917
- const array = new Uint8Array(32);
918
- webCrypto.getRandomValues(array);
919
- return base64UrlEncode(array);
920
- }
921
- async function generateCodeChallenge(verifier) {
922
- const webCrypto = await getWebCrypto();
923
- const encoder = new TextEncoder();
924
- const data = encoder.encode(verifier);
925
- const hash = await webCrypto.subtle.digest("SHA-256", data);
926
- return base64UrlEncode(new Uint8Array(hash));
927
- }
928
- function storePkceVerifier(verifier) {
929
- if (typeof sessionStorage !== "undefined") {
930
- sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
931
- }
932
- }
933
- function retrievePkceVerifier() {
934
- if (typeof sessionStorage === "undefined") {
935
- return null;
936
- }
937
- const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
938
- if (verifier) {
939
- sessionStorage.removeItem(PKCE_VERIFIER_KEY);
940
- }
941
- return verifier;
942
- }
943
- function wrapError(error, fallbackMessage) {
944
- if (error instanceof InsForgeError) {
945
- return { data: null, error };
967
+
968
+ // src/modules/ai.ts
969
+ var AI = class {
970
+ chat;
971
+ images;
972
+ embeddings;
973
+ constructor(http) {
974
+ this.chat = new Chat(http);
975
+ this.images = new Images(http);
976
+ this.embeddings = new Embeddings(http);
946
977
  }
947
- return {
948
- data: null,
949
- error: new InsForgeError(
950
- error instanceof Error ? error.message : fallbackMessage,
951
- 500,
952
- "UNEXPECTED_ERROR"
953
- )
954
- };
955
- }
956
- function cleanUrlParams(...params) {
957
- if (typeof window === "undefined") {
958
- return;
978
+ };
979
+ var Chat = class {
980
+ completions;
981
+ constructor(http) {
982
+ this.completions = new ChatCompletions(http);
959
983
  }
960
- const url = new URL(window.location.href);
961
- params.forEach((p) => url.searchParams.delete(p));
962
- window.history.replaceState({}, document.title, url.toString());
963
- }
964
- var Auth = class {
965
- constructor(http, tokenManager, options = {}) {
984
+ };
985
+ var ChatCompletions = class {
986
+ constructor(http) {
966
987
  this.http = http;
967
- this.tokenManager = tokenManager;
968
- this.options = options;
969
- this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
970
988
  }
971
- isServerMode() {
972
- return !!this.options.isServerMode;
973
- }
974
- /** Subscribe to SDK authentication state changes. */
975
- onAuthStateChange(callback) {
976
- return this.tokenManager.onAuthStateChange(callback);
989
+ http;
990
+ async create(params) {
991
+ const backendParams = {
992
+ model: params.model,
993
+ messages: params.messages,
994
+ temperature: params.temperature,
995
+ maxTokens: params.maxTokens,
996
+ topP: params.topP,
997
+ stream: params.stream,
998
+ // New plugin options
999
+ webSearch: params.webSearch,
1000
+ fileParser: params.fileParser,
1001
+ thinking: params.thinking,
1002
+ // Tool calling options
1003
+ tools: params.tools,
1004
+ toolChoice: params.toolChoice,
1005
+ parallelToolCalls: params.parallelToolCalls
1006
+ };
1007
+ if (params.stream) {
1008
+ const headers = this.http.getHeaders();
1009
+ headers["Content-Type"] = "application/json";
1010
+ const response2 = await this.http.fetch(
1011
+ `${this.http.baseUrl}/api/ai/chat/completion`,
1012
+ {
1013
+ method: "POST",
1014
+ headers,
1015
+ body: JSON.stringify(backendParams)
1016
+ }
1017
+ );
1018
+ if (!response2.ok) {
1019
+ const error = await response2.json();
1020
+ throw new Error(error.error || "Stream request failed");
1021
+ }
1022
+ return this.parseSSEStream(response2, params.model);
1023
+ }
1024
+ const response = await this.http.post(
1025
+ "/api/ai/chat/completion",
1026
+ backendParams
1027
+ );
1028
+ const content = response.text || "";
1029
+ return {
1030
+ id: `chatcmpl-${Date.now()}`,
1031
+ object: "chat.completion",
1032
+ created: Math.floor(Date.now() / 1e3),
1033
+ model: response.metadata?.model,
1034
+ choices: [
1035
+ {
1036
+ index: 0,
1037
+ message: {
1038
+ role: "assistant",
1039
+ content,
1040
+ // Include tool_calls if present (from tool calling)
1041
+ ...response.tool_calls?.length && {
1042
+ tool_calls: response.tool_calls
1043
+ },
1044
+ // Include annotations if present (from web search or file parsing)
1045
+ ...response.annotations?.length && {
1046
+ annotations: response.annotations
1047
+ }
1048
+ },
1049
+ finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
1050
+ }
1051
+ ],
1052
+ usage: {
1053
+ prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1054
+ completion_tokens: response.metadata?.usage?.completionTokens || 0,
1055
+ total_tokens: response.metadata?.usage?.totalTokens || 0
1056
+ }
1057
+ };
977
1058
  }
978
1059
  /**
979
- * Save session from API response
980
- * Handles token storage, CSRF token, and HTTP auth header
1060
+ * Parse SSE stream into async iterable of OpenAI-like chunks
981
1061
  */
982
- saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
983
- if (!response.accessToken || !response.user) {
984
- return false;
985
- }
986
- const session = {
987
- accessToken: response.accessToken,
988
- user: response.user
989
- };
990
- if (!this.isServerMode() && response.csrfToken) {
991
- setCsrfToken(response.csrfToken);
1062
+ async *parseSSEStream(response, model) {
1063
+ const reader = response.body?.getReader();
1064
+ if (!reader) {
1065
+ throw new ResultError(
1066
+ "Streaming response has no body",
1067
+ 500,
1068
+ "AI_UPSTREAM_UNAVAILABLE"
1069
+ );
992
1070
  }
993
- if (!this.isServerMode()) {
994
- this.tokenManager.saveSession(session, event);
1071
+ const decoder = new TextDecoder();
1072
+ let buffer = "";
1073
+ try {
1074
+ while (true) {
1075
+ const { done, value } = await reader.read();
1076
+ if (done) {
1077
+ break;
1078
+ }
1079
+ buffer += decoder.decode(value, { stream: true });
1080
+ const lines = buffer.split("\n");
1081
+ buffer = lines.pop() || "";
1082
+ for (const line of lines) {
1083
+ if (line.startsWith("data: ")) {
1084
+ const dataStr = line.slice(6).trim();
1085
+ if (dataStr) {
1086
+ try {
1087
+ const data = JSON.parse(dataStr);
1088
+ if (data.chunk || data.content) {
1089
+ yield {
1090
+ id: `chatcmpl-${Date.now()}`,
1091
+ object: "chat.completion.chunk",
1092
+ created: Math.floor(Date.now() / 1e3),
1093
+ model,
1094
+ choices: [
1095
+ {
1096
+ index: 0,
1097
+ delta: {
1098
+ content: data.chunk || data.content
1099
+ },
1100
+ finish_reason: null
1101
+ }
1102
+ ]
1103
+ };
1104
+ }
1105
+ if (data.tool_calls?.length) {
1106
+ yield {
1107
+ id: `chatcmpl-${Date.now()}`,
1108
+ object: "chat.completion.chunk",
1109
+ created: Math.floor(Date.now() / 1e3),
1110
+ model,
1111
+ choices: [
1112
+ {
1113
+ index: 0,
1114
+ delta: {
1115
+ tool_calls: data.tool_calls
1116
+ },
1117
+ finish_reason: "tool_calls"
1118
+ }
1119
+ ]
1120
+ };
1121
+ }
1122
+ if (data.done) {
1123
+ reader.releaseLock();
1124
+ return;
1125
+ }
1126
+ } catch {
1127
+ console.warn("Failed to parse SSE data:", dataStr);
1128
+ }
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+ } finally {
1134
+ reader.releaseLock();
995
1135
  }
996
- this.http.setAuthToken(response.accessToken);
997
- this.http.setRefreshToken(response.refreshToken ?? null);
998
- return true;
999
1136
  }
1000
- // ============================================================================
1001
- // OAuth Callback Detection (runs on initialization)
1002
- // ============================================================================
1137
+ };
1138
+ var Embeddings = class {
1139
+ constructor(http) {
1140
+ this.http = http;
1141
+ }
1142
+ http;
1003
1143
  /**
1004
- * Detect and handle OAuth callback parameters in URL
1005
- * Supports PKCE flow (insforge_code)
1144
+ * Create embeddings for text input - OpenAI-like response format
1145
+ *
1146
+ * @example
1147
+ * ```typescript
1148
+ * // Single text input
1149
+ * const response = await client.ai.embeddings.create({
1150
+ * model: 'openai/text-embedding-3-small',
1151
+ * input: 'Hello world'
1152
+ * });
1153
+ * console.log(response.data[0].embedding); // number[]
1154
+ *
1155
+ * // Multiple text inputs
1156
+ * const response = await client.ai.embeddings.create({
1157
+ * model: 'openai/text-embedding-3-small',
1158
+ * input: ['Hello world', 'Goodbye world']
1159
+ * });
1160
+ * response.data.forEach((item, i) => {
1161
+ * console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
1162
+ * });
1163
+ *
1164
+ * // With custom dimensions (if supported by model)
1165
+ * const response = await client.ai.embeddings.create({
1166
+ * model: 'openai/text-embedding-3-small',
1167
+ * input: 'Hello world',
1168
+ * dimensions: 256
1169
+ * });
1170
+ *
1171
+ * // With base64 encoding format
1172
+ * const response = await client.ai.embeddings.create({
1173
+ * model: 'openai/text-embedding-3-small',
1174
+ * input: 'Hello world',
1175
+ * encoding_format: 'base64'
1176
+ * });
1177
+ * ```
1006
1178
  */
1007
- async detectAuthCallback() {
1008
- if (this.isServerMode() || typeof window === "undefined") {
1009
- return;
1010
- }
1011
- try {
1012
- const params = new URLSearchParams(window.location.search);
1013
- const error = params.get("error");
1014
- if (error) {
1015
- cleanUrlParams("error");
1016
- console.debug("OAuth callback error:", error);
1017
- return;
1179
+ async create(params) {
1180
+ const response = await this.http.post(
1181
+ "/api/ai/embeddings",
1182
+ params
1183
+ );
1184
+ return {
1185
+ object: response.object,
1186
+ data: response.data,
1187
+ model: response.metadata?.model,
1188
+ usage: response.metadata?.usage ? {
1189
+ prompt_tokens: response.metadata.usage.promptTokens || 0,
1190
+ total_tokens: response.metadata.usage.totalTokens || 0
1191
+ } : {
1192
+ prompt_tokens: 0,
1193
+ total_tokens: 0
1018
1194
  }
1019
- const code = params.get("insforge_code");
1020
- if (code) {
1021
- cleanUrlParams("insforge_code");
1022
- const { error: exchangeError } = await this.exchangeOAuthCode(code);
1023
- if (exchangeError) {
1024
- console.debug("OAuth code exchange failed:", exchangeError.message);
1195
+ };
1196
+ }
1197
+ };
1198
+ var Images = class {
1199
+ constructor(http) {
1200
+ this.http = http;
1201
+ }
1202
+ http;
1203
+ /**
1204
+ * Generate images - OpenAI-like response format
1205
+ *
1206
+ * @example
1207
+ * ```typescript
1208
+ * // Text-to-image
1209
+ * const response = await client.ai.images.generate({
1210
+ * model: 'dall-e-3',
1211
+ * prompt: 'A sunset over mountains',
1212
+ * });
1213
+ * console.log(response.data[0].b64_json);
1214
+ *
1215
+ * // Image-to-image (with input images)
1216
+ * const response = await client.ai.images.generate({
1217
+ * model: 'stable-diffusion-xl',
1218
+ * prompt: 'Transform this into a watercolor painting',
1219
+ * images: [
1220
+ * { url: 'https://example.com/input.jpg' },
1221
+ * // or base64-encoded Data URI:
1222
+ * { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
1223
+ * ]
1224
+ * });
1225
+ * ```
1226
+ */
1227
+ async generate(params) {
1228
+ const response = await this.http.post(
1229
+ "/api/ai/image/generation",
1230
+ params
1231
+ );
1232
+ let data = [];
1233
+ if (response.images && response.images.length > 0) {
1234
+ data = response.images.map((img) => ({
1235
+ b64_json: img.imageUrl.replace(/^data:image\/\w+;base64,/, ""),
1236
+ content: response.text
1237
+ }));
1238
+ } else if (response.text) {
1239
+ data = [{ content: response.text }];
1240
+ }
1241
+ return {
1242
+ created: Math.floor(Date.now() / 1e3),
1243
+ data,
1244
+ ...response.metadata?.usage && {
1245
+ usage: {
1246
+ total_tokens: response.metadata.usage.totalTokens || 0,
1247
+ input_tokens: response.metadata.usage.promptTokens || 0,
1248
+ output_tokens: response.metadata.usage.completionTokens || 0
1025
1249
  }
1026
- return;
1027
1250
  }
1028
- } catch (error) {
1029
- console.debug("OAuth callback detection skipped:", error);
1251
+ };
1252
+ }
1253
+ };
1254
+
1255
+ // src/modules/analytics.ts
1256
+ var DEFAULT_ENDPOINT = "https://beta.result.dev/api/wa";
1257
+ var IDLE_MS = 30 * 60 * 1e3;
1258
+ var Analytics = class {
1259
+ siteId;
1260
+ endpoint;
1261
+ options;
1262
+ active = false;
1263
+ lastUrl = null;
1264
+ left = false;
1265
+ referrer = "";
1266
+ // localStorage with an in-memory fallback for private-mode browsers.
1267
+ mem = {};
1268
+ constructor(options) {
1269
+ this.options = options ?? { siteId: "" };
1270
+ this.siteId = this.options.siteId ?? "";
1271
+ this.endpoint = this.options.endpoint || DEFAULT_ENDPOINT;
1272
+ if (!this.siteId || typeof window === "undefined" || navigator.webdriver) {
1273
+ return;
1274
+ }
1275
+ if (window.rwa) {
1276
+ return;
1277
+ }
1278
+ if (this.read("rwa_disable") === "1") {
1279
+ return;
1280
+ }
1281
+ const domains = (this.options.domains ?? []).map(
1282
+ (domain) => domain.trim().toLowerCase().replace(/^www\./, "")
1283
+ ).filter(Boolean);
1284
+ const here = location.hostname.toLowerCase().replace(/^www\./, "");
1285
+ if (domains.length && domains.indexOf(here) === -1) {
1286
+ return;
1287
+ }
1288
+ this.active = true;
1289
+ this.referrer = document.referrer || "";
1290
+ window.rwa = {
1291
+ track: (name, props) => this.track(name, props),
1292
+ identify: (id) => this.identify(id)
1293
+ };
1294
+ const doc = document;
1295
+ if (doc.prerendering) {
1296
+ document.addEventListener("prerenderingchange", () => this.start(), {
1297
+ once: true
1298
+ });
1299
+ } else {
1300
+ this.start();
1030
1301
  }
1031
1302
  }
1032
- // ============================================================================
1033
- // Sign Up / Sign In / Sign Out
1034
- // ============================================================================
1035
- async signUp(request) {
1036
- try {
1037
- const response = await this.http.post(
1038
- this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
1039
- request,
1040
- { credentials: "include", skipAuthRefresh: true }
1041
- );
1042
- if (response.accessToken && response.user) {
1043
- this.saveSessionFromResponse(response);
1044
- }
1045
- if (response.refreshToken) {
1046
- this.http.setRefreshToken(response.refreshToken);
1047
- }
1048
- return { data: response, error: null };
1049
- } catch (error) {
1050
- return wrapError(error, "An unexpected error occurred during sign up");
1303
+ /** Send a custom event. No-op outside the browser or when inactive. */
1304
+ track(name, props) {
1305
+ if (!this.active || typeof name !== "string" || !name) {
1306
+ return;
1051
1307
  }
1308
+ const event = {
1309
+ e: "ev",
1310
+ n: name.slice(0, 64),
1311
+ dt: document.title
1312
+ };
1313
+ if (props && typeof props === "object") {
1314
+ event.p = props;
1315
+ }
1316
+ this.post(event);
1052
1317
  }
1053
- async signInWithPassword(request) {
1318
+ /** Attach your own user id to subsequent events. */
1319
+ identify(id) {
1320
+ if (!this.active || typeof id !== "string" || !id) {
1321
+ return;
1322
+ }
1323
+ this.write("rwa_uid", id.slice(0, 64));
1324
+ }
1325
+ // -- internals -------------------------------------------------------------
1326
+ read(key) {
1054
1327
  try {
1055
- const response = await this.http.post(
1056
- this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
1057
- request,
1058
- { credentials: "include", skipAuthRefresh: true }
1059
- );
1060
- this.saveSessionFromResponse(response);
1061
- if (response.refreshToken) {
1062
- this.http.setRefreshToken(response.refreshToken);
1063
- }
1064
- return { data: response, error: null };
1065
- } catch (error) {
1066
- return wrapError(error, "An unexpected error occurred during sign in");
1328
+ return localStorage.getItem(key) || this.mem[key] || "";
1329
+ } catch {
1330
+ return this.mem[key] || "";
1067
1331
  }
1068
1332
  }
1069
- async signOut() {
1333
+ write(key, value) {
1334
+ this.mem[key] = value;
1070
1335
  try {
1071
- try {
1072
- const serverMode = this.isServerMode();
1073
- const csrfToken = !serverMode ? getCsrfToken() : null;
1074
- await this.http.post(
1075
- serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
1076
- void 0,
1077
- {
1078
- credentials: "include",
1079
- skipAuthRefresh: true,
1080
- ...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
1081
- }
1082
- );
1083
- } catch {
1084
- }
1085
- this.tokenManager.clearSession();
1086
- this.http.setAuthToken(null);
1087
- this.http.setRefreshToken(null);
1088
- if (!this.isServerMode()) {
1089
- clearCsrfToken();
1090
- }
1091
- return { error: null };
1336
+ localStorage.setItem(key, value);
1092
1337
  } catch {
1093
- return {
1094
- error: new InsForgeError("Failed to sign out", 500, "SIGNOUT_ERROR")
1095
- };
1096
1338
  }
1097
1339
  }
1098
- async signInWithOAuth(providerOrOptions, options) {
1099
- try {
1100
- let signInOptions;
1101
- if (typeof providerOrOptions === "object") {
1102
- signInOptions = providerOrOptions;
1103
- } else if (options) {
1104
- signInOptions = { provider: providerOrOptions, ...options };
1105
- } else {
1106
- return {
1107
- data: {},
1108
- error: new InsForgeError(
1109
- "OAuth sign-in options are required",
1110
- 400,
1111
- ERROR_CODES.INVALID_INPUT
1112
- )
1113
- };
1114
- }
1115
- if (!signInOptions || !signInOptions.redirectTo) {
1116
- return {
1117
- data: {},
1118
- error: new InsForgeError("Redirect URI is required", 400, ERROR_CODES.INVALID_INPUT)
1119
- };
1120
- }
1121
- const { provider } = signInOptions;
1122
- const providerKey = encodeURIComponent(provider.toLowerCase());
1123
- const codeVerifier = await generateCodeVerifier();
1124
- const codeChallenge = await generateCodeChallenge(codeVerifier);
1125
- storePkceVerifier(codeVerifier);
1126
- const params = {
1127
- ...signInOptions.additionalParams ?? {},
1128
- redirect_uri: signInOptions.redirectTo,
1129
- code_challenge: codeChallenge
1130
- };
1131
- const isBuiltInProvider = oAuthProvidersSchema.options.includes(
1132
- providerKey
1133
- );
1134
- const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
1135
- const response = await this.http.get(oauthPath, {
1136
- params,
1137
- skipAuthRefresh: true
1340
+ visitor() {
1341
+ let visitor = this.read("rwa_id");
1342
+ if (!visitor) {
1343
+ visitor = uuid4();
1344
+ this.write("rwa_id", visitor);
1345
+ }
1346
+ return visitor;
1347
+ }
1348
+ session() {
1349
+ const parts = this.read("rwa_s").split(".");
1350
+ let id = parts[0];
1351
+ const last = Number(parts[1]) || 0;
1352
+ const now = Date.now();
1353
+ if (!id || now - last > IDLE_MS) {
1354
+ id = uuid7();
1355
+ }
1356
+ this.write("rwa_s", `${id}.${now}`);
1357
+ return id;
1358
+ }
1359
+ pageUrl() {
1360
+ let url = location.href;
1361
+ if (this.options.excludeHash) {
1362
+ url = url.split("#")[0];
1363
+ }
1364
+ if (this.options.excludeSearch) {
1365
+ const hash = this.options.excludeHash ? "" : url.split("#")[1] || "";
1366
+ url = url.split("#")[0].split("?")[0] + (hash ? `#${hash}` : "");
1367
+ }
1368
+ return url.slice(0, 4096);
1369
+ }
1370
+ post(event) {
1371
+ event.u = event.u || this.pageUrl();
1372
+ event.d = this.visitor();
1373
+ event.s = this.session();
1374
+ event.t = Date.now();
1375
+ event.w = innerWidth;
1376
+ event.h = innerHeight;
1377
+ event.sw = screen.width;
1378
+ event.sh = screen.height;
1379
+ const uid = this.read("rwa_uid");
1380
+ if (uid) {
1381
+ event.uid = uid;
1382
+ }
1383
+ const body = JSON.stringify({ site: this.siteId, events: [event] });
1384
+ if (!navigator.sendBeacon?.(this.endpoint, body)) {
1385
+ fetch(this.endpoint, {
1386
+ method: "POST",
1387
+ body,
1388
+ keepalive: true,
1389
+ mode: "no-cors"
1390
+ }).catch(() => {
1138
1391
  });
1139
- if (!this.isServerMode() && typeof window !== "undefined" && !signInOptions.skipBrowserRedirect) {
1140
- window.location.href = response.authUrl;
1141
- return { data: {}, error: null };
1142
- }
1143
- return {
1144
- data: { url: response.authUrl, provider: providerKey, codeVerifier },
1145
- error: null
1146
- };
1147
- } catch (error) {
1148
- if (error instanceof InsForgeError) {
1149
- return { data: {}, error };
1150
- }
1151
- return {
1152
- data: {},
1153
- error: new InsForgeError(
1154
- "An unexpected error occurred during OAuth initialization",
1155
- 500,
1156
- "UNEXPECTED_ERROR"
1157
- )
1158
- };
1159
1392
  }
1160
1393
  }
1161
- /**
1162
- * Exchange OAuth authorization code for tokens (PKCE flow)
1163
- * Called automatically on initialization when insforge_code is in URL
1164
- */
1165
- async exchangeOAuthCode(code, codeVerifier) {
1166
- try {
1167
- const verifier = codeVerifier ?? retrievePkceVerifier();
1168
- if (!verifier) {
1169
- return {
1170
- data: null,
1171
- error: new InsForgeError(
1172
- "PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
1173
- 400,
1174
- "PKCE_VERIFIER_MISSING"
1175
- )
1176
- };
1177
- }
1178
- const request = {
1179
- code,
1180
- code_verifier: verifier
1181
- };
1182
- const response = await this.http.post(
1183
- this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
1184
- request,
1185
- { credentials: "include", skipAuthRefresh: true }
1186
- );
1187
- this.saveSessionFromResponse(response);
1188
- return {
1189
- data: response,
1190
- error: null
1191
- };
1192
- } catch (error) {
1193
- return wrapError(error, "An unexpected error occurred during OAuth code exchange");
1394
+ pageview() {
1395
+ const url = this.pageUrl();
1396
+ if (url === this.lastUrl) {
1397
+ return;
1194
1398
  }
1399
+ const from = this.lastUrl ? this.lastUrl : this.referrer;
1400
+ this.lastUrl = url;
1401
+ this.left = false;
1402
+ this.post({ e: "pv", u: url, r: from, dt: document.title });
1195
1403
  }
1196
- /**
1197
- * Sign in with an ID token from a native SDK (Google One Tap, etc.)
1198
- * Use this for native mobile apps or Google One Tap on web.
1199
- *
1200
- * @param credentials.provider - The identity provider (currently only 'google' is supported)
1201
- * @param credentials.token - The ID token from the native SDK
1202
- */
1203
- async signInWithIdToken(credentials) {
1204
- try {
1205
- const { provider, token } = credentials;
1206
- const response = await this.http.post(
1207
- "/api/auth/id-token?client_type=mobile",
1208
- { provider, token },
1209
- { credentials: "include", skipAuthRefresh: true }
1210
- );
1211
- this.saveSessionFromResponse(response);
1212
- if (response.refreshToken) {
1213
- this.http.setRefreshToken(response.refreshToken);
1214
- }
1215
- return {
1216
- data: response,
1217
- error: null
1218
- };
1219
- } catch (error) {
1220
- return wrapError(error, "An unexpected error occurred during ID token sign in");
1404
+ pageleave() {
1405
+ if (this.left || !this.lastUrl) {
1406
+ return;
1221
1407
  }
1408
+ this.left = true;
1409
+ this.post({ e: "pl", u: this.lastUrl });
1222
1410
  }
1223
- // ============================================================================
1224
- // Session Management
1225
- // ============================================================================
1226
- /**
1227
- * Refresh the current auth session.
1228
- *
1229
- * Browser mode:
1230
- * - Uses httpOnly refresh cookie and optional CSRF header.
1231
- *
1232
- * Legacy server mode (`isServerMode: true`):
1233
- * - Uses mobile auth flow and requires `refreshToken` in request body.
1234
- *
1235
- * SSR apps should prefer `createRefreshAuthRouter()` / `refreshAuth()` from
1236
- * `@insforge/sdk/ssr`.
1237
- */
1238
- async refreshSession(options) {
1239
- try {
1240
- if (this.isServerMode() && !options?.refreshToken) {
1241
- return {
1242
- data: null,
1243
- error: new InsForgeError(
1244
- "refreshToken is required when refreshing session in server mode",
1245
- 400,
1246
- ERROR_CODES.AUTH_UNAUTHORIZED
1247
- )
1248
- };
1249
- }
1250
- const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
1251
- const response = await this.http.post(
1252
- this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
1253
- this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
1254
- {
1255
- headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
1256
- credentials: "include",
1257
- skipAuthRefresh: true
1411
+ start() {
1412
+ const onNav = () => {
1413
+ setTimeout(() => this.pageview(), 0);
1414
+ };
1415
+ if (this.options.autoTrack !== false) {
1416
+ const push = history.pushState;
1417
+ const replace = history.replaceState;
1418
+ history.pushState = function(...args) {
1419
+ push.apply(this, args);
1420
+ onNav();
1421
+ };
1422
+ history.replaceState = function(...args) {
1423
+ replace.apply(this, args);
1424
+ onNav();
1425
+ };
1426
+ addEventListener("popstate", onNav);
1427
+ addEventListener("hashchange", onNav);
1428
+ addEventListener("pageshow", (event) => {
1429
+ if (event.persisted) {
1430
+ this.lastUrl = null;
1431
+ this.pageview();
1258
1432
  }
1259
- );
1260
- if (response.accessToken) {
1261
- this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
1262
- }
1263
- return { data: response, error: null };
1264
- } catch (error) {
1265
- return wrapError(error, "An unexpected error occurred during session refresh");
1433
+ });
1434
+ this.pageview();
1266
1435
  }
1267
- }
1268
- /**
1269
- * Get current user, automatically waits for pending OAuth callback
1270
- */
1271
- async getCurrentUser() {
1272
- await this.authCallbackHandled;
1273
- try {
1274
- if (this.isServerMode()) {
1275
- const accessToken = this.tokenManager.getAccessToken();
1276
- if (!accessToken) {
1277
- return { data: { user: null }, error: null };
1278
- }
1279
- this.http.setAuthToken(accessToken);
1280
- const response = await this.http.get("/api/auth/sessions/current");
1281
- const user = response.user ?? null;
1282
- return { data: { user }, error: null };
1283
- }
1284
- const session = this.tokenManager.getSession();
1285
- if (session) {
1286
- this.http.setAuthToken(session.accessToken);
1287
- return { data: { user: session.user }, error: null };
1436
+ addEventListener("pagehide", () => this.pageleave());
1437
+ document.addEventListener("visibilitychange", () => {
1438
+ if (document.visibilityState === "hidden") {
1439
+ this.pageleave();
1440
+ } else {
1441
+ this.left = false;
1288
1442
  }
1289
- if (typeof window !== "undefined") {
1290
- const { data: refreshed, error: refreshError } = await this.refreshSession();
1291
- if (refreshError) {
1292
- return { data: { user: null }, error: refreshError };
1443
+ });
1444
+ document.addEventListener(
1445
+ "click",
1446
+ (clickEvent) => {
1447
+ const source = clickEvent.target;
1448
+ const target = source?.closest ? source.closest("[data-wa-event]") : null;
1449
+ if (!target) {
1450
+ return;
1293
1451
  }
1294
- if (refreshed?.accessToken) {
1295
- return { data: { user: refreshed.user ?? null }, error: null };
1452
+ const name = target.getAttribute("data-wa-event");
1453
+ if (!name) {
1454
+ return;
1296
1455
  }
1297
- }
1298
- return { data: { user: null }, error: null };
1299
- } catch (error) {
1300
- if (error instanceof InsForgeError) {
1301
- return { data: { user: null }, error };
1302
- }
1303
- return {
1304
- data: { user: null },
1305
- error: new InsForgeError(
1306
- "An unexpected error occurred while getting user",
1307
- 500,
1308
- "UNEXPECTED_ERROR"
1309
- )
1310
- };
1456
+ const props = {};
1457
+ let any = false;
1458
+ for (const attr of Array.from(target.attributes)) {
1459
+ if (attr.name.indexOf("data-wa-event-") === 0) {
1460
+ props[attr.name.slice(14)] = attr.value;
1461
+ any = true;
1462
+ }
1463
+ }
1464
+ this.track(name, any ? props : void 0);
1465
+ },
1466
+ true
1467
+ );
1468
+ }
1469
+ };
1470
+ function bytes() {
1471
+ const b = new Uint8Array(16);
1472
+ crypto.getRandomValues(b);
1473
+ return b;
1474
+ }
1475
+ function format(b, version) {
1476
+ b[6] = b[6] & 15 | version << 4;
1477
+ b[8] = b[8] & 63 | 128;
1478
+ let hex = "";
1479
+ for (let i = 0; i < 16; i++) {
1480
+ hex += (b[i] + 256).toString(16).slice(1);
1481
+ if (i === 3 || i === 5 || i === 7 || i === 9) hex += "-";
1482
+ }
1483
+ return hex;
1484
+ }
1485
+ function uuid4() {
1486
+ return format(bytes(), 4);
1487
+ }
1488
+ function uuid7() {
1489
+ const b = bytes();
1490
+ let t = Date.now();
1491
+ for (let i = 5; i >= 0; i--) {
1492
+ b[i] = t & 255;
1493
+ t = Math.floor(t / 256);
1494
+ }
1495
+ return format(b, 7);
1496
+ }
1497
+
1498
+ // src/types/api.ts
1499
+ var ERROR_CODE_VALUES = [
1500
+ // Auth
1501
+ "AUTH_INVALID_EMAIL",
1502
+ "AUTH_WEAK_PASSWORD",
1503
+ "AUTH_INVALID_CREDENTIALS",
1504
+ "AUTH_INVALID_API_KEY",
1505
+ "AUTH_EMAIL_EXISTS",
1506
+ "AUTH_USER_NOT_FOUND",
1507
+ "AUTH_OAUTH_CONFIG_ALREADY_EXISTS",
1508
+ "AUTH_OAUTH_CONFIG_ERROR",
1509
+ "AUTH_OAUTH_CONFIG_NOT_FOUND",
1510
+ "AUTH_UNSUPPORTED_PROVIDER",
1511
+ "AUTH_TOKEN_EXPIRED",
1512
+ "AUTH_UNAUTHORIZED",
1513
+ "AUTH_NEED_VERIFICATION",
1514
+ "AUTH_SIGNUP_DISABLED",
1515
+ "AUTH_VERIFICATION_EMAIL_DELIVERY_FAILED",
1516
+ // Database
1517
+ "DATABASE_INVALID_PARAMETER",
1518
+ "DATABASE_VALIDATION_ERROR",
1519
+ "DATABASE_CONSTRAINT_VIOLATION",
1520
+ "DATABASE_NOT_FOUND",
1521
+ "DATABASE_DUPLICATE",
1522
+ "DATABASE_MIGRATION_ALREADY_EXISTS",
1523
+ "DATABASE_PERMISSION_DENIED",
1524
+ "DATABASE_INTERNAL_ERROR",
1525
+ "DATABASE_FORBIDDEN",
1526
+ // Storage
1527
+ "STORAGE_ALREADY_EXISTS",
1528
+ "STORAGE_INVALID_PARAMETER",
1529
+ "STORAGE_INVALID_FILE_TYPE",
1530
+ "STORAGE_INSUFFICIENT_QUOTA",
1531
+ "STORAGE_NOT_FOUND",
1532
+ "STORAGE_PERMISSION_DENIED",
1533
+ "S3_ACCESS_KEY_LIMIT_EXCEEDED",
1534
+ "S3_ACCESS_KEY_NOT_FOUND",
1535
+ "S3_PROTOCOL_UNAVAILABLE",
1536
+ // Realtime
1537
+ "REALTIME_CHANNEL_NOT_FOUND",
1538
+ "REALTIME_CONNECTION_FAILED",
1539
+ "REALTIME_INVALID_CHANNEL_REQUEST",
1540
+ "REALTIME_INVALID_CHANNEL_PATTERN",
1541
+ "REALTIME_INVALID_EVENT",
1542
+ "REALTIME_NOT_SUBSCRIBED",
1543
+ "REALTIME_UNAUTHORIZED",
1544
+ // AI
1545
+ "AI_INVALID_API_KEY",
1546
+ "AI_INVALID_MODEL",
1547
+ "AI_UPSTREAM_UNAVAILABLE",
1548
+ // Analytics
1549
+ "ANALYTICS_NOT_CONNECTED",
1550
+ "ANALYTICS_UNAVAILABLE",
1551
+ // Logs
1552
+ "LOGS_AWS_NOT_CONFIGURED",
1553
+ "LOG_NOT_FOUND",
1554
+ // Compute
1555
+ "COMPUTE_CLOUD_UNAVAILABLE",
1556
+ "COMPUTE_NOT_CONFIGURED",
1557
+ "COMPUTE_PROVIDER_ERROR",
1558
+ "COMPUTE_SERVICE_NOT_FOUND",
1559
+ "COMPUTE_MACHINE_NOT_FOUND",
1560
+ "COMPUTE_SERVICE_NOT_CONFIGURED",
1561
+ "COMPUTE_SERVICE_DEPLOY_FAILED",
1562
+ "COMPUTE_SERVICE_ALREADY_EXISTS",
1563
+ "COMPUTE_SERVICE_START_FAILED",
1564
+ "COMPUTE_SERVICE_STOP_FAILED",
1565
+ "COMPUTE_SERVICE_DELETE_FAILED",
1566
+ "COMPUTE_REGION_CHANGE_NOT_SUPPORTED",
1567
+ "COMPUTE_QUOTA_EXCEEDED",
1568
+ // Billing
1569
+ "BILLING_INSUFFICIENT_BALANCE",
1570
+ // Email
1571
+ "EMAIL_PROVIDER_NOT_CONFIGURED",
1572
+ "EMAIL_SMTP_CONNECTION_FAILED",
1573
+ "EMAIL_SMTP_SEND_FAILED",
1574
+ "EMAIL_TEMPLATE_NOT_FOUND",
1575
+ // Deployments
1576
+ "DEPLOYMENT_ALREADY_EXISTS",
1577
+ "DEPLOYMENT_INVALID_FILE",
1578
+ "DEPLOYMENT_NOT_FOUND",
1579
+ "DEPLOYMENT_UPLOAD_CANCELED",
1580
+ "DOMAIN_ALREADY_EXISTS",
1581
+ "DOMAIN_INVALID",
1582
+ "DOMAIN_NOT_FOUND",
1583
+ "ENVIRONMENT_VARIABLE_NOT_FOUND",
1584
+ // Docs
1585
+ "DOCS_NOT_FOUND",
1586
+ // Functions
1587
+ "FUNCTION_ALREADY_EXISTS",
1588
+ "FUNCTION_DEPLOYMENT_NOT_FOUND",
1589
+ "FUNCTION_NOT_FOUND",
1590
+ // Schedules
1591
+ "SCHEDULE_INVALID_CRON",
1592
+ "SCHEDULE_NOT_FOUND",
1593
+ // Payments
1594
+ "PAYMENT_CHECKOUT_ALREADY_EXISTS",
1595
+ "PAYMENT_CONFIG_INVALID",
1596
+ "PAYMENT_CONFIG_NOT_FOUND",
1597
+ "PAYMENT_NOT_FOUND",
1598
+ "PAYMENT_METHOD_DECLINED",
1599
+ "PAYMENT_PRICE_NOT_FOUND",
1600
+ "PAYMENT_PRODUCT_NOT_FOUND",
1601
+ // Secrets
1602
+ "SECRET_ALREADY_EXISTS",
1603
+ "SECRET_NOT_FOUND",
1604
+ // General
1605
+ "MISSING_FIELD",
1606
+ "ALREADY_EXISTS",
1607
+ "INVALID_INPUT",
1608
+ "NOT_FOUND",
1609
+ "UNKNOWN_ERROR",
1610
+ "INTERNAL_ERROR",
1611
+ "TOO_MANY_REQUESTS",
1612
+ "FORBIDDEN",
1613
+ "RATE_LIMITED",
1614
+ "NOT_IMPLEMENTED",
1615
+ "UPSTREAM_FAILURE"
1616
+ ];
1617
+ var ERROR_CODES = Object.fromEntries(
1618
+ ERROR_CODE_VALUES.map((code) => [code, code])
1619
+ );
1620
+ var OAUTH_PROVIDERS = [
1621
+ "google",
1622
+ "github",
1623
+ "discord",
1624
+ "linkedin",
1625
+ "facebook",
1626
+ "instagram",
1627
+ "tiktok",
1628
+ "apple",
1629
+ "x",
1630
+ "spotify",
1631
+ "microsoft"
1632
+ ];
1633
+
1634
+ // src/modules/auth/helpers.ts
1635
+ var PKCE_VERIFIER_KEY = "result_pkce_verifier";
1636
+ async function getWebCrypto() {
1637
+ const webCrypto = globalThis.crypto;
1638
+ if (typeof webCrypto?.getRandomValues === "function" && webCrypto.subtle) {
1639
+ return webCrypto;
1640
+ }
1641
+ if (typeof process !== "undefined" && process.versions?.node) {
1642
+ const { webcrypto } = await import("crypto");
1643
+ return webcrypto;
1644
+ }
1645
+ throw new Error("Web Crypto API is not available in this environment");
1646
+ }
1647
+ function base64UrlEncode(buffer) {
1648
+ const base64 = btoa(String.fromCharCode(...buffer));
1649
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1650
+ }
1651
+ async function generateCodeVerifier() {
1652
+ const webCrypto = await getWebCrypto();
1653
+ const array = new Uint8Array(32);
1654
+ webCrypto.getRandomValues(array);
1655
+ return base64UrlEncode(array);
1656
+ }
1657
+ async function generateCodeChallenge(verifier) {
1658
+ const webCrypto = await getWebCrypto();
1659
+ const encoder = new TextEncoder();
1660
+ const data = encoder.encode(verifier);
1661
+ const hash = await webCrypto.subtle.digest("SHA-256", data);
1662
+ return base64UrlEncode(new Uint8Array(hash));
1663
+ }
1664
+ function storePkceVerifier(verifier) {
1665
+ if (typeof sessionStorage !== "undefined") {
1666
+ sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
1667
+ }
1668
+ }
1669
+ function retrievePkceVerifier() {
1670
+ if (typeof sessionStorage === "undefined") {
1671
+ return null;
1672
+ }
1673
+ const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
1674
+ if (verifier) {
1675
+ sessionStorage.removeItem(PKCE_VERIFIER_KEY);
1676
+ }
1677
+ return verifier;
1678
+ }
1679
+ function wrapError(error, fallbackMessage) {
1680
+ if (error instanceof ResultError) {
1681
+ return { data: null, error };
1682
+ }
1683
+ return {
1684
+ data: null,
1685
+ error: new ResultError(
1686
+ error instanceof Error ? error.message : fallbackMessage,
1687
+ 500,
1688
+ "UNEXPECTED_ERROR"
1689
+ )
1690
+ };
1691
+ }
1692
+ function cleanUrlParams(...params) {
1693
+ if (typeof window === "undefined") {
1694
+ return;
1695
+ }
1696
+ const url = new URL(window.location.href);
1697
+ for (const p of params) {
1698
+ url.searchParams.delete(p);
1699
+ }
1700
+ window.history.replaceState({}, document.title, url.toString());
1701
+ }
1702
+
1703
+ // src/modules/auth/auth.ts
1704
+ var Auth = class {
1705
+ constructor(http, tokenManager, options = {}) {
1706
+ this.http = http;
1707
+ this.tokenManager = tokenManager;
1708
+ this.options = options;
1709
+ this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
1710
+ }
1711
+ http;
1712
+ tokenManager;
1713
+ options;
1714
+ authCallbackHandled;
1715
+ isServerMode() {
1716
+ return !!this.options.isServerMode;
1717
+ }
1718
+ /** Subscribe to SDK authentication state changes. */
1719
+ onAuthStateChange(callback) {
1720
+ return this.tokenManager.onAuthStateChange(callback);
1721
+ }
1722
+ /**
1723
+ * Save session from API response
1724
+ * Handles token storage, CSRF token, and HTTP auth header
1725
+ */
1726
+ saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
1727
+ if (!response.accessToken || !response.user) {
1728
+ return false;
1729
+ }
1730
+ const session = {
1731
+ accessToken: response.accessToken,
1732
+ user: response.user
1733
+ };
1734
+ if (!this.isServerMode() && response.csrfToken) {
1735
+ setCsrfToken(response.csrfToken);
1736
+ }
1737
+ if (!this.isServerMode()) {
1738
+ this.tokenManager.saveSession(session, event);
1311
1739
  }
1740
+ this.http.setAuthToken(response.accessToken);
1741
+ this.http.setRefreshToken(response.refreshToken ?? null);
1742
+ return true;
1312
1743
  }
1313
1744
  // ============================================================================
1314
- // Profile Management
1745
+ // OAuth Callback Detection (runs on initialization)
1315
1746
  // ============================================================================
1316
- async getProfile(userId) {
1317
- try {
1318
- const response = await this.http.get(`/api/auth/profiles/${userId}`);
1319
- return { data: response, error: null };
1320
- } catch (error) {
1321
- return wrapError(error, "An unexpected error occurred while fetching user profile");
1747
+ /**
1748
+ * Detect and handle OAuth callback parameters in URL
1749
+ * Supports the PKCE flow (OAuth callback code in the URL)
1750
+ */
1751
+ async detectAuthCallback() {
1752
+ if (this.isServerMode() || typeof window === "undefined") {
1753
+ return;
1322
1754
  }
1323
- }
1324
- async setProfile(profile) {
1325
1755
  try {
1326
- const response = await this.http.patch("/api/auth/profiles/current", {
1327
- profile
1328
- });
1329
- const currentUser = this.tokenManager.getUser();
1330
- if (!this.isServerMode() && currentUser && response.profile !== void 0) {
1331
- this.tokenManager.setUser({
1332
- ...currentUser,
1333
- profile: response.profile
1334
- });
1756
+ const params = new URLSearchParams(window.location.search);
1757
+ const error = params.get("error");
1758
+ if (error) {
1759
+ cleanUrlParams("error");
1760
+ console.debug("OAuth callback error:", error);
1761
+ return;
1762
+ }
1763
+ const code = params.get("insforge_code");
1764
+ if (code) {
1765
+ cleanUrlParams("insforge_code");
1766
+ const { error: exchangeError } = await this.exchangeOAuthCode(code);
1767
+ if (exchangeError) {
1768
+ console.debug("OAuth code exchange failed:", exchangeError.message);
1769
+ }
1770
+ return;
1335
1771
  }
1336
- return { data: response, error: null };
1337
1772
  } catch (error) {
1338
- return wrapError(error, "An unexpected error occurred while updating user profile");
1773
+ console.debug("OAuth callback detection skipped:", error);
1339
1774
  }
1340
1775
  }
1341
1776
  // ============================================================================
1342
- // Email Verification
1777
+ // Sign Up / Sign In / Sign Out
1343
1778
  // ============================================================================
1344
- async resendVerificationEmail(request) {
1345
- try {
1346
- const response = await this.http.post("/api/auth/email/send-verification", request, {
1347
- skipAuthRefresh: true
1348
- });
1349
- return { data: response, error: null };
1350
- } catch (error) {
1351
- return wrapError(error, "An unexpected error occurred while sending verification email");
1352
- }
1353
- }
1354
- async verifyEmail(request) {
1779
+ async signUp(request) {
1355
1780
  try {
1356
1781
  const response = await this.http.post(
1357
- this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
1782
+ this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
1358
1783
  request,
1359
1784
  { credentials: "include", skipAuthRefresh: true }
1360
1785
  );
1361
- this.saveSessionFromResponse(response);
1786
+ if (response.accessToken && response.user) {
1787
+ this.saveSessionFromResponse(response);
1788
+ }
1362
1789
  if (response.refreshToken) {
1363
1790
  this.http.setRefreshToken(response.refreshToken);
1364
1791
  }
1365
1792
  return { data: response, error: null };
1366
1793
  } catch (error) {
1367
- return wrapError(error, "An unexpected error occurred while verifying email");
1368
- }
1369
- }
1370
- // ============================================================================
1371
- // Password Reset
1372
- // ============================================================================
1373
- async sendResetPasswordEmail(request) {
1374
- try {
1375
- const response = await this.http.post("/api/auth/email/send-reset-password", request, {
1376
- skipAuthRefresh: true
1377
- });
1378
- return { data: response, error: null };
1379
- } catch (error) {
1380
- return wrapError(error, "An unexpected error occurred while sending password reset email");
1381
- }
1382
- }
1383
- async exchangeResetPasswordToken(request) {
1384
- try {
1385
- const response = await this.http.post(
1386
- "/api/auth/email/exchange-reset-password-token",
1387
- request,
1388
- { skipAuthRefresh: true }
1389
- );
1390
- return { data: response, error: null };
1391
- } catch (error) {
1392
- return wrapError(error, "An unexpected error occurred while verifying reset code");
1794
+ return wrapError(error, "An unexpected error occurred during sign up");
1393
1795
  }
1394
1796
  }
1395
- async resetPassword(request) {
1797
+ async signInWithPassword(request) {
1396
1798
  try {
1397
1799
  const response = await this.http.post(
1398
- "/api/auth/email/reset-password",
1800
+ this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
1399
1801
  request,
1400
- { skipAuthRefresh: true }
1401
- );
1402
- return { data: response, error: null };
1403
- } catch (error) {
1404
- return wrapError(error, "An unexpected error occurred while resetting password");
1405
- }
1406
- }
1407
- // ============================================================================
1408
- // Configuration
1409
- // ============================================================================
1410
- async getPublicAuthConfig() {
1411
- try {
1412
- const response = await this.http.get("/api/auth/public-config", {
1413
- skipAuthRefresh: true
1414
- });
1415
- return { data: response, error: null };
1416
- } catch (error) {
1417
- return wrapError(error, "An unexpected error occurred while fetching auth configuration");
1418
- }
1419
- }
1420
- };
1421
- function createInsForgePostgrestFetch(httpClient) {
1422
- return async (input, init) => {
1423
- const url = typeof input === "string" ? input : input.toString();
1424
- const urlObj = new URL(url);
1425
- const pathname = urlObj.pathname.slice(1);
1426
- const rpcMatch = pathname.match(/^rpc\/(.+)$/);
1427
- const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
1428
- const insforgeUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
1429
- const headers = new Headers(httpClient.getHeaders());
1430
- new Headers(init?.headers).forEach((value, key) => {
1431
- headers.set(key, value);
1432
- });
1433
- const response = await httpClient.rawFetch(insforgeUrl, {
1434
- ...init,
1435
- headers
1436
- });
1437
- return response;
1438
- };
1439
- }
1440
- var Database = class {
1441
- constructor(httpClient, defaultSchema) {
1442
- this.postgrest = new PostgrestClient("http://dummy", {
1443
- fetch: createInsForgePostgrestFetch(httpClient),
1444
- headers: {},
1445
- ...defaultSchema ? { schema: defaultSchema } : {}
1446
- });
1447
- }
1448
- /**
1449
- * Select a non-default Postgres schema for the chained query. Maps to
1450
- * PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
1451
- * The schema must be exposed by the backend.
1452
- *
1453
- * @example
1454
- * const { data } = await client.database
1455
- * .schema('analytics')
1456
- * .from('events')
1457
- * .select('*');
1458
- *
1459
- * @example
1460
- * await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
1461
- */
1462
- schema(schemaName) {
1463
- return this.postgrest.schema(schemaName);
1464
- }
1465
- /**
1466
- * Create a query builder for a table
1467
- *
1468
- * @example
1469
- * // Basic query
1470
- * const { data, error } = await client.database
1471
- * .from('posts')
1472
- * .select('*')
1473
- * .eq('user_id', userId);
1474
- *
1475
- * // With count (Supabase style!)
1476
- * const { data, error, count } = await client.database
1477
- * .from('posts')
1478
- * .select('*', { count: 'exact' })
1479
- * .range(0, 9);
1480
- *
1481
- * // Just get count, no data
1482
- * const { count } = await client.database
1483
- * .from('posts')
1484
- * .select('*', { count: 'exact', head: true });
1485
- *
1486
- * // Complex queries with OR
1487
- * const { data } = await client.database
1488
- * .from('posts')
1489
- * .select('*, users!inner(*)')
1490
- * .or('status.eq.active,status.eq.pending');
1491
- *
1492
- * // All features work:
1493
- * - Nested selects
1494
- * - Foreign key expansion
1495
- * - OR/AND/NOT conditions
1496
- * - Count with head
1497
- * - Range pagination
1498
- * - Upserts
1499
- */
1500
- from(table) {
1501
- return this.postgrest.from(table);
1502
- }
1503
- /**
1504
- * Call a PostgreSQL function (RPC)
1505
- *
1506
- * @example
1507
- * // Call a function with parameters
1508
- * const { data, error } = await client.database
1509
- * .rpc('get_user_stats', { user_id: 123 });
1510
- *
1511
- * // Call a function with no parameters
1512
- * const { data, error } = await client.database
1513
- * .rpc('get_all_active_users');
1514
- *
1515
- * // With options (head, count, get)
1516
- * const { data, count } = await client.database
1517
- * .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
1518
- */
1519
- rpc(fn, args, options) {
1520
- return this.postgrest.rpc(fn, args, options);
1521
- }
1522
- };
1523
- var StorageBucket = class {
1524
- constructor(bucketName, http) {
1525
- this.bucketName = bucketName;
1526
- this.http = http;
1527
- }
1528
- /**
1529
- * Upload a file with a specific key
1530
- * Uses the upload strategy from backend (direct or presigned)
1531
- * @param path - The object key/path
1532
- * @param file - File or Blob to upload
1533
- */
1534
- async upload(path, file) {
1535
- try {
1536
- const strategyResponse = await this.http.post(
1537
- `/api/storage/buckets/${this.bucketName}/upload-strategy`,
1538
- {
1539
- filename: path,
1540
- contentType: file.type || "application/octet-stream",
1541
- size: file.size
1542
- }
1802
+ { credentials: "include", skipAuthRefresh: true }
1543
1803
  );
1544
- if (strategyResponse.method === "presigned") {
1545
- return await this.uploadWithPresignedUrl(strategyResponse, file);
1546
- }
1547
- if (strategyResponse.method === "direct") {
1548
- const formData = new FormData();
1549
- formData.append("file", file);
1550
- const response = await this.http.request(
1551
- "PUT",
1552
- `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
1553
- {
1554
- body: formData,
1555
- headers: {
1556
- // Don't set Content-Type, let browser set multipart boundary
1557
- }
1558
- }
1559
- );
1560
- return { data: response, error: null };
1804
+ this.saveSessionFromResponse(response);
1805
+ if (response.refreshToken) {
1806
+ this.http.setRefreshToken(response.refreshToken);
1561
1807
  }
1562
- throw new InsForgeError(
1563
- `Unsupported upload method: ${strategyResponse.method}`,
1564
- 500,
1565
- "STORAGE_ERROR"
1566
- );
1808
+ return { data: response, error: null };
1567
1809
  } catch (error) {
1568
- return {
1569
- data: null,
1570
- error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1571
- };
1810
+ return wrapError(error, "An unexpected error occurred during sign in");
1572
1811
  }
1573
1812
  }
1574
- /**
1575
- * Upload a file with auto-generated key
1576
- * Uses the upload strategy from backend (direct or presigned)
1577
- * @param file - File or Blob to upload
1578
- */
1579
- async uploadAuto(file) {
1813
+ async signOut() {
1580
1814
  try {
1581
- const filename = file instanceof File ? file.name : "file";
1582
- const strategyResponse = await this.http.post(
1583
- `/api/storage/buckets/${this.bucketName}/upload-strategy`,
1584
- {
1585
- filename,
1586
- contentType: file.type || "application/octet-stream",
1587
- size: file.size
1588
- }
1589
- );
1590
- if (strategyResponse.method === "presigned") {
1591
- return await this.uploadWithPresignedUrl(strategyResponse, file);
1592
- }
1593
- if (strategyResponse.method === "direct") {
1594
- const formData = new FormData();
1595
- formData.append("file", file);
1596
- const response = await this.http.request(
1597
- "POST",
1598
- `/api/storage/buckets/${this.bucketName}/objects`,
1815
+ try {
1816
+ const serverMode = this.isServerMode();
1817
+ const csrfToken = !serverMode ? getCsrfToken() : null;
1818
+ await this.http.post(
1819
+ serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
1820
+ void 0,
1599
1821
  {
1600
- body: formData,
1601
- headers: {
1602
- // Don't set Content-Type, let browser set multipart boundary
1603
- }
1822
+ credentials: "include",
1823
+ skipAuthRefresh: true,
1824
+ ...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
1604
1825
  }
1605
1826
  );
1606
- return { data: response, error: null };
1827
+ } catch {
1607
1828
  }
1608
- throw new InsForgeError(
1609
- `Unsupported upload method: ${strategyResponse.method}`,
1610
- 500,
1611
- "STORAGE_ERROR"
1612
- );
1613
- } catch (error) {
1829
+ this.tokenManager.clearSession();
1830
+ this.http.setAuthToken(null);
1831
+ this.http.setRefreshToken(null);
1832
+ if (!this.isServerMode()) {
1833
+ clearCsrfToken();
1834
+ }
1835
+ return { error: null };
1836
+ } catch {
1614
1837
  return {
1615
- data: null,
1616
- error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1838
+ error: new ResultError("Failed to sign out", 500, "SIGNOUT_ERROR")
1617
1839
  };
1618
1840
  }
1619
1841
  }
1620
- /**
1621
- * Internal method to handle presigned URL uploads
1622
- */
1623
- async uploadWithPresignedUrl(strategy, file) {
1842
+ async signInWithOAuth(providerOrOptions, options) {
1624
1843
  try {
1625
- const formData = new FormData();
1626
- if (strategy.fields) {
1627
- Object.entries(strategy.fields).forEach(([key, value]) => {
1628
- formData.append(key, value);
1629
- });
1844
+ let signInOptions;
1845
+ if (typeof providerOrOptions === "object") {
1846
+ signInOptions = providerOrOptions;
1847
+ } else if (options) {
1848
+ signInOptions = { provider: providerOrOptions, ...options };
1849
+ } else {
1850
+ return {
1851
+ data: {},
1852
+ error: new ResultError(
1853
+ "OAuth sign-in options are required",
1854
+ 400,
1855
+ ERROR_CODES.INVALID_INPUT
1856
+ )
1857
+ };
1630
1858
  }
1631
- formData.append("file", file);
1632
- const uploadResponse = await fetch(strategy.uploadUrl, {
1633
- method: "POST",
1634
- body: formData
1635
- });
1636
- if (!uploadResponse.ok) {
1637
- throw new InsForgeError(
1638
- `Upload to storage failed: ${uploadResponse.statusText}`,
1639
- uploadResponse.status,
1640
- "STORAGE_ERROR"
1641
- );
1859
+ if (!signInOptions || !signInOptions.redirectTo) {
1860
+ return {
1861
+ data: {},
1862
+ error: new ResultError(
1863
+ "Redirect URI is required",
1864
+ 400,
1865
+ ERROR_CODES.INVALID_INPUT
1866
+ )
1867
+ };
1642
1868
  }
1643
- if (strategy.confirmRequired && strategy.confirmUrl) {
1644
- const confirmResponse = await this.http.post(strategy.confirmUrl, {
1645
- size: file.size,
1646
- contentType: file.type || "application/octet-stream"
1647
- });
1648
- return { data: confirmResponse, error: null };
1869
+ const { provider } = signInOptions;
1870
+ const providerKey = encodeURIComponent(provider.toLowerCase());
1871
+ const codeVerifier = await generateCodeVerifier();
1872
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
1873
+ storePkceVerifier(codeVerifier);
1874
+ const params = {
1875
+ ...signInOptions.additionalParams ?? {},
1876
+ redirect_uri: signInOptions.redirectTo,
1877
+ code_challenge: codeChallenge
1878
+ };
1879
+ const isBuiltInProvider = OAUTH_PROVIDERS.includes(
1880
+ providerKey
1881
+ );
1882
+ const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
1883
+ const response = await this.http.get(oauthPath, {
1884
+ params,
1885
+ skipAuthRefresh: true
1886
+ });
1887
+ if (!this.isServerMode() && typeof window !== "undefined" && !signInOptions.skipBrowserRedirect) {
1888
+ window.location.href = response.authUrl;
1889
+ return { data: {}, error: null };
1649
1890
  }
1650
1891
  return {
1651
- data: {
1652
- key: strategy.key,
1653
- bucket: this.bucketName,
1654
- size: file.size,
1655
- mimeType: file.type || "application/octet-stream",
1656
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
1657
- url: this.getPublicUrl(strategy.key).data.publicUrl
1658
- },
1892
+ data: { url: response.authUrl, provider: providerKey, codeVerifier },
1659
1893
  error: null
1660
1894
  };
1661
1895
  } catch (error) {
1662
- throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
1663
- }
1664
- }
1665
- /**
1666
- * Download a file
1667
- * Uses the download strategy from backend (direct or presigned)
1668
- * @param path - The object key/path
1669
- * Returns the file as a Blob
1670
- */
1671
- async download(path) {
1672
- try {
1673
- const encodedKey = encodeURIComponent(path);
1674
- let strategyResponse;
1675
- try {
1676
- strategyResponse = await this.http.get(
1677
- `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encodedKey}`
1678
- );
1679
- } catch (err) {
1680
- const status = err instanceof InsForgeError ? err.statusCode : void 0;
1681
- if (status === 404 || status === 405) {
1682
- strategyResponse = await this.http.post(
1683
- `/api/storage/buckets/${this.bucketName}/objects/${encodedKey}/download-strategy`,
1684
- {}
1685
- );
1686
- } else {
1687
- throw err;
1688
- }
1689
- }
1690
- const downloadUrl = strategyResponse.url;
1691
- const headers = {};
1692
- if (strategyResponse.method === "direct") {
1693
- Object.assign(headers, this.http.getHeaders());
1694
- }
1695
- const response = await fetch(downloadUrl, {
1696
- method: "GET",
1697
- headers
1698
- });
1699
- if (!response.ok) {
1700
- try {
1701
- const error = await response.json();
1702
- throw InsForgeError.fromApiError(error);
1703
- } catch {
1704
- throw new InsForgeError(
1705
- `Download failed: ${response.statusText}`,
1706
- response.status,
1707
- "STORAGE_ERROR"
1708
- );
1709
- }
1896
+ if (error instanceof ResultError) {
1897
+ return { data: {}, error };
1710
1898
  }
1711
- const blob = await response.blob();
1712
- return { data: blob, error: null };
1713
- } catch (error) {
1714
1899
  return {
1715
- data: null,
1716
- error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
1900
+ data: {},
1901
+ error: new ResultError(
1902
+ "An unexpected error occurred during OAuth initialization",
1903
+ 500,
1904
+ "UNEXPECTED_ERROR"
1905
+ )
1717
1906
  };
1718
1907
  }
1719
1908
  }
1720
1909
  /**
1721
- * Get the public URL for an object in a public bucket.
1722
- *
1723
- * Pure string construction — no network call, no auth. The URL only resolves
1724
- * if the bucket is public; for private objects use {@link createSignedUrl}.
1725
- *
1726
- * @param path - The object key/path
1727
- * @returns `{ data: { publicUrl }, error }` — matches the external SDK pattern,
1728
- * so `const { data } = getPublicUrl(path)` then `data.publicUrl`.
1729
- */
1730
- getPublicUrl(path) {
1731
- const publicUrl = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
1732
- return { data: { publicUrl }, error: null };
1733
- }
1734
- /**
1735
- * Resolve a download strategy (signed or direct URL) for an object with a
1736
- * caller-supplied TTL. Prefers the canonical GET route and falls back to the
1737
- * legacy POST alias so signed-URL creation still works against older backends
1738
- * that predate the GET route (they return 404/405 for it). A genuine
1739
- * "object not found" (STORAGE_NOT_FOUND) is not retried.
1910
+ * Exchange OAuth authorization code for tokens (PKCE flow)
1911
+ * Called automatically on initialization when the OAuth callback code is in the URL
1740
1912
  */
1741
- async requestDownloadStrategy(path, expiresIn) {
1742
- const encoded = encodeURIComponent(path);
1913
+ async exchangeOAuthCode(code, codeVerifier) {
1743
1914
  try {
1744
- return await this.http.get(
1745
- `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encoded}`,
1746
- { params: { expiresIn: expiresIn.toString() } }
1915
+ const verifier = codeVerifier ?? retrievePkceVerifier();
1916
+ if (!verifier) {
1917
+ return {
1918
+ data: null,
1919
+ error: new ResultError(
1920
+ "PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
1921
+ 400,
1922
+ "PKCE_VERIFIER_MISSING"
1923
+ )
1924
+ };
1925
+ }
1926
+ const request = {
1927
+ code,
1928
+ code_verifier: verifier
1929
+ };
1930
+ const response = await this.http.post(
1931
+ this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
1932
+ request,
1933
+ { credentials: "include", skipAuthRefresh: true }
1747
1934
  );
1935
+ this.saveSessionFromResponse(response);
1936
+ return {
1937
+ data: response,
1938
+ error: null
1939
+ };
1748
1940
  } catch (error) {
1749
- const status = error instanceof InsForgeError ? error.statusCode : void 0;
1750
- const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
1751
- if (!isMissingRoute) {
1752
- throw error;
1753
- }
1754
- return await this.http.post(
1755
- `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
1756
- { expiresIn }
1941
+ return wrapError(
1942
+ error,
1943
+ "An unexpected error occurred during OAuth code exchange"
1757
1944
  );
1758
1945
  }
1759
1946
  }
1760
1947
  /**
1761
- * Create a signed URL for an object.
1762
- *
1763
- * Returns a time-limited, credential-free URL that can be handed directly to
1764
- * a browser (`<img src>`), an email, or a third party — no SDK or session is
1765
- * needed to fetch it. Authorization is enforced when the URL is minted (the
1766
- * caller must be allowed to read the object), so the resulting link is a
1767
- * pre-authorized capability scoped to this one object until it expires.
1948
+ * Sign in with an ID token from a native SDK (Google One Tap, etc.)
1949
+ * Use this for native mobile apps or Google One Tap on web.
1768
1950
  *
1769
- * @param path - The object key/path
1770
- * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d).
1771
- * Honored for private buckets; public buckets return their long-lived URL.
1951
+ * @param credentials.provider - The identity provider (currently only 'google' is supported)
1952
+ * @param credentials.token - The ID token from the native SDK
1772
1953
  */
1773
- async createSignedUrl(path, expiresIn = 3600) {
1954
+ async signInWithIdToken(credentials) {
1774
1955
  try {
1775
- const strategy = await this.requestDownloadStrategy(path, expiresIn);
1956
+ const { provider, token } = credentials;
1957
+ const response = await this.http.post(
1958
+ "/api/auth/id-token?client_type=mobile",
1959
+ { provider, token },
1960
+ { credentials: "include", skipAuthRefresh: true }
1961
+ );
1962
+ this.saveSessionFromResponse(response);
1963
+ if (response.refreshToken) {
1964
+ this.http.setRefreshToken(response.refreshToken);
1965
+ }
1776
1966
  return {
1777
- data: {
1778
- signedUrl: strategy.url,
1779
- expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
1780
- },
1967
+ data: response,
1781
1968
  error: null
1782
1969
  };
1783
1970
  } catch (error) {
1784
- return {
1785
- data: null,
1786
- error: error instanceof InsForgeError ? error : new InsForgeError("Failed to create signed URL", 500, "STORAGE_ERROR")
1787
- };
1971
+ return wrapError(
1972
+ error,
1973
+ "An unexpected error occurred during ID token sign in"
1974
+ );
1788
1975
  }
1789
1976
  }
1977
+ // ============================================================================
1978
+ // Session Management
1979
+ // ============================================================================
1790
1980
  /**
1791
- * Create signed URLs for multiple objects in a single call.
1981
+ * Refresh the current auth session.
1792
1982
  *
1793
- * Each entry resolves independently: a failure on one key (not found / not
1794
- * permitted) is reported on that entry's `error` without failing the rest.
1983
+ * Browser mode:
1984
+ * - Uses httpOnly refresh cookie and optional CSRF header.
1795
1985
  *
1796
- * @param paths - The object keys/paths
1797
- * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
1986
+ * Legacy server mode (`isServerMode: true`):
1987
+ * - Uses mobile auth flow and requires `refreshToken` in request body.
1988
+ *
1989
+ * SSR apps should prefer `createRefreshAuthRouter()` / `refreshAuth()` from
1990
+ * `@resultdev/sdk/ssr`.
1798
1991
  */
1799
- async createSignedUrls(paths, expiresIn = 3600) {
1992
+ async refreshSession(options) {
1800
1993
  try {
1801
- const data = await Promise.all(
1802
- paths.map(async (path) => {
1803
- const { data: signed, error } = await this.createSignedUrl(path, expiresIn);
1804
- return {
1805
- path,
1806
- signedUrl: signed?.signedUrl ?? null,
1807
- error: error ? error.message : null
1808
- };
1809
- })
1994
+ if (this.isServerMode() && !options?.refreshToken) {
1995
+ return {
1996
+ data: null,
1997
+ error: new ResultError(
1998
+ "refreshToken is required when refreshing session in server mode",
1999
+ 400,
2000
+ ERROR_CODES.AUTH_UNAUTHORIZED
2001
+ )
2002
+ };
2003
+ }
2004
+ const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
2005
+ const response = await this.http.post(
2006
+ this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
2007
+ this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
2008
+ {
2009
+ headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
2010
+ credentials: "include",
2011
+ skipAuthRefresh: true
2012
+ }
1810
2013
  );
1811
- return { data, error: null };
2014
+ if (response.accessToken) {
2015
+ this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
2016
+ }
2017
+ return { data: response, error: null };
1812
2018
  } catch (error) {
1813
- return {
1814
- data: null,
1815
- error: error instanceof InsForgeError ? error : new InsForgeError("Failed to create signed URLs", 500, "STORAGE_ERROR")
1816
- };
2019
+ return wrapError(
2020
+ error,
2021
+ "An unexpected error occurred during session refresh"
2022
+ );
1817
2023
  }
1818
2024
  }
1819
2025
  /**
1820
- * List objects in the bucket
1821
- * @param prefix - Filter by key prefix
1822
- * @param search - Search in file names
1823
- * @param limit - Maximum number of results (default: 100, max: 1000)
1824
- * @param offset - Number of results to skip
2026
+ * Get current user, automatically waits for pending OAuth callback
1825
2027
  */
1826
- async list(options) {
2028
+ async getCurrentUser() {
2029
+ await this.authCallbackHandled;
1827
2030
  try {
1828
- const params = {};
1829
- if (options?.prefix) {
1830
- params.prefix = options.prefix;
2031
+ if (this.isServerMode()) {
2032
+ const accessToken = this.tokenManager.getAccessToken();
2033
+ if (!accessToken) {
2034
+ return { data: { user: null }, error: null };
2035
+ }
2036
+ this.http.setAuthToken(accessToken);
2037
+ const response = await this.http.get(
2038
+ "/api/auth/sessions/current"
2039
+ );
2040
+ const user = response.user ?? null;
2041
+ return { data: { user }, error: null };
1831
2042
  }
1832
- if (options?.search) {
1833
- params.search = options.search;
2043
+ const session = this.tokenManager.getSession();
2044
+ if (session) {
2045
+ this.http.setAuthToken(session.accessToken);
2046
+ return { data: { user: session.user }, error: null };
1834
2047
  }
1835
- if (options?.limit) {
1836
- params.limit = options.limit.toString();
2048
+ if (typeof window !== "undefined") {
2049
+ const { data: refreshed, error: refreshError } = await this.refreshSession();
2050
+ if (refreshError) {
2051
+ return { data: { user: null }, error: refreshError };
2052
+ }
2053
+ if (refreshed?.accessToken) {
2054
+ return { data: { user: refreshed.user ?? null }, error: null };
2055
+ }
1837
2056
  }
1838
- if (options?.offset) {
1839
- params.offset = options.offset.toString();
2057
+ return { data: { user: null }, error: null };
2058
+ } catch (error) {
2059
+ if (error instanceof ResultError) {
2060
+ return { data: { user: null }, error };
1840
2061
  }
2062
+ return {
2063
+ data: { user: null },
2064
+ error: new ResultError(
2065
+ "An unexpected error occurred while getting user",
2066
+ 500,
2067
+ "UNEXPECTED_ERROR"
2068
+ )
2069
+ };
2070
+ }
2071
+ }
2072
+ // ============================================================================
2073
+ // Profile Management
2074
+ // ============================================================================
2075
+ async getProfile(userId) {
2076
+ try {
1841
2077
  const response = await this.http.get(
1842
- `/api/storage/buckets/${this.bucketName}/objects`,
1843
- { params }
2078
+ `/api/auth/profiles/${userId}`
1844
2079
  );
1845
2080
  return { data: response, error: null };
1846
2081
  } catch (error) {
1847
- return {
1848
- data: null,
1849
- error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
1850
- };
2082
+ return wrapError(
2083
+ error,
2084
+ "An unexpected error occurred while fetching user profile"
2085
+ );
1851
2086
  }
1852
2087
  }
1853
- /**
1854
- * Delete a file
1855
- * @param path - The object key/path
1856
- */
1857
- async remove(path) {
2088
+ async setProfile(profile) {
1858
2089
  try {
1859
- const response = await this.http.delete(
1860
- `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`
2090
+ const response = await this.http.patch(
2091
+ "/api/auth/profiles/current",
2092
+ {
2093
+ profile
2094
+ }
1861
2095
  );
2096
+ const currentUser = this.tokenManager.getUser();
2097
+ if (!this.isServerMode() && currentUser && response.profile !== void 0) {
2098
+ this.tokenManager.setUser({
2099
+ ...currentUser,
2100
+ profile: response.profile
2101
+ });
2102
+ }
1862
2103
  return { data: response, error: null };
1863
2104
  } catch (error) {
1864
- return {
1865
- data: null,
1866
- error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
1867
- };
2105
+ return wrapError(
2106
+ error,
2107
+ "An unexpected error occurred while updating user profile"
2108
+ );
1868
2109
  }
1869
2110
  }
1870
- };
1871
- var Storage = class {
1872
- constructor(http) {
1873
- this.http = http;
2111
+ // ============================================================================
2112
+ // Email Verification
2113
+ // ============================================================================
2114
+ async resendVerificationEmail(request) {
2115
+ try {
2116
+ const response = await this.http.post("/api/auth/email/send-verification", request, {
2117
+ skipAuthRefresh: true
2118
+ });
2119
+ return { data: response, error: null };
2120
+ } catch (error) {
2121
+ return wrapError(
2122
+ error,
2123
+ "An unexpected error occurred while sending verification email"
2124
+ );
2125
+ }
1874
2126
  }
1875
- /**
1876
- * Get a bucket instance for operations
1877
- * @param bucketName - Name of the bucket
1878
- */
1879
- from(bucketName) {
1880
- return new StorageBucket(bucketName, this.http);
2127
+ async verifyEmail(request) {
2128
+ try {
2129
+ const response = await this.http.post(
2130
+ this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
2131
+ request,
2132
+ { credentials: "include", skipAuthRefresh: true }
2133
+ );
2134
+ this.saveSessionFromResponse(response);
2135
+ if (response.refreshToken) {
2136
+ this.http.setRefreshToken(response.refreshToken);
2137
+ }
2138
+ return { data: response, error: null };
2139
+ } catch (error) {
2140
+ return wrapError(
2141
+ error,
2142
+ "An unexpected error occurred while verifying email"
2143
+ );
2144
+ }
1881
2145
  }
1882
- };
1883
- var AI = class {
1884
- constructor(http) {
1885
- this.http = http;
1886
- this.chat = new Chat(http);
1887
- this.images = new Images(http);
1888
- this.embeddings = new Embeddings(http);
2146
+ // ============================================================================
2147
+ // Password Reset
2148
+ // ============================================================================
2149
+ async sendResetPasswordEmail(request) {
2150
+ try {
2151
+ const response = await this.http.post("/api/auth/email/send-reset-password", request, {
2152
+ skipAuthRefresh: true
2153
+ });
2154
+ return { data: response, error: null };
2155
+ } catch (error) {
2156
+ return wrapError(
2157
+ error,
2158
+ "An unexpected error occurred while sending password reset email"
2159
+ );
2160
+ }
1889
2161
  }
1890
- };
1891
- var Chat = class {
1892
- constructor(http) {
1893
- this.completions = new ChatCompletions(http);
2162
+ async exchangeResetPasswordToken(request) {
2163
+ try {
2164
+ const response = await this.http.post(
2165
+ "/api/auth/email/exchange-reset-password-token",
2166
+ request,
2167
+ { skipAuthRefresh: true }
2168
+ );
2169
+ return { data: response, error: null };
2170
+ } catch (error) {
2171
+ return wrapError(
2172
+ error,
2173
+ "An unexpected error occurred while verifying reset code"
2174
+ );
2175
+ }
2176
+ }
2177
+ async resetPassword(request) {
2178
+ try {
2179
+ const response = await this.http.post(
2180
+ "/api/auth/email/reset-password",
2181
+ request,
2182
+ { skipAuthRefresh: true }
2183
+ );
2184
+ return { data: response, error: null };
2185
+ } catch (error) {
2186
+ return wrapError(
2187
+ error,
2188
+ "An unexpected error occurred while resetting password"
2189
+ );
2190
+ }
2191
+ }
2192
+ // ============================================================================
2193
+ // Configuration
2194
+ // ============================================================================
2195
+ async getPublicAuthConfig() {
2196
+ try {
2197
+ const response = await this.http.get(
2198
+ "/api/auth/public-config",
2199
+ {
2200
+ skipAuthRefresh: true
2201
+ }
2202
+ );
2203
+ return { data: response, error: null };
2204
+ } catch (error) {
2205
+ return wrapError(
2206
+ error,
2207
+ "An unexpected error occurred while fetching auth configuration"
2208
+ );
2209
+ }
1894
2210
  }
1895
2211
  };
1896
- var ChatCompletions = class {
1897
- constructor(http) {
1898
- this.http = http;
2212
+
2213
+ // src/modules/database-postgrest.ts
2214
+ import { PostgrestClient } from "@supabase/postgrest-js";
2215
+ function createResultPostgrestFetch(httpClient) {
2216
+ return async (input, init) => {
2217
+ const url = typeof input === "string" ? input : input.toString();
2218
+ const urlObj = new URL(url);
2219
+ const pathname = urlObj.pathname.slice(1);
2220
+ const rpcMatch = pathname.match(/^rpc\/(.+)$/);
2221
+ const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
2222
+ const backendUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
2223
+ const headers = new Headers(httpClient.getHeaders());
2224
+ new Headers(init?.headers).forEach((value, key) => {
2225
+ headers.set(key, value);
2226
+ });
2227
+ const response = await httpClient.rawFetch(backendUrl, {
2228
+ ...init,
2229
+ headers
2230
+ });
2231
+ return response;
2232
+ };
2233
+ }
2234
+ var Database = class {
2235
+ postgrest;
2236
+ constructor(httpClient, defaultSchema) {
2237
+ this.postgrest = new PostgrestClient("http://dummy", {
2238
+ fetch: createResultPostgrestFetch(httpClient),
2239
+ headers: {},
2240
+ ...defaultSchema ? { schema: defaultSchema } : {}
2241
+ });
1899
2242
  }
1900
2243
  /**
1901
- * Create a chat completion - OpenAI-like response format
2244
+ * Select a non-default Postgres schema for the chained query. Maps to
2245
+ * PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
2246
+ * The schema must be exposed by the backend.
1902
2247
  *
1903
2248
  * @example
1904
- * ```typescript
1905
- * // Non-streaming
1906
- * const completion = await client.ai.chat.completions.create({
1907
- * model: 'gpt-4',
1908
- * messages: [{ role: 'user', content: 'Hello!' }]
1909
- * });
1910
- * console.log(completion.choices[0].message.content);
2249
+ * const { data } = await client.database
2250
+ * .schema('analytics')
2251
+ * .from('events')
2252
+ * .select('*');
1911
2253
  *
1912
- * // With images (OpenAI-compatible format)
1913
- * const response = await client.ai.chat.completions.create({
1914
- * model: 'gpt-4-vision',
1915
- * messages: [{
1916
- * role: 'user',
1917
- * content: [
1918
- * { type: 'text', text: 'What is in this image?' },
1919
- * { type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
1920
- * ]
1921
- * }]
1922
- * });
2254
+ * @example
2255
+ * await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
2256
+ */
2257
+ schema(schemaName) {
2258
+ return this.postgrest.schema(schemaName);
2259
+ }
2260
+ /**
2261
+ * Create a query builder for a table
1923
2262
  *
1924
- * // With PDF files
1925
- * const pdfResponse = await client.ai.chat.completions.create({
1926
- * model: 'anthropic/claude-3.5-sonnet',
1927
- * messages: [{
1928
- * role: 'user',
1929
- * content: [
1930
- * { type: 'text', text: 'Summarize this document' },
1931
- * { type: 'file', file: { filename: 'doc.pdf', file_data: 'https://example.com/doc.pdf' } }
1932
- * ]
1933
- * }],
1934
- * fileParser: { enabled: true, pdf: { engine: 'mistral-ocr' } }
1935
- * });
2263
+ * @example
2264
+ * // Basic query
2265
+ * const { data, error } = await client.database
2266
+ * .from('posts')
2267
+ * .select('*')
2268
+ * .eq('user_id', userId);
1936
2269
  *
1937
- * // With web search
1938
- * const searchResponse = await client.ai.chat.completions.create({
1939
- * model: 'openai/gpt-4',
1940
- * messages: [{ role: 'user', content: 'What are the latest news about AI?' }],
1941
- * webSearch: { enabled: true, maxResults: 5 }
1942
- * });
1943
- * // Access citations from response.choices[0].message.annotations
2270
+ * // With count (Supabase style!)
2271
+ * const { data, error, count } = await client.database
2272
+ * .from('posts')
2273
+ * .select('*', { count: 'exact' })
2274
+ * .range(0, 9);
1944
2275
  *
1945
- * // With thinking/reasoning mode (Anthropic models)
1946
- * const thinkingResponse = await client.ai.chat.completions.create({
1947
- * model: 'anthropic/claude-3.5-sonnet',
1948
- * messages: [{ role: 'user', content: 'Solve this complex math problem...' }],
1949
- * thinking: true
1950
- * });
2276
+ * // Just get count, no data
2277
+ * const { count } = await client.database
2278
+ * .from('posts')
2279
+ * .select('*', { count: 'exact', head: true });
1951
2280
  *
1952
- * // Streaming - returns async iterable
1953
- * const stream = await client.ai.chat.completions.create({
1954
- * model: 'gpt-4',
1955
- * messages: [{ role: 'user', content: 'Tell me a story' }],
1956
- * stream: true
1957
- * });
2281
+ * // Complex queries with OR
2282
+ * const { data } = await client.database
2283
+ * .from('posts')
2284
+ * .select('*, users!inner(*)')
2285
+ * .or('status.eq.active,status.eq.pending');
1958
2286
  *
1959
- * for await (const chunk of stream) {
1960
- * if (chunk.choices[0]?.delta?.content) {
1961
- * process.stdout.write(chunk.choices[0].delta.content);
1962
- * }
1963
- * }
1964
- * ```
1965
- */
1966
- async create(params) {
1967
- const backendParams = {
1968
- model: params.model,
1969
- messages: params.messages,
1970
- temperature: params.temperature,
1971
- maxTokens: params.maxTokens,
1972
- topP: params.topP,
1973
- stream: params.stream,
1974
- // New plugin options
1975
- webSearch: params.webSearch,
1976
- fileParser: params.fileParser,
1977
- thinking: params.thinking,
1978
- // Tool calling options
1979
- tools: params.tools,
1980
- toolChoice: params.toolChoice,
1981
- parallelToolCalls: params.parallelToolCalls
1982
- };
1983
- if (params.stream) {
1984
- const headers = this.http.getHeaders();
1985
- headers["Content-Type"] = "application/json";
1986
- const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
1987
- method: "POST",
1988
- headers,
1989
- body: JSON.stringify(backendParams)
1990
- });
1991
- if (!response2.ok) {
1992
- const error = await response2.json();
1993
- throw new Error(error.error || "Stream request failed");
1994
- }
1995
- return this.parseSSEStream(response2, params.model);
1996
- }
1997
- const response = await this.http.post(
1998
- "/api/ai/chat/completion",
1999
- backendParams
2000
- );
2001
- const content = response.text || "";
2002
- return {
2003
- id: `chatcmpl-${Date.now()}`,
2004
- object: "chat.completion",
2005
- created: Math.floor(Date.now() / 1e3),
2006
- model: response.metadata?.model,
2007
- choices: [
2008
- {
2009
- index: 0,
2010
- message: {
2011
- role: "assistant",
2012
- content,
2013
- // Include tool_calls if present (from tool calling)
2014
- ...response.tool_calls?.length && { tool_calls: response.tool_calls },
2015
- // Include annotations if present (from web search or file parsing)
2016
- ...response.annotations?.length && { annotations: response.annotations }
2017
- },
2018
- finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
2019
- }
2020
- ],
2021
- usage: response.metadata?.usage || {
2022
- prompt_tokens: 0,
2023
- completion_tokens: 0,
2024
- total_tokens: 0
2025
- }
2026
- };
2027
- }
2028
- /**
2029
- * Parse SSE stream into async iterable of OpenAI-like chunks
2287
+ * // All features work:
2288
+ * - Nested selects
2289
+ * - Foreign key expansion
2290
+ * - OR/AND/NOT conditions
2291
+ * - Count with head
2292
+ * - Range pagination
2293
+ * - Upserts
2030
2294
  */
2031
- async *parseSSEStream(response, model) {
2032
- const reader = response.body.getReader();
2033
- const decoder = new TextDecoder();
2034
- let buffer = "";
2035
- try {
2036
- while (true) {
2037
- const { done, value } = await reader.read();
2038
- if (done) {
2039
- break;
2040
- }
2041
- buffer += decoder.decode(value, { stream: true });
2042
- const lines = buffer.split("\n");
2043
- buffer = lines.pop() || "";
2044
- for (const line of lines) {
2045
- if (line.startsWith("data: ")) {
2046
- const dataStr = line.slice(6).trim();
2047
- if (dataStr) {
2048
- try {
2049
- const data = JSON.parse(dataStr);
2050
- if (data.chunk || data.content) {
2051
- yield {
2052
- id: `chatcmpl-${Date.now()}`,
2053
- object: "chat.completion.chunk",
2054
- created: Math.floor(Date.now() / 1e3),
2055
- model,
2056
- choices: [
2057
- {
2058
- index: 0,
2059
- delta: {
2060
- content: data.chunk || data.content
2061
- },
2062
- finish_reason: null
2063
- }
2064
- ]
2065
- };
2066
- }
2067
- if (data.tool_calls?.length) {
2068
- yield {
2069
- id: `chatcmpl-${Date.now()}`,
2070
- object: "chat.completion.chunk",
2071
- created: Math.floor(Date.now() / 1e3),
2072
- model,
2073
- choices: [
2074
- {
2075
- index: 0,
2076
- delta: {
2077
- tool_calls: data.tool_calls
2078
- },
2079
- finish_reason: "tool_calls"
2080
- }
2081
- ]
2082
- };
2083
- }
2084
- if (data.done) {
2085
- reader.releaseLock();
2086
- return;
2087
- }
2088
- } catch {
2089
- console.warn("Failed to parse SSE data:", dataStr);
2090
- }
2091
- }
2092
- }
2093
- }
2094
- }
2095
- } finally {
2096
- reader.releaseLock();
2097
- }
2098
- }
2099
- };
2100
- var Embeddings = class {
2101
- constructor(http) {
2102
- this.http = http;
2295
+ from(table) {
2296
+ return this.postgrest.from(table);
2103
2297
  }
2104
2298
  /**
2105
- * Create embeddings for text input - OpenAI-like response format
2299
+ * Call a PostgreSQL function (RPC)
2106
2300
  *
2107
2301
  * @example
2108
- * ```typescript
2109
- * // Single text input
2110
- * const response = await client.ai.embeddings.create({
2111
- * model: 'openai/text-embedding-3-small',
2112
- * input: 'Hello world'
2113
- * });
2114
- * console.log(response.data[0].embedding); // number[]
2115
- *
2116
- * // Multiple text inputs
2117
- * const response = await client.ai.embeddings.create({
2118
- * model: 'openai/text-embedding-3-small',
2119
- * input: ['Hello world', 'Goodbye world']
2120
- * });
2121
- * response.data.forEach((item, i) => {
2122
- * console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
2123
- * });
2124
- *
2125
- * // With custom dimensions (if supported by model)
2126
- * const response = await client.ai.embeddings.create({
2127
- * model: 'openai/text-embedding-3-small',
2128
- * input: 'Hello world',
2129
- * dimensions: 256
2130
- * });
2302
+ * // Call a function with parameters
2303
+ * const { data, error } = await client.database
2304
+ * .rpc('get_user_stats', { user_id: 123 });
2131
2305
  *
2132
- * // With base64 encoding format
2133
- * const response = await client.ai.embeddings.create({
2134
- * model: 'openai/text-embedding-3-small',
2135
- * input: 'Hello world',
2136
- * encoding_format: 'base64'
2137
- * });
2138
- * ```
2306
+ * // Call a function with no parameters
2307
+ * const { data, error } = await client.database
2308
+ * .rpc('get_all_active_users');
2309
+ *
2310
+ * // With options (head, count, get)
2311
+ * const { data, count } = await client.database
2312
+ * .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
2139
2313
  */
2140
- async create(params) {
2141
- const response = await this.http.post("/api/ai/embeddings", params);
2142
- return {
2143
- object: response.object,
2144
- data: response.data,
2145
- model: response.metadata?.model,
2146
- usage: response.metadata?.usage ? {
2147
- prompt_tokens: response.metadata.usage.promptTokens || 0,
2148
- total_tokens: response.metadata.usage.totalTokens || 0
2149
- } : {
2150
- prompt_tokens: 0,
2151
- total_tokens: 0
2152
- }
2153
- };
2314
+ rpc(fn, args, options) {
2315
+ return this.postgrest.rpc(fn, args, options);
2154
2316
  }
2155
2317
  };
2156
- var Images = class {
2318
+
2319
+ // src/modules/email.ts
2320
+ var Emails = class {
2321
+ http;
2157
2322
  constructor(http) {
2158
2323
  this.http = http;
2159
2324
  }
2160
2325
  /**
2161
- * Generate images - OpenAI-like response format
2162
- *
2163
- * @example
2164
- * ```typescript
2165
- * // Text-to-image
2166
- * const response = await client.ai.images.generate({
2167
- * model: 'dall-e-3',
2168
- * prompt: 'A sunset over mountains',
2169
- * });
2170
- * console.log(response.data[0].b64_json);
2171
- *
2172
- * // Image-to-image (with input images)
2173
- * const response = await client.ai.images.generate({
2174
- * model: 'stable-diffusion-xl',
2175
- * prompt: 'Transform this into a watercolor painting',
2176
- * images: [
2177
- * { url: 'https://example.com/input.jpg' },
2178
- * // or base64-encoded Data URI:
2179
- * { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
2180
- * ]
2181
- * });
2182
- * ```
2326
+ * Send a custom HTML email
2327
+ * @param options Email options including recipients, subject, and HTML content
2183
2328
  */
2184
- async generate(params) {
2185
- const response = await this.http.post(
2186
- "/api/ai/image/generation",
2187
- params
2188
- );
2189
- let data = [];
2190
- if (response.images && response.images.length > 0) {
2191
- data = response.images.map((img) => ({
2192
- b64_json: img.imageUrl.replace(/^data:image\/\w+;base64,/, ""),
2193
- content: response.text
2194
- }));
2195
- } else if (response.text) {
2196
- data = [{ content: response.text }];
2197
- }
2198
- return {
2199
- created: Math.floor(Date.now() / 1e3),
2200
- data,
2201
- ...response.metadata?.usage && {
2202
- usage: {
2203
- total_tokens: response.metadata.usage.totalTokens || 0,
2204
- input_tokens: response.metadata.usage.promptTokens || 0,
2205
- output_tokens: response.metadata.usage.completionTokens || 0
2206
- }
2329
+ async send(options) {
2330
+ try {
2331
+ const data = await this.http.post(
2332
+ "/api/email/send-raw",
2333
+ options
2334
+ );
2335
+ return { data, error: null };
2336
+ } catch (error) {
2337
+ if (error instanceof Error && error.name === "AbortError") {
2338
+ throw error;
2207
2339
  }
2208
- };
2340
+ return {
2341
+ data: null,
2342
+ error: error instanceof ResultError ? error : new ResultError(
2343
+ error instanceof Error ? error.message : "Email send failed",
2344
+ 500,
2345
+ "EMAIL_ERROR"
2346
+ )
2347
+ };
2348
+ }
2209
2349
  }
2210
2350
  };
2351
+
2352
+ // src/modules/functions.ts
2211
2353
  var Functions = class _Functions {
2354
+ http;
2355
+ functionsUrl;
2212
2356
  constructor(http, functionsUrl) {
2213
2357
  this.http = http;
2214
2358
  this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
2215
2359
  }
2216
2360
  /**
2217
2361
  * Derive the subhosting URL from the base URL.
2218
- * Base URL pattern: https://{appKey}.{region}.insforge.app
2219
- * Functions URL: https://{appKey}.functions.insforge.app
2220
- * Only applies to .insforge.app domains.
2362
+ * Rewrites the backend base URL to its functions subhosting URL
2363
+ * ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
2364
+ * Only applies to platform-hosted backends.
2221
2365
  */
2222
2366
  static deriveSubhostingUrl(baseUrl) {
2223
2367
  try {
@@ -2236,7 +2380,7 @@ var Functions = class _Functions {
2236
2380
  * placeholder; the router only reads pathname.
2237
2381
  */
2238
2382
  buildInProcessRequest(slug, method, body, callerHeaders) {
2239
- const url = new URL("/" + slug, "http://insforge.local").toString();
2383
+ const url = new URL(`/${slug}`, "http://backend.local").toString();
2240
2384
  const headers = { ...this.http.getHeaders() };
2241
2385
  const reqBody = serializeBody(method, body, headers);
2242
2386
  Object.assign(headers, callerHeaders);
@@ -2250,7 +2394,7 @@ var Functions = class _Functions {
2250
2394
  * Invoke an Edge Function.
2251
2395
  *
2252
2396
  * Dispatch order:
2253
- * 1. If `globalThis.__insforge_dispatch__` is present, call it in-process.
2397
+ * 1. If the platform's in-process dispatch hook is present, call it directly.
2254
2398
  * This avoids Deno Subhosting's 508 Loop Detected when one bundled
2255
2399
  * function invokes another inside the same deployment.
2256
2400
  * 2. Otherwise, try the configured subhosting URL.
@@ -2275,7 +2419,7 @@ var Functions = class _Functions {
2275
2419
  }
2276
2420
  return {
2277
2421
  data: null,
2278
- error: error instanceof InsForgeError ? error : new InsForgeError(
2422
+ error: error instanceof ResultError ? error : new ResultError(
2279
2423
  error instanceof Error ? error.message : "Function invocation failed",
2280
2424
  500,
2281
2425
  "FUNCTION_ERROR"
@@ -2285,20 +2429,24 @@ var Functions = class _Functions {
2285
2429
  }
2286
2430
  if (this.functionsUrl) {
2287
2431
  try {
2288
- const data = await this.http.request(method, `${this.functionsUrl}/${slug}`, {
2289
- body,
2290
- headers
2291
- });
2432
+ const data = await this.http.request(
2433
+ method,
2434
+ `${this.functionsUrl}/${slug}`,
2435
+ {
2436
+ body,
2437
+ headers
2438
+ }
2439
+ );
2292
2440
  return { data, error: null };
2293
2441
  } catch (error) {
2294
2442
  if (error instanceof Error && error.name === "AbortError") {
2295
2443
  throw error;
2296
2444
  }
2297
- if (error instanceof InsForgeError && error.statusCode === 404) {
2445
+ if (error instanceof ResultError && error.statusCode === 404) {
2298
2446
  } else {
2299
2447
  return {
2300
2448
  data: null,
2301
- error: error instanceof InsForgeError ? error : new InsForgeError(
2449
+ error: error instanceof ResultError ? error : new ResultError(
2302
2450
  error instanceof Error ? error.message : "Function invocation failed",
2303
2451
  500,
2304
2452
  "FUNCTION_ERROR"
@@ -2317,7 +2465,7 @@ var Functions = class _Functions {
2317
2465
  }
2318
2466
  return {
2319
2467
  data: null,
2320
- error: error instanceof InsForgeError ? error : new InsForgeError(
2468
+ error: error instanceof ResultError ? error : new ResultError(
2321
2469
  error instanceof Error ? error.message : "Function invocation failed",
2322
2470
  500,
2323
2471
  "FUNCTION_ERROR"
@@ -2326,6 +2474,8 @@ var Functions = class _Functions {
2326
2474
  }
2327
2475
  }
2328
2476
  };
2477
+
2478
+ // src/modules/realtime.ts
2329
2479
  var CONNECT_TIMEOUT = 1e4;
2330
2480
  var SUBSCRIBE_TIMEOUT = 1e4;
2331
2481
  var Realtime = class {
@@ -2334,18 +2484,22 @@ var Realtime = class {
2334
2484
  this.tokenManager = tokenManager;
2335
2485
  this.anonKey = anonKey;
2336
2486
  this.getValidAccessToken = getValidAccessToken;
2337
- this.socket = null;
2338
- this.connectPromise = null;
2339
- this.connectionAttempt = null;
2340
- this.nextConnectionAttemptId = 0;
2341
- this.subscriptions = /* @__PURE__ */ new Map();
2342
- this.eventListeners = /* @__PURE__ */ new Map();
2343
2487
  this.tokenManager.onAuthStateChange((event) => {
2344
2488
  if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
2345
2489
  this.reconnectForAuthChange();
2346
2490
  }
2347
2491
  });
2348
2492
  }
2493
+ baseUrl;
2494
+ tokenManager;
2495
+ anonKey;
2496
+ getValidAccessToken;
2497
+ socket = null;
2498
+ connectPromise = null;
2499
+ connectionAttempt = null;
2500
+ nextConnectionAttemptId = 0;
2501
+ subscriptions = /* @__PURE__ */ new Map();
2502
+ eventListeners = /* @__PURE__ */ new Map();
2349
2503
  notifyListeners(event, payload) {
2350
2504
  for (const callback of this.eventListeners.get(event) ?? []) {
2351
2505
  try {
@@ -2510,7 +2664,10 @@ var Realtime = class {
2510
2664
  {
2511
2665
  ok: false,
2512
2666
  channel: subscription.channel,
2513
- error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
2667
+ error: {
2668
+ code: "DISCONNECTED",
2669
+ message: "Connection lost before subscription completed"
2670
+ }
2514
2671
  },
2515
2672
  true
2516
2673
  );
@@ -2533,7 +2690,10 @@ var Realtime = class {
2533
2690
  return Promise.resolve({
2534
2691
  ok: false,
2535
2692
  channel,
2536
- error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
2693
+ error: {
2694
+ code: "CONNECTION_FAILED",
2695
+ message: "Not connected to realtime server"
2696
+ }
2537
2697
  });
2538
2698
  }
2539
2699
  subscription.status = "pending";
@@ -2564,21 +2724,28 @@ var Realtime = class {
2564
2724
  );
2565
2725
  }
2566
2726
  }, SUBSCRIBE_TIMEOUT);
2567
- socket.emit("realtime:subscribe", { channel }, (response) => {
2568
- if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2569
- return;
2570
- }
2571
- if (response.ok) {
2572
- subscription.status = "subscribed";
2573
- subscription.members = new Map(
2574
- response.presence.members.map((member) => [member.presenceId, member])
2575
- );
2576
- } else {
2577
- subscription.status = "rejected";
2578
- subscription.members.clear();
2727
+ socket.emit(
2728
+ "realtime:subscribe",
2729
+ { channel },
2730
+ (response) => {
2731
+ if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2732
+ return;
2733
+ }
2734
+ if (response.ok) {
2735
+ subscription.status = "subscribed";
2736
+ subscription.members = new Map(
2737
+ response.presence.members.map((member) => [
2738
+ member.presenceId,
2739
+ member
2740
+ ])
2741
+ );
2742
+ } else {
2743
+ subscription.status = "rejected";
2744
+ subscription.members.clear();
2745
+ }
2746
+ this.settleSubscription(subscription, response, false);
2579
2747
  }
2580
- this.settleSubscription(subscription, response, false);
2581
- });
2748
+ );
2582
2749
  });
2583
2750
  return subscription.pending;
2584
2751
  }
@@ -2627,10 +2794,19 @@ var Realtime = class {
2627
2794
  return subscription.pending;
2628
2795
  }
2629
2796
  if (subscription.status === "subscribed") {
2630
- return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
2797
+ return {
2798
+ ok: true,
2799
+ channel,
2800
+ presence: { members: [...subscription.members.values()] }
2801
+ };
2631
2802
  }
2632
2803
  } else {
2633
- subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
2804
+ subscription = {
2805
+ channel,
2806
+ epoch: 0,
2807
+ status: "pending",
2808
+ members: /* @__PURE__ */ new Map()
2809
+ };
2634
2810
  this.subscriptions.set(channel, subscription);
2635
2811
  }
2636
2812
  if (!this.socket?.connected) {
@@ -2641,7 +2817,11 @@ var Realtime = class {
2641
2817
  this.subscriptions.delete(channel);
2642
2818
  }
2643
2819
  const message = error instanceof Error ? error.message : "Connection failed";
2644
- return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
2820
+ return {
2821
+ ok: false,
2822
+ channel,
2823
+ error: { code: "CONNECTION_FAILED", message }
2824
+ };
2645
2825
  }
2646
2826
  }
2647
2827
  return subscription.pending ?? this.requestSubscription(channel, subscription);
@@ -2657,7 +2837,10 @@ var Realtime = class {
2657
2837
  {
2658
2838
  ok: false,
2659
2839
  channel,
2660
- error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
2840
+ error: {
2841
+ code: "SUBSCRIPTION_CANCELLED",
2842
+ message: "Subscription cancelled"
2843
+ }
2661
2844
  },
2662
2845
  true
2663
2846
  );
@@ -2667,7 +2850,9 @@ var Realtime = class {
2667
2850
  }
2668
2851
  async publish(channel, event, payload) {
2669
2852
  if (!this.socket?.connected) {
2670
- throw new Error("Not connected to realtime server. Call connect() first.");
2853
+ throw new Error(
2854
+ "Not connected to realtime server. Call connect() first."
2855
+ );
2671
2856
  }
2672
2857
  this.socket.emit("realtime:publish", { channel, event, payload });
2673
2858
  }
@@ -2696,201 +2881,545 @@ var Realtime = class {
2696
2881
  getPresenceState(channel) {
2697
2882
  return [...this.subscriptions.get(channel)?.members.values() ?? []];
2698
2883
  }
2699
- };
2700
- var Emails = class {
2701
- constructor(http) {
2702
- this.http = http;
2884
+ };
2885
+
2886
+ // src/modules/storage.ts
2887
+ function generateObjectKey(filename) {
2888
+ const dotIndex = filename.lastIndexOf(".");
2889
+ const hasExt = dotIndex > 0;
2890
+ const ext = hasExt ? filename.slice(dotIndex) : "";
2891
+ const base = hasExt ? filename.slice(0, dotIndex) : filename;
2892
+ const sanitizedBase = base.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 32) || "file";
2893
+ const timestamp = Date.now();
2894
+ const random = Math.random().toString(36).slice(2, 8);
2895
+ return `${sanitizedBase}-${timestamp}-${random}${ext}`;
2896
+ }
2897
+ var StorageBucket = class {
2898
+ constructor(bucketName, http) {
2899
+ this.bucketName = bucketName;
2900
+ this.http = http;
2901
+ }
2902
+ bucketName;
2903
+ http;
2904
+ /**
2905
+ * Upload a file to a specific key.
2906
+ * Uses the upload strategy from the backend (direct or presigned).
2907
+ * Standard PUT semantics: uploading to an existing key replaces the
2908
+ * current object in place.
2909
+ * @param path - The object key/path
2910
+ * @param file - File or Blob to upload
2911
+ */
2912
+ async upload(path, file) {
2913
+ try {
2914
+ const strategyResponse = await this.http.post(
2915
+ `/api/storage/buckets/${this.bucketName}/upload-strategy`,
2916
+ {
2917
+ filename: path,
2918
+ contentType: file.type || "application/octet-stream",
2919
+ size: file.size
2920
+ }
2921
+ );
2922
+ if (strategyResponse.method === "presigned") {
2923
+ return await this.uploadWithPresignedUrl(strategyResponse, file);
2924
+ }
2925
+ if (strategyResponse.method === "direct") {
2926
+ const formData = new FormData();
2927
+ formData.append("file", file);
2928
+ const response = await this.http.request(
2929
+ "PUT",
2930
+ `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
2931
+ {
2932
+ body: formData,
2933
+ headers: {
2934
+ // Don't set Content-Type, let browser set multipart boundary
2935
+ }
2936
+ }
2937
+ );
2938
+ return { data: response, error: null };
2939
+ }
2940
+ throw new ResultError(
2941
+ `Unsupported upload method: ${strategyResponse.method}`,
2942
+ 500,
2943
+ "STORAGE_ERROR"
2944
+ );
2945
+ } catch (error) {
2946
+ return {
2947
+ data: null,
2948
+ error: error instanceof ResultError ? error : new ResultError("Upload failed", 500, "STORAGE_ERROR")
2949
+ };
2950
+ }
2951
+ }
2952
+ /**
2953
+ * Upload a file under an automatically generated, collision-free key.
2954
+ * The key is derived client-side from the filename (sanitized base +
2955
+ * timestamp + random suffix) and uploaded through the standard
2956
+ * {@link upload} path, so repeated uploads of the same file never
2957
+ * overwrite each other. Reads the filename structurally to avoid assuming
2958
+ * a global `File` (which Node 18 does not expose).
2959
+ * @param file - File or Blob to upload
2960
+ */
2961
+ async uploadAuto(file) {
2962
+ const filename = "name" in file && typeof file.name === "string" ? file.name : "file";
2963
+ return this.upload(generateObjectKey(filename), file);
2964
+ }
2965
+ /**
2966
+ * Internal method to handle presigned URL uploads
2967
+ */
2968
+ async uploadWithPresignedUrl(strategy, file) {
2969
+ try {
2970
+ const formData = new FormData();
2971
+ if (strategy.fields) {
2972
+ Object.entries(strategy.fields).forEach(([key, value]) => {
2973
+ formData.append(key, value);
2974
+ });
2975
+ }
2976
+ formData.append("file", file);
2977
+ const uploadResponse = await fetch(strategy.uploadUrl, {
2978
+ method: "POST",
2979
+ body: formData
2980
+ });
2981
+ if (!uploadResponse.ok) {
2982
+ throw new ResultError(
2983
+ `Upload to storage failed: ${uploadResponse.statusText}`,
2984
+ uploadResponse.status,
2985
+ "STORAGE_ERROR"
2986
+ );
2987
+ }
2988
+ if (strategy.confirmRequired && strategy.confirmUrl) {
2989
+ const confirmResponse = await this.http.post(
2990
+ strategy.confirmUrl,
2991
+ {
2992
+ size: file.size,
2993
+ contentType: file.type || "application/octet-stream"
2994
+ }
2995
+ );
2996
+ return { data: confirmResponse, error: null };
2997
+ }
2998
+ return {
2999
+ data: {
3000
+ key: strategy.key,
3001
+ bucket: this.bucketName,
3002
+ size: file.size,
3003
+ mimeType: file.type || "application/octet-stream",
3004
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
3005
+ url: this.getPublicUrl(strategy.key).data?.publicUrl
3006
+ },
3007
+ error: null
3008
+ };
3009
+ } catch (error) {
3010
+ throw error instanceof ResultError ? error : new ResultError("Presigned upload failed", 500, "STORAGE_ERROR");
3011
+ }
3012
+ }
3013
+ /**
3014
+ * Download a file
3015
+ * Uses the download strategy from backend (direct or presigned)
3016
+ * @param path - The object key/path
3017
+ * Returns the file as a Blob
3018
+ */
3019
+ async download(path) {
3020
+ try {
3021
+ const encodedKey = encodeURIComponent(path);
3022
+ let strategyResponse;
3023
+ try {
3024
+ strategyResponse = await this.http.get(
3025
+ `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encodedKey}`
3026
+ );
3027
+ } catch (err) {
3028
+ const status = err instanceof ResultError ? err.statusCode : void 0;
3029
+ if (status === 404 || status === 405) {
3030
+ strategyResponse = await this.http.post(
3031
+ `/api/storage/buckets/${this.bucketName}/objects/${encodedKey}/download-strategy`,
3032
+ {}
3033
+ );
3034
+ } else {
3035
+ throw err;
3036
+ }
3037
+ }
3038
+ const downloadUrl = strategyResponse.url;
3039
+ const headers = {};
3040
+ if (strategyResponse.method === "direct") {
3041
+ Object.assign(headers, this.http.getHeaders());
3042
+ }
3043
+ const response = await fetch(downloadUrl, {
3044
+ method: "GET",
3045
+ headers
3046
+ });
3047
+ if (!response.ok) {
3048
+ try {
3049
+ const error = await response.json();
3050
+ throw ResultError.fromApiError(error);
3051
+ } catch {
3052
+ throw new ResultError(
3053
+ `Download failed: ${response.statusText}`,
3054
+ response.status,
3055
+ "STORAGE_ERROR"
3056
+ );
3057
+ }
3058
+ }
3059
+ const blob = await response.blob();
3060
+ return { data: blob, error: null };
3061
+ } catch (error) {
3062
+ return {
3063
+ data: null,
3064
+ error: error instanceof ResultError ? error : new ResultError("Download failed", 500, "STORAGE_ERROR")
3065
+ };
3066
+ }
3067
+ }
3068
+ /**
3069
+ * Get the public URL for an object in a public bucket.
3070
+ *
3071
+ * Pure string construction — no network call, no auth. The URL only resolves
3072
+ * if the bucket is public; for private objects use {@link createSignedUrl}.
3073
+ *
3074
+ * @param path - The object key/path
3075
+ * @returns `{ data: { publicUrl }, error }` — matches the external SDK pattern,
3076
+ * so `const { data } = getPublicUrl(path)` then `data.publicUrl`.
3077
+ */
3078
+ getPublicUrl(path) {
3079
+ const publicUrl = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
3080
+ return { data: { publicUrl }, error: null };
2703
3081
  }
2704
3082
  /**
2705
- * Send a custom HTML email
2706
- * @param options Email options including recipients, subject, and HTML content
3083
+ * Resolve a download strategy (signed or direct URL) for an object with a
3084
+ * caller-supplied TTL. Prefers the canonical GET route and falls back to the
3085
+ * legacy POST alias so signed-URL creation still works against older backends
3086
+ * that predate the GET route (they return 404/405 for it). A genuine
3087
+ * "object not found" (STORAGE_NOT_FOUND) is not retried.
2707
3088
  */
2708
- async send(options) {
3089
+ async requestDownloadStrategy(path, expiresIn) {
3090
+ const encoded = encodeURIComponent(path);
2709
3091
  try {
2710
- const data = await this.http.post("/api/email/send-raw", options);
2711
- return { data, error: null };
3092
+ return await this.http.get(
3093
+ `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encoded}`,
3094
+ { params: { expiresIn: expiresIn.toString() } }
3095
+ );
2712
3096
  } catch (error) {
2713
- if (error instanceof Error && error.name === "AbortError") {
3097
+ const status = error instanceof ResultError ? error.statusCode : void 0;
3098
+ const isMissingRoute = (status === 404 || status === 405) && !(error instanceof ResultError && error.error === "STORAGE_NOT_FOUND");
3099
+ if (!isMissingRoute) {
2714
3100
  throw error;
2715
3101
  }
3102
+ return await this.http.post(
3103
+ `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
3104
+ { expiresIn }
3105
+ );
3106
+ }
3107
+ }
3108
+ /**
3109
+ * Create a signed URL for an object.
3110
+ *
3111
+ * Returns a time-limited, credential-free URL that can be handed directly to
3112
+ * a browser (`<img src>`), an email, or a third party — no SDK or session is
3113
+ * needed to fetch it. Authorization is enforced when the URL is minted (the
3114
+ * caller must be allowed to read the object), so the resulting link is a
3115
+ * pre-authorized capability scoped to this one object until it expires.
3116
+ *
3117
+ * @param path - The object key/path
3118
+ * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d).
3119
+ * Honored for private buckets; public buckets return their long-lived URL.
3120
+ */
3121
+ async createSignedUrl(path, expiresIn = 3600) {
3122
+ try {
3123
+ const strategy = await this.requestDownloadStrategy(path, expiresIn);
3124
+ return {
3125
+ data: {
3126
+ signedUrl: strategy.url,
3127
+ expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
3128
+ },
3129
+ error: null
3130
+ };
3131
+ } catch (error) {
2716
3132
  return {
2717
3133
  data: null,
2718
- error: error instanceof InsForgeError ? error : new InsForgeError(
2719
- error instanceof Error ? error.message : "Email send failed",
3134
+ error: error instanceof ResultError ? error : new ResultError(
3135
+ "Failed to create signed URL",
2720
3136
  500,
2721
- "EMAIL_ERROR"
3137
+ "STORAGE_ERROR"
2722
3138
  )
2723
3139
  };
2724
3140
  }
2725
3141
  }
2726
- };
2727
- function providerEnvironmentPath(provider, environment) {
2728
- return `/api/payments/${provider}/${encodeURIComponent(environment)}`;
2729
- }
2730
- var StripePayments = class {
2731
- constructor(http) {
2732
- this.http = http;
2733
- }
2734
3142
  /**
2735
- * Create a Stripe Checkout Session through the InsForge backend.
3143
+ * Create signed URLs for multiple objects in a single call.
2736
3144
  *
2737
- * @example
2738
- * ```typescript
2739
- * const { data, error } = await client.payments.stripe.createCheckoutSession('test', {
2740
- * mode: 'payment',
2741
- * lineItems: [{ priceId: 'price_123', quantity: 1 }],
2742
- * successUrl: `${window.location.origin}/success`,
2743
- * cancelUrl: `${window.location.origin}/pricing`
2744
- * });
3145
+ * Each entry resolves independently: a failure on one key (not found / not
3146
+ * permitted) is reported on that entry's `error` without failing the rest.
2745
3147
  *
2746
- * if (!error && data.checkoutSession.url) {
2747
- * window.location.assign(data.checkoutSession.url);
2748
- * }
2749
- * ```
3148
+ * @param paths - The object keys/paths
3149
+ * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
2750
3150
  */
2751
- async createCheckoutSession(environment, request) {
3151
+ async createSignedUrls(paths, expiresIn = 3600) {
2752
3152
  try {
2753
- const data = await this.http.post(
2754
- `${providerEnvironmentPath("stripe", environment)}/checkout-sessions`,
2755
- request,
2756
- { idempotent: !!request.idempotencyKey }
3153
+ const data = await Promise.all(
3154
+ paths.map(async (path) => {
3155
+ const { data: signed, error } = await this.createSignedUrl(
3156
+ path,
3157
+ expiresIn
3158
+ );
3159
+ return {
3160
+ path,
3161
+ signedUrl: signed?.signedUrl ?? null,
3162
+ error: error ? error.message : null
3163
+ };
3164
+ })
2757
3165
  );
2758
3166
  return { data, error: null };
2759
3167
  } catch (error) {
2760
- return wrapError(
2761
- error,
2762
- "Stripe checkout session creation failed"
2763
- );
3168
+ return {
3169
+ data: null,
3170
+ error: error instanceof ResultError ? error : new ResultError(
3171
+ "Failed to create signed URLs",
3172
+ 500,
3173
+ "STORAGE_ERROR"
3174
+ )
3175
+ };
2764
3176
  }
2765
3177
  }
2766
3178
  /**
2767
- * Create a Stripe Billing Portal Session for a mapped billing subject.
3179
+ * List objects in the bucket
3180
+ * @param prefix - Filter by key prefix
3181
+ * @param search - Search in file names
3182
+ * @param limit - Maximum number of results (default: 100, max: 1000)
3183
+ * @param offset - Number of results to skip
2768
3184
  */
2769
- async createCustomerPortalSession(environment, request) {
3185
+ async list(options) {
2770
3186
  try {
2771
- const data = await this.http.post(
2772
- `${providerEnvironmentPath("stripe", environment)}/customer-portal-sessions`,
2773
- request
3187
+ const params = {};
3188
+ if (options?.prefix) {
3189
+ params.prefix = options.prefix;
3190
+ }
3191
+ if (options?.search) {
3192
+ params.search = options.search;
3193
+ }
3194
+ if (options?.limit) {
3195
+ params.limit = options.limit.toString();
3196
+ }
3197
+ if (options?.offset) {
3198
+ params.offset = options.offset.toString();
3199
+ }
3200
+ const response = await this.http.get(
3201
+ `/api/storage/buckets/${this.bucketName}/objects`,
3202
+ { params }
2774
3203
  );
2775
- return { data, error: null };
3204
+ return { data: response, error: null };
2776
3205
  } catch (error) {
2777
- return wrapError(
2778
- error,
2779
- "Stripe customer portal session creation failed"
3206
+ return {
3207
+ data: null,
3208
+ error: error instanceof ResultError ? error : new ResultError("List failed", 500, "STORAGE_ERROR")
3209
+ };
3210
+ }
3211
+ }
3212
+ /**
3213
+ * Delete one or more files.
3214
+ *
3215
+ * A string uses the single-object endpoint. An array uses the batch endpoint,
3216
+ * which accepts at most 1000 keys and returns one result per key.
3217
+ *
3218
+ * @param pathOrPaths - One object key/path or an array of object keys/paths
3219
+ */
3220
+ async remove(pathOrPaths) {
3221
+ try {
3222
+ const response = Array.isArray(pathOrPaths) ? await this.http.delete(
3223
+ `/api/storage/buckets/${this.bucketName}/objects`,
3224
+ { body: { keys: pathOrPaths } }
3225
+ ) : await this.http.delete(
3226
+ `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(pathOrPaths)}`
2780
3227
  );
3228
+ return { data: response, error: null };
3229
+ } catch (error) {
3230
+ return {
3231
+ data: null,
3232
+ error: error instanceof ResultError ? error : new ResultError("Delete failed", 500, "STORAGE_ERROR")
3233
+ };
2781
3234
  }
2782
3235
  }
2783
3236
  };
2784
- var RazorpayPayments = class {
3237
+ var Storage = class {
2785
3238
  constructor(http) {
2786
3239
  this.http = http;
2787
3240
  }
2788
- async createOrder(environment, request) {
2789
- try {
2790
- const data = await this.http.post(
2791
- `${providerEnvironmentPath("razorpay", environment)}/orders`,
2792
- request
2793
- );
2794
- return { data, error: null };
2795
- } catch (error) {
2796
- return wrapError(error, "Razorpay order creation failed");
2797
- }
3241
+ http;
3242
+ /**
3243
+ * Get a bucket instance for operations
3244
+ * @param bucketName - Name of the bucket
3245
+ */
3246
+ from(bucketName) {
3247
+ return new StorageBucket(bucketName, this.http);
2798
3248
  }
2799
- async verifyOrder(environment, request) {
2800
- try {
2801
- const data = await this.http.post(
2802
- `${providerEnvironmentPath("razorpay", environment)}/orders/verify`,
2803
- request
2804
- );
2805
- return { data, error: null };
2806
- } catch (error) {
2807
- return wrapError(error, "Razorpay order verification failed");
2808
- }
3249
+ };
3250
+
3251
+ // src/modules/support.ts
3252
+ var DEFAULT_ENDPOINT2 = "https://beta.result.dev";
3253
+ var Support = class {
3254
+ handle;
3255
+ endpoint;
3256
+ // Fallback for non-browser runtimes and private mode.
3257
+ mem = { threads: [] };
3258
+ constructor(options) {
3259
+ this.handle = options?.handle ?? "";
3260
+ this.endpoint = (options?.endpoint || DEFAULT_ENDPOINT2).replace(/\/$/, "");
2809
3261
  }
2810
- async createSubscription(environment, request) {
2811
- try {
2812
- const data = await this.http.post(
2813
- `${providerEnvironmentPath("razorpay", environment)}/subscriptions`,
2814
- request
2815
- );
2816
- return { data, error: null };
2817
- } catch (error) {
2818
- return wrapError(
2819
- error,
2820
- "Razorpay subscription creation failed"
2821
- );
2822
- }
3262
+ /**
3263
+ * Start a new support conversation. The returned access key is stored
3264
+ * locally so `getThread`/`sendMessage` work without passing it around.
3265
+ */
3266
+ async startThread(options) {
3267
+ const guard = this.requireHandle();
3268
+ if (guard) return { data: null, error: guard };
3269
+ const { data, error } = await this.request(
3270
+ "POST",
3271
+ "/api/support/threads",
3272
+ {
3273
+ username: this.handle,
3274
+ email: options.email,
3275
+ name: options.name,
3276
+ message: options.message
3277
+ }
3278
+ );
3279
+ if (data) {
3280
+ this.remember(data.id, data.accessKey, options);
3281
+ }
3282
+ return { data, error };
3283
+ }
3284
+ /** Fetch a thread (status + full message history) with its stored access key. */
3285
+ async getThread(threadId, accessKey) {
3286
+ const guard = this.requireHandle();
3287
+ if (guard) return { data: null, error: guard };
3288
+ const key = accessKey ?? this.keyFor(threadId);
3289
+ if (!key) {
3290
+ return { data: null, error: this.missingKey(threadId) };
3291
+ }
3292
+ const { data, error } = await this.request(
3293
+ "GET",
3294
+ `/api/support/threads/${encodeURIComponent(threadId)}?key=${encodeURIComponent(key)}`
3295
+ );
3296
+ return { data: data?.thread ?? null, error };
3297
+ }
3298
+ /** Send a visitor reply on an existing thread (reopens it if closed). */
3299
+ async sendMessage(threadId, message, accessKey) {
3300
+ const guard = this.requireHandle();
3301
+ if (guard) return { data: null, error: guard };
3302
+ const key = accessKey ?? this.keyFor(threadId);
3303
+ if (!key) {
3304
+ return { data: null, error: this.missingKey(threadId) };
3305
+ }
3306
+ const { data, error } = await this.request(
3307
+ "POST",
3308
+ `/api/support/threads/${encodeURIComponent(threadId)}/messages`,
3309
+ { message, key }
3310
+ );
3311
+ return { data, error };
3312
+ }
3313
+ /** Threads started from this browser (ids only; fetch each with getThread). */
3314
+ listThreads() {
3315
+ return this.load().threads.map(({ id }) => ({ id }));
3316
+ }
3317
+ /** The contact details used when this browser last started a thread. */
3318
+ getContact() {
3319
+ return this.load().contact;
3320
+ }
3321
+ // -- internals -------------------------------------------------------------
3322
+ requireHandle() {
3323
+ if (this.handle) return null;
3324
+ return new ResultError(
3325
+ "Missing support handle. Pass support: { handle } to createClient().",
3326
+ 400,
3327
+ "MISSING_FIELD"
3328
+ );
2823
3329
  }
2824
- async verifySubscription(environment, request) {
2825
- try {
2826
- const data = await this.http.post(
2827
- `${providerEnvironmentPath("razorpay", environment)}/subscriptions/verify`,
2828
- request
2829
- );
2830
- return { data, error: null };
2831
- } catch (error) {
2832
- return wrapError(
2833
- error,
2834
- "Razorpay subscription verification failed"
2835
- );
2836
- }
3330
+ missingKey(threadId) {
3331
+ return new ResultError(
3332
+ `No access key stored for thread ${threadId}. Pass the accessKey returned by startThread().`,
3333
+ 401,
3334
+ "AUTH_UNAUTHORIZED"
3335
+ );
2837
3336
  }
2838
- async cancelSubscription(environment, subscriptionId, request = {}) {
3337
+ async request(method, path, body) {
2839
3338
  try {
2840
- const data = await this.http.post(
2841
- `${providerEnvironmentPath("razorpay", environment)}/subscriptions/${encodeURIComponent(
2842
- subscriptionId
2843
- )}/cancel`,
2844
- request
2845
- );
2846
- return { data, error: null };
3339
+ const response = await fetch(`${this.endpoint}${path}`, {
3340
+ method,
3341
+ headers: body ? { "Content-Type": "application/json" } : void 0,
3342
+ body: body ? JSON.stringify(body) : void 0
3343
+ });
3344
+ const payload = await response.json().catch(() => null);
3345
+ if (!response.ok) {
3346
+ return {
3347
+ data: null,
3348
+ error: new ResultError(
3349
+ payload?.error?.message ?? `Support request failed (${response.status})`,
3350
+ response.status,
3351
+ payload?.error?.code ?? "UNKNOWN_ERROR"
3352
+ )
3353
+ };
3354
+ }
3355
+ return { data: payload, error: null };
2847
3356
  } catch (error) {
2848
- return wrapError(
2849
- error,
2850
- "Razorpay subscription cancellation failed"
2851
- );
3357
+ if (error instanceof Error && error.name === "AbortError") {
3358
+ throw error;
3359
+ }
3360
+ return {
3361
+ data: null,
3362
+ error: new ResultError(
3363
+ error instanceof Error ? error.message : "Support request failed",
3364
+ 500,
3365
+ "UNKNOWN_ERROR"
3366
+ )
3367
+ };
2852
3368
  }
2853
3369
  }
2854
- async pauseSubscription(environment, subscriptionId) {
3370
+ storageKey() {
3371
+ return `rsup_${this.handle}`;
3372
+ }
3373
+ load() {
2855
3374
  try {
2856
- const data = await this.http.post(
2857
- `${providerEnvironmentPath("razorpay", environment)}/subscriptions/${encodeURIComponent(
2858
- subscriptionId
2859
- )}/pause`,
2860
- {}
2861
- );
2862
- return { data, error: null };
2863
- } catch (error) {
2864
- return wrapError(
2865
- error,
2866
- "Razorpay subscription pause failed"
2867
- );
3375
+ const raw = localStorage.getItem(this.storageKey());
3376
+ if (raw) {
3377
+ const parsed = JSON.parse(raw);
3378
+ if (parsed && Array.isArray(parsed.threads)) {
3379
+ return parsed;
3380
+ }
3381
+ }
3382
+ } catch {
2868
3383
  }
3384
+ return this.mem;
2869
3385
  }
2870
- async resumeSubscription(environment, subscriptionId) {
3386
+ save(state) {
3387
+ this.mem = state;
2871
3388
  try {
2872
- const data = await this.http.post(
2873
- `${providerEnvironmentPath("razorpay", environment)}/subscriptions/${encodeURIComponent(
2874
- subscriptionId
2875
- )}/resume`,
2876
- {}
2877
- );
2878
- return { data, error: null };
2879
- } catch (error) {
2880
- return wrapError(
2881
- error,
2882
- "Razorpay subscription resume failed"
2883
- );
3389
+ localStorage.setItem(this.storageKey(), JSON.stringify(state));
3390
+ } catch {
2884
3391
  }
2885
3392
  }
2886
- };
2887
- var Payments = class {
2888
- constructor(http) {
2889
- this.stripe = new StripePayments(http);
2890
- this.razorpay = new RazorpayPayments(http);
3393
+ keyFor(threadId) {
3394
+ return this.load().threads.find((thread) => thread.id === threadId)?.key;
3395
+ }
3396
+ remember(id, key, options) {
3397
+ const state = this.load();
3398
+ const threads = state.threads.filter((thread) => thread.id !== id);
3399
+ threads.unshift({ id, key });
3400
+ this.save({
3401
+ contact: {
3402
+ email: options.email,
3403
+ name: options.name ?? state.contact?.name
3404
+ },
3405
+ threads
3406
+ });
2891
3407
  }
2892
3408
  };
2893
- var InsForgeClient = class {
3409
+
3410
+ // src/client.ts
3411
+ var ResultClient = class {
3412
+ http;
3413
+ tokenManager;
3414
+ auth;
3415
+ database;
3416
+ storage;
3417
+ ai;
3418
+ functions;
3419
+ realtime;
3420
+ emails;
3421
+ analytics;
3422
+ support;
2894
3423
  constructor(config = {}) {
2895
3424
  const logger = new Logger(config.debug);
2896
3425
  this.tokenManager = new TokenManager();
@@ -2915,7 +3444,8 @@ var InsForgeClient = class {
2915
3444
  () => this.http.getValidAccessToken()
2916
3445
  );
2917
3446
  this.emails = new Emails(this.http);
2918
- this.payments = new Payments(this.http);
3447
+ this.analytics = new Analytics(config.analytics);
3448
+ this.support = new Support(config.support);
2919
3449
  }
2920
3450
  /**
2921
3451
  * Get the underlying HTTP client for custom requests
@@ -2939,16 +3469,16 @@ var InsForgeClient = class {
2939
3469
  *
2940
3470
  * Use this when an external auth provider (Better Auth, Clerk, Auth0,
2941
3471
  * WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
2942
- * long-lived InsForge client in sync. Without this, you'd have to call
3472
+ * long-lived Result client in sync. Without this, you'd have to call
2943
3473
  * `client.getHttpClient().setAuthToken(token)` AND reach into the private
2944
3474
  * realtime token manager separately.
2945
3475
  *
2946
3476
  * @example
2947
3477
  * ```typescript
2948
- * import { AuthChangeEvent } from '@insforge/sdk';
3478
+ * import { AuthChangeEvent } from '@resultdev/sdk';
2949
3479
  *
2950
3480
  * // Refresh a third-party-issued JWT periodically
2951
- * const { token } = await fetch('/api/insforge-token').then((r) => r.json());
3481
+ * const { token } = await fetch('/api/backend-token').then((r) => r.json());
2952
3482
  * client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
2953
3483
  *
2954
3484
  * // Sign-out
@@ -2963,17 +3493,11 @@ var InsForgeClient = class {
2963
3493
  this.tokenManager.setAccessToken(token, event);
2964
3494
  }
2965
3495
  }
2966
- /**
2967
- * Future modules will be added here:
2968
- * - database: Database operations
2969
- * - storage: File storage operations
2970
- * - functions: Serverless functions
2971
- * - tables: Table management
2972
- * - metadata: Backend metadata
2973
- */
2974
3496
  };
3497
+
3498
+ // src/index.ts
2975
3499
  function createClient(config = {}) {
2976
- return new InsForgeClient(config);
3500
+ return new ResultClient(config);
2977
3501
  }
2978
3502
  function createAdminClient(config) {
2979
3503
  const { apiKey: rawApiKey, ...clientConfig } = config ?? {};
@@ -2981,27 +3505,32 @@ function createAdminClient(config) {
2981
3505
  if (!apiKey) {
2982
3506
  throw new Error("Missing apiKey. Pass apiKey to createAdminClient().");
2983
3507
  }
2984
- return new InsForgeClient({
3508
+ return new ResultClient({
2985
3509
  ...clientConfig,
2986
3510
  accessToken: apiKey,
2987
3511
  isServerMode: true
2988
3512
  });
2989
3513
  }
3514
+ var src_default = ResultClient;
2990
3515
  export {
2991
3516
  AI,
3517
+ Analytics,
2992
3518
  Auth,
2993
3519
  AuthChangeEvent,
2994
3520
  Database,
3521
+ ERROR_CODES,
2995
3522
  Emails,
2996
3523
  Functions,
2997
3524
  HttpClient,
2998
- InsForgeClient,
2999
- InsForgeError,
3000
3525
  Logger,
3001
- Payments,
3526
+ OAUTH_PROVIDERS,
3002
3527
  Realtime,
3528
+ ResultClient,
3529
+ ResultError,
3003
3530
  Storage,
3004
3531
  StorageBucket,
3532
+ Support,
3005
3533
  createAdminClient,
3006
- createClient
3534
+ createClient,
3535
+ src_default as default
3007
3536
  };