@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/ssr.js CHANGED
@@ -1,20 +1,250 @@
1
- import {
2
- ERROR_CODES,
3
- oAuthProvidersSchema
4
- } from "./chunk-Y7VCTXPN.js";
1
+ // src/lib/jwt.ts
2
+ function decodeBase64Url(input) {
3
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
4
+ const padded = normalized.padEnd(
5
+ normalized.length + (4 - normalized.length % 4) % 4,
6
+ "="
7
+ );
8
+ const binary = atob(padded);
9
+ const bytes2 = Uint8Array.from(binary, (char) => char.charCodeAt(0));
10
+ return new TextDecoder().decode(bytes2);
11
+ }
12
+ function getJwtExpiration(token) {
13
+ if (!token) {
14
+ return null;
15
+ }
16
+ const [, payload] = token.split(".");
17
+ if (!payload) {
18
+ return null;
19
+ }
20
+ try {
21
+ const parsed = JSON.parse(decodeBase64Url(payload));
22
+ if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
23
+ return null;
24
+ }
25
+ return new Date(parsed.exp * 1e3);
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
31
+ if (!token) {
32
+ return false;
33
+ }
34
+ const expires = getJwtExpiration(token);
35
+ if (!expires) {
36
+ return true;
37
+ }
38
+ return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
39
+ }
5
40
 
6
- // ../../node_modules/.bun/@insforge+sdk@1.4.4/node_modules/@insforge/sdk/dist/ssr.mjs
7
- import { PostgrestClient } from "@supabase/postgrest-js";
8
- var InsForgeError = class _InsForgeError extends Error {
41
+ // src/ssr/cookies.ts
42
+ var DEFAULT_ACCESS_TOKEN_COOKIE = "result_access_token";
43
+ var DEFAULT_REFRESH_TOKEN_COOKIE = "result_refresh_token";
44
+ var EXPIRED_DATE = /* @__PURE__ */ new Date(0);
45
+ function getAccessTokenCookieName(names) {
46
+ return names?.accessToken ?? DEFAULT_ACCESS_TOKEN_COOKIE;
47
+ }
48
+ function getRefreshTokenCookieName(names) {
49
+ return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
50
+ }
51
+ function getCookieValue(cookies, name) {
52
+ if (!cookies) {
53
+ return null;
54
+ }
55
+ const value = cookies.get(name);
56
+ if (typeof value === "string") {
57
+ return value || null;
58
+ }
59
+ if (value && typeof value.value === "string") {
60
+ return value.value || null;
61
+ }
62
+ return null;
63
+ }
64
+ function getCookieValueFromHeader(cookieHeader, name) {
65
+ if (!cookieHeader) {
66
+ return null;
67
+ }
68
+ const parts = cookieHeader.split(";");
69
+ for (const part of parts) {
70
+ const [rawName, ...rawValue] = part.trim().split("=");
71
+ if (rawName !== name) {
72
+ continue;
73
+ }
74
+ try {
75
+ return decodeURIComponent(rawValue.join("="));
76
+ } catch {
77
+ return rawValue.join("=");
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function getBrowserCookie(name) {
83
+ if (typeof document === "undefined") {
84
+ return null;
85
+ }
86
+ return getCookieValueFromHeader(document.cookie, name);
87
+ }
88
+ function defaultCookieOptions() {
89
+ const secure = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : typeof location !== "undefined" && location.protocol === "https:";
90
+ return {
91
+ path: "/",
92
+ sameSite: "lax",
93
+ secure
94
+ };
95
+ }
96
+ function accessTokenCookieOptions(token, overrides) {
97
+ return {
98
+ ...defaultCookieOptions(),
99
+ httpOnly: false,
100
+ expires: getJwtExpiration(token) ?? void 0,
101
+ ...overrides
102
+ };
103
+ }
104
+ function refreshTokenCookieOptions(token, overrides) {
105
+ return {
106
+ ...defaultCookieOptions(),
107
+ httpOnly: true,
108
+ expires: getJwtExpiration(token) ?? void 0,
109
+ ...overrides
110
+ };
111
+ }
112
+ function expiredCookieOptions(overrides) {
113
+ const {
114
+ expires: _expires,
115
+ maxAge: _maxAge,
116
+ ...safeOverrides
117
+ } = overrides ?? {};
118
+ return {
119
+ ...defaultCookieOptions(),
120
+ ...safeOverrides,
121
+ expires: EXPIRED_DATE,
122
+ maxAge: 0
123
+ };
124
+ }
125
+ function setCookie(cookies, name, value, options) {
126
+ if (!cookies?.set) {
127
+ return;
128
+ }
129
+ cookies.set(name, value, options);
130
+ }
131
+ function deleteCookie(cookies, name, options) {
132
+ if (!cookies) {
133
+ return;
134
+ }
135
+ if (cookies.set) {
136
+ cookies.set(name, "", expiredCookieOptions(options));
137
+ return;
138
+ }
139
+ cookies.delete?.(name);
140
+ }
141
+ function serializeCookie(name, value, options = {}) {
142
+ const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
143
+ if (options.maxAge !== void 0) {
144
+ parts.push(`Max-Age=${options.maxAge}`);
145
+ }
146
+ if (options.domain) {
147
+ parts.push(`Domain=${options.domain}`);
148
+ }
149
+ if (options.path) {
150
+ parts.push(`Path=${options.path}`);
151
+ }
152
+ if (options.expires) {
153
+ parts.push(`Expires=${options.expires.toUTCString()}`);
154
+ }
155
+ if (options.httpOnly) {
156
+ parts.push("HttpOnly");
157
+ }
158
+ if (options.secure) {
159
+ parts.push("Secure");
160
+ }
161
+ if (options.sameSite) {
162
+ const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
163
+ parts.push(`SameSite=${sameSite}`);
164
+ }
165
+ return parts.join("; ");
166
+ }
167
+ function appendSetCookie(headers, name, value, options) {
168
+ headers.append("Set-Cookie", serializeCookie(name, value, options));
169
+ }
170
+ function setAuthCookies(cookies, tokens, settings = {}) {
171
+ const accessName = getAccessTokenCookieName(settings.names);
172
+ const refreshName = getRefreshTokenCookieName(settings.names);
173
+ const accessOptions = accessTokenCookieOptions(
174
+ tokens.accessToken,
175
+ settings.options?.accessToken
176
+ );
177
+ setCookie(cookies, accessName, tokens.accessToken, accessOptions);
178
+ if (tokens.refreshToken) {
179
+ setCookie(
180
+ cookies,
181
+ refreshName,
182
+ tokens.refreshToken,
183
+ refreshTokenCookieOptions(
184
+ tokens.refreshToken,
185
+ settings.options?.refreshToken
186
+ )
187
+ );
188
+ }
189
+ }
190
+ function clearAuthCookies(cookies, settings = {}) {
191
+ const accessName = getAccessTokenCookieName(settings.names);
192
+ const refreshName = getRefreshTokenCookieName(settings.names);
193
+ const accessOptions = expiredCookieOptions(settings.options?.accessToken);
194
+ const refreshOptions = expiredCookieOptions(settings.options?.refreshToken);
195
+ deleteCookie(cookies, accessName, accessOptions);
196
+ deleteCookie(cookies, refreshName, refreshOptions);
197
+ }
198
+ function setAuthCookieHeaders(headers, tokens, settings = {}) {
199
+ const accessName = getAccessTokenCookieName(settings.names);
200
+ const refreshName = getRefreshTokenCookieName(settings.names);
201
+ appendSetCookie(
202
+ headers,
203
+ accessName,
204
+ tokens.accessToken,
205
+ accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken)
206
+ );
207
+ if (tokens.refreshToken) {
208
+ appendSetCookie(
209
+ headers,
210
+ refreshName,
211
+ tokens.refreshToken,
212
+ refreshTokenCookieOptions(
213
+ tokens.refreshToken,
214
+ settings.options?.refreshToken
215
+ )
216
+ );
217
+ }
218
+ }
219
+ function clearAuthCookieHeaders(headers, settings = {}) {
220
+ appendSetCookie(
221
+ headers,
222
+ getAccessTokenCookieName(settings.names),
223
+ "",
224
+ expiredCookieOptions(settings.options?.accessToken)
225
+ );
226
+ appendSetCookie(
227
+ headers,
228
+ getRefreshTokenCookieName(settings.names),
229
+ "",
230
+ expiredCookieOptions(settings.options?.refreshToken)
231
+ );
232
+ }
233
+
234
+ // src/types.ts
235
+ var ResultError = class _ResultError extends Error {
236
+ statusCode;
237
+ error;
238
+ nextActions;
9
239
  constructor(message, statusCode, error, nextActions) {
10
240
  super(message);
11
- this.name = "InsForgeError";
241
+ this.name = "ResultError";
12
242
  this.statusCode = statusCode;
13
243
  this.error = error;
14
244
  this.nextActions = nextActions;
15
245
  }
16
246
  static fromApiError(apiError) {
17
- return new _InsForgeError(
247
+ return new _ResultError(
18
248
  apiError.message,
19
249
  apiError.statusCode,
20
250
  apiError.error,
@@ -22,7 +252,14 @@ var InsForgeError = class _InsForgeError extends Error {
22
252
  );
23
253
  }
24
254
  };
25
- var SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie", "set-cookie"];
255
+
256
+ // src/lib/logger.ts
257
+ var SENSITIVE_HEADERS = [
258
+ "authorization",
259
+ "x-api-key",
260
+ "cookie",
261
+ "set-cookie"
262
+ ];
26
263
  var SENSITIVE_BODY_KEYS = [
27
264
  "password",
28
265
  "token",
@@ -97,6 +334,9 @@ function formatBody(body) {
97
334
  }
98
335
  }
99
336
  var Logger = class {
337
+ /** Whether debug logging is currently enabled */
338
+ enabled;
339
+ customLog;
100
340
  /**
101
341
  * Creates a new Logger instance.
102
342
  * @param debug - Set to true to enable console logging, or pass a custom log function
@@ -119,7 +359,7 @@ var Logger = class {
119
359
  if (!this.enabled) {
120
360
  return;
121
361
  }
122
- const formatted = `[InsForge Debug] ${message}`;
362
+ const formatted = `[Result Debug] ${message}`;
123
363
  if (this.customLog) {
124
364
  this.customLog(formatted, ...args);
125
365
  } else {
@@ -135,7 +375,7 @@ var Logger = class {
135
375
  if (!this.enabled) {
136
376
  return;
137
377
  }
138
- const formatted = `[InsForge Debug] ${message}`;
378
+ const formatted = `[Result Debug] ${message}`;
139
379
  if (this.customLog) {
140
380
  this.customLog(formatted, ...args);
141
381
  } else {
@@ -151,7 +391,7 @@ var Logger = class {
151
391
  if (!this.enabled) {
152
392
  return;
153
393
  }
154
- const formatted = `[InsForge Debug] ${message}`;
394
+ const formatted = `[Result Debug] ${message}`;
155
395
  if (this.customLog) {
156
396
  this.customLog(formatted, ...args);
157
397
  } else {
@@ -176,7 +416,7 @@ var Logger = class {
176
416
  }
177
417
  const formattedBody = formatBody(sanitizeBody(body));
178
418
  if (formattedBody) {
179
- const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
419
+ const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
180
420
  parts.push(` Body: ${truncated}`);
181
421
  }
182
422
  this.log(parts.join("\n"));
@@ -197,7 +437,7 @@ var Logger = class {
197
437
  const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
198
438
  const formattedBody = formatBody(sanitizeBody(body));
199
439
  if (formattedBody) {
200
- const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
440
+ const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
201
441
  parts.push(` Body: ${truncated}`);
202
442
  }
203
443
  if (status >= 400) {
@@ -207,12 +447,14 @@ var Logger = class {
207
447
  }
208
448
  }
209
449
  };
450
+
451
+ // src/lib/token-manager.ts
210
452
  var AuthChangeEvent = {
211
453
  SIGNED_IN: "signedIn",
212
454
  SIGNED_OUT: "signedOut",
213
455
  TOKEN_REFRESHED: "tokenRefreshed"
214
456
  };
215
- var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
457
+ var CSRF_TOKEN_COOKIE = "result_csrf_token";
216
458
  function getCsrfToken() {
217
459
  if (typeof document === "undefined") {
218
460
  return null;
@@ -239,11 +481,10 @@ function clearCsrfToken() {
239
481
  document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
240
482
  }
241
483
  var TokenManager = class {
242
- constructor() {
243
- this.accessToken = null;
244
- this.user = null;
245
- this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
246
- }
484
+ // In-memory storage
485
+ accessToken = null;
486
+ user = null;
487
+ authStateChangeCallbacks = /* @__PURE__ */ new Map();
247
488
  /**
248
489
  * Save session in memory
249
490
  */
@@ -321,41 +562,8 @@ var TokenManager = class {
321
562
  }
322
563
  }
323
564
  };
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
- }
565
+
566
+ // src/lib/http-client.ts
359
567
  var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
360
568
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
361
569
  var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
@@ -385,7 +593,7 @@ async function parseResponse(response) {
385
593
  data = await response.text();
386
594
  }
387
595
  } catch (parseErr) {
388
- throw new InsForgeError(
596
+ throw new ResultError(
389
597
  `Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
390
598
  response.status,
391
599
  response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
@@ -393,8 +601,8 @@ async function parseResponse(response) {
393
601
  }
394
602
  if (!response.ok) {
395
603
  if (data && typeof data === "object" && "error" in data) {
396
- data.statusCode ?? (data.statusCode = data.status ?? response.status);
397
- const error = InsForgeError.fromApiError(data);
604
+ data.statusCode ??= data.status ?? response.status;
605
+ const error = ResultError.fromApiError(data);
398
606
  Object.keys(data).forEach((key) => {
399
607
  if (key !== "error" && key !== "message" && key !== "statusCode") {
400
608
  error[key] = data[key];
@@ -402,7 +610,7 @@ async function parseResponse(response) {
402
610
  });
403
611
  throw error;
404
612
  }
405
- throw new InsForgeError(
613
+ throw new ResultError(
406
614
  `Request failed: ${response.statusText}`,
407
615
  response.status,
408
616
  "REQUEST_FAILED"
@@ -411,6 +619,20 @@ async function parseResponse(response) {
411
619
  return data;
412
620
  }
413
621
  var HttpClient = class {
622
+ baseUrl;
623
+ fetch;
624
+ config;
625
+ defaultHeaders;
626
+ anonKey;
627
+ userToken = null;
628
+ logger;
629
+ isRefreshing = false;
630
+ refreshPromise = null;
631
+ tokenManager;
632
+ refreshToken = null;
633
+ timeout;
634
+ retryCount;
635
+ retryDelay;
414
636
  /**
415
637
  * Creates a new HttpClient instance.
416
638
  * @param config - SDK configuration including baseUrl, timeout, retry settings, and fetch implementation.
@@ -418,10 +640,6 @@ var HttpClient = class {
418
640
  * @param logger - Optional logger instance for request/response debugging.
419
641
  */
420
642
  constructor(config, tokenManager, logger) {
421
- this.userToken = null;
422
- this.isRefreshing = false;
423
- this.refreshPromise = null;
424
- this.refreshToken = null;
425
643
  this.config = config;
426
644
  this.baseUrl = config.baseUrl || "http://localhost:7130";
427
645
  this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
@@ -469,7 +687,7 @@ var HttpClient = class {
469
687
  * @returns Delay in milliseconds.
470
688
  */
471
689
  computeRetryDelay(attempt) {
472
- const base = this.retryDelay * Math.pow(2, attempt - 1);
690
+ const base = this.retryDelay * 2 ** (attempt - 1);
473
691
  const jitter = base * (0.85 + Math.random() * 0.3);
474
692
  return Math.round(jitter);
475
693
  }
@@ -477,19 +695,29 @@ var HttpClient = class {
477
695
  return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
478
696
  }
479
697
  async fetchWithRetry(args) {
480
- const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
481
- let lastError;
482
- for (let attempt = 0; attempt <= maxAttempts; attempt++) {
483
- if (attempt > 0) {
484
- const delay = this.computeRetryDelay(attempt);
485
- this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
698
+ const {
699
+ method,
700
+ url,
701
+ headers,
702
+ body,
703
+ fetchOptions,
704
+ callerSignal,
705
+ maxAttempts
706
+ } = args;
707
+ let lastError;
708
+ for (let attempt = 0; attempt <= maxAttempts; attempt++) {
709
+ if (attempt > 0) {
710
+ const delay = this.computeRetryDelay(attempt);
711
+ this.logger.warn(
712
+ `Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
713
+ );
486
714
  if (callerSignal?.aborted) {
487
715
  throw callerSignal.reason;
488
716
  }
489
717
  await new Promise((resolve, reject) => {
490
718
  const onAbort = () => {
491
719
  clearTimeout(timer2);
492
- reject(callerSignal.reason);
720
+ reject(callerSignal?.reason);
493
721
  };
494
722
  const timer2 = setTimeout(() => {
495
723
  if (callerSignal) {
@@ -507,13 +735,13 @@ var HttpClient = class {
507
735
  if (this.timeout > 0 || callerSignal) {
508
736
  controller = new AbortController();
509
737
  if (this.timeout > 0) {
510
- timer = setTimeout(() => controller.abort(), this.timeout);
738
+ timer = setTimeout(() => controller?.abort(), this.timeout);
511
739
  }
512
740
  if (callerSignal) {
513
741
  if (callerSignal.aborted) {
514
742
  controller.abort(callerSignal.reason);
515
743
  } else {
516
- const onCallerAbort = () => controller.abort(callerSignal.reason);
744
+ const onCallerAbort = () => controller?.abort(callerSignal.reason);
517
745
  callerSignal.addEventListener("abort", onCallerAbort, {
518
746
  once: true
519
747
  });
@@ -540,7 +768,7 @@ var HttpClient = class {
540
768
  clearTimeout(timer);
541
769
  }
542
770
  await response.body?.cancel();
543
- lastError = new InsForgeError(
771
+ lastError = new ResultError(
544
772
  `Server error: ${response.status} ${response.statusText}`,
545
773
  response.status,
546
774
  "SERVER_ERROR"
@@ -556,8 +784,8 @@ var HttpClient = class {
556
784
  clearTimeout(timer);
557
785
  }
558
786
  if (err?.name === "AbortError") {
559
- if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
560
- throw new InsForgeError(
787
+ if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
788
+ throw new ResultError(
561
789
  `Request timed out after ${this.timeout}ms`,
562
790
  408,
563
791
  "REQUEST_TIMEOUT"
@@ -569,14 +797,18 @@ var HttpClient = class {
569
797
  lastError = err;
570
798
  continue;
571
799
  }
572
- throw new InsForgeError(
800
+ throw new ResultError(
573
801
  `Network request failed: ${err?.message || "Unknown error"}`,
574
802
  0,
575
803
  "NETWORK_ERROR"
576
804
  );
577
805
  }
578
806
  }
579
- throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
807
+ throw lastError || new ResultError(
808
+ "Request failed after all retry attempts",
809
+ 0,
810
+ "NETWORK_ERROR"
811
+ );
580
812
  }
581
813
  /**
582
814
  * Performs an HTTP request with automatic retry and timeout handling.
@@ -586,7 +818,7 @@ var HttpClient = class {
586
818
  * @param path - API path relative to the base URL.
587
819
  * @param options - Optional request configuration including headers, body, and query params.
588
820
  * @returns Parsed response data.
589
- * @throws {InsForgeError} On timeout, network failure, or HTTP error responses.
821
+ * @throws {ResultError} On timeout, network failure, or HTTP error responses.
590
822
  */
591
823
  async handleRequest(method, path, options = {}, tokenOverride) {
592
824
  const {
@@ -606,14 +838,14 @@ var HttpClient = class {
606
838
  };
607
839
  const authToken = tokenOverride ?? this.userToken ?? this.anonKey;
608
840
  if (authToken) {
609
- requestHeaders["Authorization"] = `Bearer ${authToken}`;
841
+ requestHeaders.Authorization = `Bearer ${authToken}`;
610
842
  }
611
843
  const processedBody = serializeBody(method, body, requestHeaders);
612
844
  const setRequestHeader = (key, value) => {
613
845
  if (key.toLowerCase() === "authorization") {
614
- delete requestHeaders["Authorization"];
615
- delete requestHeaders["authorization"];
616
- requestHeaders["Authorization"] = value;
846
+ delete requestHeaders.Authorization;
847
+ delete requestHeaders.authorization;
848
+ requestHeaders.Authorization = value;
617
849
  return;
618
850
  }
619
851
  requestHeaders[key] = value;
@@ -645,7 +877,7 @@ var HttpClient = class {
645
877
  try {
646
878
  data = await parseResponse(response);
647
879
  } catch (err) {
648
- if (err instanceof InsForgeError) {
880
+ if (err instanceof ResultError) {
649
881
  this.logger.logResponse(
650
882
  method,
651
883
  url,
@@ -656,15 +888,31 @@ var HttpClient = class {
656
888
  }
657
889
  throw err;
658
890
  }
659
- this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
891
+ this.logger.logResponse(
892
+ method,
893
+ url,
894
+ response.status,
895
+ Date.now() - startTime,
896
+ data
897
+ );
660
898
  return data;
661
899
  }
662
900
  async request(method, path, options = {}) {
663
901
  const tokenUsed = this.userToken;
664
902
  try {
665
- return await this.handleRequest(method, path, { ...options }, tokenUsed);
903
+ return await this.handleRequest(
904
+ method,
905
+ path,
906
+ { ...options },
907
+ tokenUsed
908
+ );
666
909
  } catch (error) {
667
- if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
910
+ if (!(error instanceof ResultError) || !this.shouldRefreshAccessToken(
911
+ error.statusCode,
912
+ error.error,
913
+ tokenUsed,
914
+ options
915
+ )) {
668
916
  throw error;
669
917
  }
670
918
  if (tokenUsed !== this.userToken) {
@@ -684,7 +932,7 @@ var HttpClient = class {
684
932
  try {
685
933
  await this.refreshAndSaveSession();
686
934
  } catch (error2) {
687
- if (error2 instanceof InsForgeError && (error2.statusCode === 401 || error2.statusCode === 403)) {
935
+ if (error2 instanceof ResultError && (error2.statusCode === 401 || error2.statusCode === 403)) {
688
936
  this.clearAuthSession();
689
937
  }
690
938
  throw error2;
@@ -749,7 +997,12 @@ var HttpClient = class {
749
997
  callerSignal,
750
998
  maxAttempts
751
999
  });
752
- this.logger.logResponse(method, url, response.status, Date.now() - startTime);
1000
+ this.logger.logResponse(
1001
+ method,
1002
+ url,
1003
+ response.status,
1004
+ Date.now() - startTime
1005
+ );
753
1006
  let errorCode = null;
754
1007
  if (response.status === 401) {
755
1008
  try {
@@ -763,7 +1016,12 @@ var HttpClient = class {
763
1016
  } catch {
764
1017
  }
765
1018
  }
766
- if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
1019
+ if (!this.shouldRefreshAccessToken(
1020
+ response.status,
1021
+ errorCode,
1022
+ tokenUsed,
1023
+ options
1024
+ )) {
767
1025
  return response;
768
1026
  }
769
1027
  if (tokenUsed !== this.userToken) {
@@ -782,7 +1040,7 @@ var HttpClient = class {
782
1040
  try {
783
1041
  newTokenData = await this.refreshAndSaveSession();
784
1042
  } catch (error) {
785
- if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403)) {
1043
+ if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403)) {
786
1044
  this.clearAuthSession();
787
1045
  }
788
1046
  throw error;
@@ -827,7 +1085,7 @@ var HttpClient = class {
827
1085
  const headers = { ...this.defaultHeaders };
828
1086
  const authToken = this.userToken || this.anonKey;
829
1087
  if (authToken) {
830
- headers["Authorization"] = `Bearer ${authToken}`;
1088
+ headers.Authorization = `Bearer ${authToken}`;
831
1089
  }
832
1090
  return headers;
833
1091
  }
@@ -871,7 +1129,7 @@ var HttpClient = class {
871
1129
  const refreshed = await this.refreshAndSaveSession();
872
1130
  return refreshed.accessToken;
873
1131
  } catch (error) {
874
- if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
1132
+ if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
875
1133
  this.clearAuthSession();
876
1134
  }
877
1135
  throw error;
@@ -880,7 +1138,10 @@ var HttpClient = class {
880
1138
  async refreshAndSaveSession() {
881
1139
  const newTokenData = await this.refreshAccessToken();
882
1140
  this.setAuthToken(newTokenData.accessToken);
883
- this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
1141
+ this.tokenManager.saveSession(
1142
+ newTokenData,
1143
+ AuthChangeEvent.TOKEN_REFRESHED
1144
+ );
884
1145
  if (newTokenData.csrfToken) {
885
1146
  setCsrfToken(newTokenData.csrfToken);
886
1147
  }
@@ -896,1328 +1157,1404 @@ var HttpClient = class {
896
1157
  clearCsrfToken();
897
1158
  }
898
1159
  };
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 };
1160
+
1161
+ // src/modules/ai.ts
1162
+ var AI = class {
1163
+ chat;
1164
+ images;
1165
+ embeddings;
1166
+ constructor(http) {
1167
+ this.chat = new Chat(http);
1168
+ this.images = new Images(http);
1169
+ this.embeddings = new Embeddings(http);
946
1170
  }
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;
1171
+ };
1172
+ var Chat = class {
1173
+ completions;
1174
+ constructor(http) {
1175
+ this.completions = new ChatCompletions(http);
959
1176
  }
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 = {}) {
1177
+ };
1178
+ var ChatCompletions = class {
1179
+ constructor(http) {
966
1180
  this.http = http;
967
- this.tokenManager = tokenManager;
968
- this.options = options;
969
- this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
970
- }
971
- isServerMode() {
972
- return !!this.options.isServerMode;
973
- }
974
- /** Subscribe to SDK authentication state changes. */
975
- onAuthStateChange(callback) {
976
- return this.tokenManager.onAuthStateChange(callback);
977
1181
  }
978
- /**
979
- * Save session from API response
980
- * Handles token storage, CSRF token, and HTTP auth header
981
- */
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
1182
+ http;
1183
+ async create(params) {
1184
+ const backendParams = {
1185
+ model: params.model,
1186
+ messages: params.messages,
1187
+ temperature: params.temperature,
1188
+ maxTokens: params.maxTokens,
1189
+ topP: params.topP,
1190
+ stream: params.stream,
1191
+ // New plugin options
1192
+ webSearch: params.webSearch,
1193
+ fileParser: params.fileParser,
1194
+ thinking: params.thinking,
1195
+ // Tool calling options
1196
+ tools: params.tools,
1197
+ toolChoice: params.toolChoice,
1198
+ parallelToolCalls: params.parallelToolCalls
989
1199
  };
990
- if (!this.isServerMode() && response.csrfToken) {
991
- setCsrfToken(response.csrfToken);
992
- }
993
- if (!this.isServerMode()) {
994
- this.tokenManager.saveSession(session, event);
995
- }
996
- this.http.setAuthToken(response.accessToken);
997
- this.http.setRefreshToken(response.refreshToken ?? null);
998
- return true;
999
- }
1000
- // ============================================================================
1001
- // OAuth Callback Detection (runs on initialization)
1002
- // ============================================================================
1003
- /**
1004
- * Detect and handle OAuth callback parameters in URL
1005
- * Supports PKCE flow (insforge_code)
1006
- */
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;
1018
- }
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);
1200
+ if (params.stream) {
1201
+ const headers = this.http.getHeaders();
1202
+ headers["Content-Type"] = "application/json";
1203
+ const response2 = await this.http.fetch(
1204
+ `${this.http.baseUrl}/api/ai/chat/completion`,
1205
+ {
1206
+ method: "POST",
1207
+ headers,
1208
+ body: JSON.stringify(backendParams)
1025
1209
  }
1026
- return;
1027
- }
1028
- } catch (error) {
1029
- console.debug("OAuth callback detection skipped:", error);
1030
- }
1031
- }
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
1210
  );
1042
- if (response.accessToken && response.user) {
1043
- this.saveSessionFromResponse(response);
1044
- }
1045
- if (response.refreshToken) {
1046
- this.http.setRefreshToken(response.refreshToken);
1211
+ if (!response2.ok) {
1212
+ const error = await response2.json();
1213
+ throw new Error(error.error || "Stream request failed");
1047
1214
  }
1048
- return { data: response, error: null };
1049
- } catch (error) {
1050
- return wrapError(error, "An unexpected error occurred during sign up");
1215
+ return this.parseSSEStream(response2, params.model);
1051
1216
  }
1217
+ const response = await this.http.post(
1218
+ "/api/ai/chat/completion",
1219
+ backendParams
1220
+ );
1221
+ const content = response.text || "";
1222
+ return {
1223
+ id: `chatcmpl-${Date.now()}`,
1224
+ object: "chat.completion",
1225
+ created: Math.floor(Date.now() / 1e3),
1226
+ model: response.metadata?.model,
1227
+ choices: [
1228
+ {
1229
+ index: 0,
1230
+ message: {
1231
+ role: "assistant",
1232
+ content,
1233
+ // Include tool_calls if present (from tool calling)
1234
+ ...response.tool_calls?.length && {
1235
+ tool_calls: response.tool_calls
1236
+ },
1237
+ // Include annotations if present (from web search or file parsing)
1238
+ ...response.annotations?.length && {
1239
+ annotations: response.annotations
1240
+ }
1241
+ },
1242
+ finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
1243
+ }
1244
+ ],
1245
+ usage: {
1246
+ prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1247
+ completion_tokens: response.metadata?.usage?.completionTokens || 0,
1248
+ total_tokens: response.metadata?.usage?.totalTokens || 0
1249
+ }
1250
+ };
1052
1251
  }
1053
- async signInWithPassword(request) {
1054
- 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 }
1252
+ /**
1253
+ * Parse SSE stream into async iterable of OpenAI-like chunks
1254
+ */
1255
+ async *parseSSEStream(response, model) {
1256
+ const reader = response.body?.getReader();
1257
+ if (!reader) {
1258
+ throw new ResultError(
1259
+ "Streaming response has no body",
1260
+ 500,
1261
+ "AI_UPSTREAM_UNAVAILABLE"
1059
1262
  );
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");
1067
1263
  }
1068
- }
1069
- async signOut() {
1264
+ const decoder = new TextDecoder();
1265
+ let buffer = "";
1070
1266
  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 } } : {}
1267
+ while (true) {
1268
+ const { done, value } = await reader.read();
1269
+ if (done) {
1270
+ break;
1271
+ }
1272
+ buffer += decoder.decode(value, { stream: true });
1273
+ const lines = buffer.split("\n");
1274
+ buffer = lines.pop() || "";
1275
+ for (const line of lines) {
1276
+ if (line.startsWith("data: ")) {
1277
+ const dataStr = line.slice(6).trim();
1278
+ if (dataStr) {
1279
+ try {
1280
+ const data = JSON.parse(dataStr);
1281
+ if (data.chunk || data.content) {
1282
+ yield {
1283
+ id: `chatcmpl-${Date.now()}`,
1284
+ object: "chat.completion.chunk",
1285
+ created: Math.floor(Date.now() / 1e3),
1286
+ model,
1287
+ choices: [
1288
+ {
1289
+ index: 0,
1290
+ delta: {
1291
+ content: data.chunk || data.content
1292
+ },
1293
+ finish_reason: null
1294
+ }
1295
+ ]
1296
+ };
1297
+ }
1298
+ if (data.tool_calls?.length) {
1299
+ yield {
1300
+ id: `chatcmpl-${Date.now()}`,
1301
+ object: "chat.completion.chunk",
1302
+ created: Math.floor(Date.now() / 1e3),
1303
+ model,
1304
+ choices: [
1305
+ {
1306
+ index: 0,
1307
+ delta: {
1308
+ tool_calls: data.tool_calls
1309
+ },
1310
+ finish_reason: "tool_calls"
1311
+ }
1312
+ ]
1313
+ };
1314
+ }
1315
+ if (data.done) {
1316
+ reader.releaseLock();
1317
+ return;
1318
+ }
1319
+ } catch {
1320
+ console.warn("Failed to parse SSE data:", dataStr);
1321
+ }
1322
+ }
1081
1323
  }
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 };
1092
- } catch {
1093
- return {
1094
- error: new InsForgeError("Failed to sign out", 500, "SIGNOUT_ERROR")
1095
- };
1096
- }
1097
- }
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
1138
- });
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 };
1324
+ }
1150
1325
  }
1151
- return {
1152
- data: {},
1153
- error: new InsForgeError(
1154
- "An unexpected error occurred during OAuth initialization",
1155
- 500,
1156
- "UNEXPECTED_ERROR"
1157
- )
1158
- };
1326
+ } finally {
1327
+ reader.releaseLock();
1159
1328
  }
1160
1329
  }
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");
1194
- }
1330
+ };
1331
+ var Embeddings = class {
1332
+ constructor(http) {
1333
+ this.http = http;
1195
1334
  }
1335
+ http;
1196
1336
  /**
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.
1337
+ * Create embeddings for text input - OpenAI-like response format
1199
1338
  *
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");
1221
- }
1222
- }
1223
- // ============================================================================
1224
- // Session Management
1225
- // ============================================================================
1226
- /**
1227
- * Refresh the current auth session.
1339
+ * @example
1340
+ * ```typescript
1341
+ * // Single text input
1342
+ * const response = await client.ai.embeddings.create({
1343
+ * model: 'openai/text-embedding-3-small',
1344
+ * input: 'Hello world'
1345
+ * });
1346
+ * console.log(response.data[0].embedding); // number[]
1228
1347
  *
1229
- * Browser mode:
1230
- * - Uses httpOnly refresh cookie and optional CSRF header.
1348
+ * // Multiple text inputs
1349
+ * const response = await client.ai.embeddings.create({
1350
+ * model: 'openai/text-embedding-3-small',
1351
+ * input: ['Hello world', 'Goodbye world']
1352
+ * });
1353
+ * response.data.forEach((item, i) => {
1354
+ * console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
1355
+ * });
1231
1356
  *
1232
- * Legacy server mode (`isServerMode: true`):
1233
- * - Uses mobile auth flow and requires `refreshToken` in request body.
1357
+ * // With custom dimensions (if supported by model)
1358
+ * const response = await client.ai.embeddings.create({
1359
+ * model: 'openai/text-embedding-3-small',
1360
+ * input: 'Hello world',
1361
+ * dimensions: 256
1362
+ * });
1234
1363
  *
1235
- * SSR apps should prefer `createRefreshAuthRouter()` / `refreshAuth()` from
1236
- * `@insforge/sdk/ssr`.
1364
+ * // With base64 encoding format
1365
+ * const response = await client.ai.embeddings.create({
1366
+ * model: 'openai/text-embedding-3-small',
1367
+ * input: 'Hello world',
1368
+ * encoding_format: 'base64'
1369
+ * });
1370
+ * ```
1237
1371
  */
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
1258
- }
1259
- );
1260
- if (response.accessToken) {
1261
- this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
1372
+ async create(params) {
1373
+ const response = await this.http.post(
1374
+ "/api/ai/embeddings",
1375
+ params
1376
+ );
1377
+ return {
1378
+ object: response.object,
1379
+ data: response.data,
1380
+ model: response.metadata?.model,
1381
+ usage: response.metadata?.usage ? {
1382
+ prompt_tokens: response.metadata.usage.promptTokens || 0,
1383
+ total_tokens: response.metadata.usage.totalTokens || 0
1384
+ } : {
1385
+ prompt_tokens: 0,
1386
+ total_tokens: 0
1262
1387
  }
1263
- return { data: response, error: null };
1264
- } catch (error) {
1265
- return wrapError(error, "An unexpected error occurred during session refresh");
1266
- }
1388
+ };
1389
+ }
1390
+ };
1391
+ var Images = class {
1392
+ constructor(http) {
1393
+ this.http = http;
1267
1394
  }
1395
+ http;
1268
1396
  /**
1269
- * Get current user, automatically waits for pending OAuth callback
1397
+ * Generate images - OpenAI-like response format
1398
+ *
1399
+ * @example
1400
+ * ```typescript
1401
+ * // Text-to-image
1402
+ * const response = await client.ai.images.generate({
1403
+ * model: 'dall-e-3',
1404
+ * prompt: 'A sunset over mountains',
1405
+ * });
1406
+ * console.log(response.data[0].b64_json);
1407
+ *
1408
+ * // Image-to-image (with input images)
1409
+ * const response = await client.ai.images.generate({
1410
+ * model: 'stable-diffusion-xl',
1411
+ * prompt: 'Transform this into a watercolor painting',
1412
+ * images: [
1413
+ * { url: 'https://example.com/input.jpg' },
1414
+ * // or base64-encoded Data URI:
1415
+ * { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
1416
+ * ]
1417
+ * });
1418
+ * ```
1270
1419
  */
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 };
1288
- }
1289
- if (typeof window !== "undefined") {
1290
- const { data: refreshed, error: refreshError } = await this.refreshSession();
1291
- if (refreshError) {
1292
- return { data: { user: null }, error: refreshError };
1293
- }
1294
- if (refreshed?.accessToken) {
1295
- return { data: { user: refreshed.user ?? null }, error: null };
1420
+ async generate(params) {
1421
+ const response = await this.http.post(
1422
+ "/api/ai/image/generation",
1423
+ params
1424
+ );
1425
+ let data = [];
1426
+ if (response.images && response.images.length > 0) {
1427
+ data = response.images.map((img) => ({
1428
+ b64_json: img.imageUrl.replace(/^data:image\/\w+;base64,/, ""),
1429
+ content: response.text
1430
+ }));
1431
+ } else if (response.text) {
1432
+ data = [{ content: response.text }];
1433
+ }
1434
+ return {
1435
+ created: Math.floor(Date.now() / 1e3),
1436
+ data,
1437
+ ...response.metadata?.usage && {
1438
+ usage: {
1439
+ total_tokens: response.metadata.usage.totalTokens || 0,
1440
+ input_tokens: response.metadata.usage.promptTokens || 0,
1441
+ output_tokens: response.metadata.usage.completionTokens || 0
1296
1442
  }
1297
1443
  }
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
- };
1444
+ };
1445
+ }
1446
+ };
1447
+
1448
+ // src/modules/analytics.ts
1449
+ var DEFAULT_ENDPOINT = "https://beta.result.dev/api/wa";
1450
+ var IDLE_MS = 30 * 60 * 1e3;
1451
+ var Analytics = class {
1452
+ siteId;
1453
+ endpoint;
1454
+ options;
1455
+ active = false;
1456
+ lastUrl = null;
1457
+ left = false;
1458
+ referrer = "";
1459
+ // localStorage with an in-memory fallback for private-mode browsers.
1460
+ mem = {};
1461
+ constructor(options) {
1462
+ this.options = options ?? { siteId: "" };
1463
+ this.siteId = this.options.siteId ?? "";
1464
+ this.endpoint = this.options.endpoint || DEFAULT_ENDPOINT;
1465
+ if (!this.siteId || typeof window === "undefined" || navigator.webdriver) {
1466
+ return;
1467
+ }
1468
+ if (window.rwa) {
1469
+ return;
1470
+ }
1471
+ if (this.read("rwa_disable") === "1") {
1472
+ return;
1473
+ }
1474
+ const domains = (this.options.domains ?? []).map(
1475
+ (domain) => domain.trim().toLowerCase().replace(/^www\./, "")
1476
+ ).filter(Boolean);
1477
+ const here = location.hostname.toLowerCase().replace(/^www\./, "");
1478
+ if (domains.length && domains.indexOf(here) === -1) {
1479
+ return;
1480
+ }
1481
+ this.active = true;
1482
+ this.referrer = document.referrer || "";
1483
+ window.rwa = {
1484
+ track: (name, props) => this.track(name, props),
1485
+ identify: (id) => this.identify(id)
1486
+ };
1487
+ const doc = document;
1488
+ if (doc.prerendering) {
1489
+ document.addEventListener("prerenderingchange", () => this.start(), {
1490
+ once: true
1491
+ });
1492
+ } else {
1493
+ this.start();
1311
1494
  }
1312
1495
  }
1313
- // ============================================================================
1314
- // Profile Management
1315
- // ============================================================================
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");
1496
+ /** Send a custom event. No-op outside the browser or when inactive. */
1497
+ track(name, props) {
1498
+ if (!this.active || typeof name !== "string" || !name) {
1499
+ return;
1500
+ }
1501
+ const event = {
1502
+ e: "ev",
1503
+ n: name.slice(0, 64),
1504
+ dt: document.title
1505
+ };
1506
+ if (props && typeof props === "object") {
1507
+ event.p = props;
1322
1508
  }
1509
+ this.post(event);
1323
1510
  }
1324
- async setProfile(profile) {
1325
- 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
- });
1335
- }
1336
- return { data: response, error: null };
1337
- } catch (error) {
1338
- return wrapError(error, "An unexpected error occurred while updating user profile");
1511
+ /** Attach your own user id to subsequent events. */
1512
+ identify(id) {
1513
+ if (!this.active || typeof id !== "string" || !id) {
1514
+ return;
1339
1515
  }
1516
+ this.write("rwa_uid", id.slice(0, 64));
1340
1517
  }
1341
- // ============================================================================
1342
- // Email Verification
1343
- // ============================================================================
1344
- async resendVerificationEmail(request) {
1518
+ // -- internals -------------------------------------------------------------
1519
+ read(key) {
1345
1520
  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");
1521
+ return localStorage.getItem(key) || this.mem[key] || "";
1522
+ } catch {
1523
+ return this.mem[key] || "";
1352
1524
  }
1353
1525
  }
1354
- async verifyEmail(request) {
1526
+ write(key, value) {
1527
+ this.mem[key] = value;
1355
1528
  try {
1356
- const response = await this.http.post(
1357
- this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
1358
- request,
1359
- { credentials: "include", skipAuthRefresh: true }
1360
- );
1361
- this.saveSessionFromResponse(response);
1362
- if (response.refreshToken) {
1363
- this.http.setRefreshToken(response.refreshToken);
1364
- }
1365
- return { data: response, error: null };
1366
- } catch (error) {
1367
- return wrapError(error, "An unexpected error occurred while verifying email");
1529
+ localStorage.setItem(key, value);
1530
+ } catch {
1368
1531
  }
1369
1532
  }
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
1533
+ visitor() {
1534
+ let visitor = this.read("rwa_id");
1535
+ if (!visitor) {
1536
+ visitor = uuid4();
1537
+ this.write("rwa_id", visitor);
1538
+ }
1539
+ return visitor;
1540
+ }
1541
+ session() {
1542
+ const parts = this.read("rwa_s").split(".");
1543
+ let id = parts[0];
1544
+ const last = Number(parts[1]) || 0;
1545
+ const now = Date.now();
1546
+ if (!id || now - last > IDLE_MS) {
1547
+ id = uuid7();
1548
+ }
1549
+ this.write("rwa_s", `${id}.${now}`);
1550
+ return id;
1551
+ }
1552
+ pageUrl() {
1553
+ let url = location.href;
1554
+ if (this.options.excludeHash) {
1555
+ url = url.split("#")[0];
1556
+ }
1557
+ if (this.options.excludeSearch) {
1558
+ const hash = this.options.excludeHash ? "" : url.split("#")[1] || "";
1559
+ url = url.split("#")[0].split("?")[0] + (hash ? `#${hash}` : "");
1560
+ }
1561
+ return url.slice(0, 4096);
1562
+ }
1563
+ post(event) {
1564
+ event.u = event.u || this.pageUrl();
1565
+ event.d = this.visitor();
1566
+ event.s = this.session();
1567
+ event.t = Date.now();
1568
+ event.w = innerWidth;
1569
+ event.h = innerHeight;
1570
+ event.sw = screen.width;
1571
+ event.sh = screen.height;
1572
+ const uid = this.read("rwa_uid");
1573
+ if (uid) {
1574
+ event.uid = uid;
1575
+ }
1576
+ const body = JSON.stringify({ site: this.siteId, events: [event] });
1577
+ if (!navigator.sendBeacon?.(this.endpoint, body)) {
1578
+ fetch(this.endpoint, {
1579
+ method: "POST",
1580
+ body,
1581
+ keepalive: true,
1582
+ mode: "no-cors"
1583
+ }).catch(() => {
1377
1584
  });
1378
- return { data: response, error: null };
1379
- } catch (error) {
1380
- return wrapError(error, "An unexpected error occurred while sending password reset email");
1381
1585
  }
1382
1586
  }
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");
1587
+ pageview() {
1588
+ const url = this.pageUrl();
1589
+ if (url === this.lastUrl) {
1590
+ return;
1393
1591
  }
1592
+ const from = this.lastUrl ? this.lastUrl : this.referrer;
1593
+ this.lastUrl = url;
1594
+ this.left = false;
1595
+ this.post({ e: "pv", u: url, r: from, dt: document.title });
1394
1596
  }
1395
- async resetPassword(request) {
1396
- try {
1397
- const response = await this.http.post(
1398
- "/api/auth/email/reset-password",
1399
- 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");
1597
+ pageleave() {
1598
+ if (this.left || !this.lastUrl) {
1599
+ return;
1405
1600
  }
1601
+ this.left = true;
1602
+ this.post({ e: "pl", u: this.lastUrl });
1406
1603
  }
1407
- // ============================================================================
1408
- // Configuration
1409
- // ============================================================================
1410
- async getPublicAuthConfig() {
1411
- try {
1412
- const response = await this.http.get("/api/auth/public-config", {
1413
- skipAuthRefresh: true
1604
+ start() {
1605
+ const onNav = () => {
1606
+ setTimeout(() => this.pageview(), 0);
1607
+ };
1608
+ if (this.options.autoTrack !== false) {
1609
+ const push = history.pushState;
1610
+ const replace = history.replaceState;
1611
+ history.pushState = function(...args) {
1612
+ push.apply(this, args);
1613
+ onNav();
1614
+ };
1615
+ history.replaceState = function(...args) {
1616
+ replace.apply(this, args);
1617
+ onNav();
1618
+ };
1619
+ addEventListener("popstate", onNav);
1620
+ addEventListener("hashchange", onNav);
1621
+ addEventListener("pageshow", (event) => {
1622
+ if (event.persisted) {
1623
+ this.lastUrl = null;
1624
+ this.pageview();
1625
+ }
1414
1626
  });
1415
- return { data: response, error: null };
1416
- } catch (error) {
1417
- return wrapError(error, "An unexpected error occurred while fetching auth configuration");
1627
+ this.pageview();
1418
1628
  }
1629
+ addEventListener("pagehide", () => this.pageleave());
1630
+ document.addEventListener("visibilitychange", () => {
1631
+ if (document.visibilityState === "hidden") {
1632
+ this.pageleave();
1633
+ } else {
1634
+ this.left = false;
1635
+ }
1636
+ });
1637
+ document.addEventListener(
1638
+ "click",
1639
+ (clickEvent) => {
1640
+ const source = clickEvent.target;
1641
+ const target = source?.closest ? source.closest("[data-wa-event]") : null;
1642
+ if (!target) {
1643
+ return;
1644
+ }
1645
+ const name = target.getAttribute("data-wa-event");
1646
+ if (!name) {
1647
+ return;
1648
+ }
1649
+ const props = {};
1650
+ let any = false;
1651
+ for (const attr of Array.from(target.attributes)) {
1652
+ if (attr.name.indexOf("data-wa-event-") === 0) {
1653
+ props[attr.name.slice(14)] = attr.value;
1654
+ any = true;
1655
+ }
1656
+ }
1657
+ this.track(name, any ? props : void 0);
1658
+ },
1659
+ true
1660
+ );
1419
1661
  }
1420
1662
  };
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
- };
1663
+ function bytes() {
1664
+ const b = new Uint8Array(16);
1665
+ crypto.getRandomValues(b);
1666
+ return b;
1439
1667
  }
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
- });
1668
+ function format(b, version) {
1669
+ b[6] = b[6] & 15 | version << 4;
1670
+ b[8] = b[8] & 63 | 128;
1671
+ let hex = "";
1672
+ for (let i = 0; i < 16; i++) {
1673
+ hex += (b[i] + 256).toString(16).slice(1);
1674
+ if (i === 3 || i === 5 || i === 7 || i === 9) hex += "-";
1675
+ }
1676
+ return hex;
1677
+ }
1678
+ function uuid4() {
1679
+ return format(bytes(), 4);
1680
+ }
1681
+ function uuid7() {
1682
+ const b = bytes();
1683
+ let t = Date.now();
1684
+ for (let i = 5; i >= 0; i--) {
1685
+ b[i] = t & 255;
1686
+ t = Math.floor(t / 256);
1687
+ }
1688
+ return format(b, 7);
1689
+ }
1690
+
1691
+ // src/types/api.ts
1692
+ var ERROR_CODE_VALUES = [
1693
+ // Auth
1694
+ "AUTH_INVALID_EMAIL",
1695
+ "AUTH_WEAK_PASSWORD",
1696
+ "AUTH_INVALID_CREDENTIALS",
1697
+ "AUTH_INVALID_API_KEY",
1698
+ "AUTH_EMAIL_EXISTS",
1699
+ "AUTH_USER_NOT_FOUND",
1700
+ "AUTH_OAUTH_CONFIG_ALREADY_EXISTS",
1701
+ "AUTH_OAUTH_CONFIG_ERROR",
1702
+ "AUTH_OAUTH_CONFIG_NOT_FOUND",
1703
+ "AUTH_UNSUPPORTED_PROVIDER",
1704
+ "AUTH_TOKEN_EXPIRED",
1705
+ "AUTH_UNAUTHORIZED",
1706
+ "AUTH_NEED_VERIFICATION",
1707
+ "AUTH_SIGNUP_DISABLED",
1708
+ "AUTH_VERIFICATION_EMAIL_DELIVERY_FAILED",
1709
+ // Database
1710
+ "DATABASE_INVALID_PARAMETER",
1711
+ "DATABASE_VALIDATION_ERROR",
1712
+ "DATABASE_CONSTRAINT_VIOLATION",
1713
+ "DATABASE_NOT_FOUND",
1714
+ "DATABASE_DUPLICATE",
1715
+ "DATABASE_MIGRATION_ALREADY_EXISTS",
1716
+ "DATABASE_PERMISSION_DENIED",
1717
+ "DATABASE_INTERNAL_ERROR",
1718
+ "DATABASE_FORBIDDEN",
1719
+ // Storage
1720
+ "STORAGE_ALREADY_EXISTS",
1721
+ "STORAGE_INVALID_PARAMETER",
1722
+ "STORAGE_INVALID_FILE_TYPE",
1723
+ "STORAGE_INSUFFICIENT_QUOTA",
1724
+ "STORAGE_NOT_FOUND",
1725
+ "STORAGE_PERMISSION_DENIED",
1726
+ "S3_ACCESS_KEY_LIMIT_EXCEEDED",
1727
+ "S3_ACCESS_KEY_NOT_FOUND",
1728
+ "S3_PROTOCOL_UNAVAILABLE",
1729
+ // Realtime
1730
+ "REALTIME_CHANNEL_NOT_FOUND",
1731
+ "REALTIME_CONNECTION_FAILED",
1732
+ "REALTIME_INVALID_CHANNEL_REQUEST",
1733
+ "REALTIME_INVALID_CHANNEL_PATTERN",
1734
+ "REALTIME_INVALID_EVENT",
1735
+ "REALTIME_NOT_SUBSCRIBED",
1736
+ "REALTIME_UNAUTHORIZED",
1737
+ // AI
1738
+ "AI_INVALID_API_KEY",
1739
+ "AI_INVALID_MODEL",
1740
+ "AI_UPSTREAM_UNAVAILABLE",
1741
+ // Analytics
1742
+ "ANALYTICS_NOT_CONNECTED",
1743
+ "ANALYTICS_UNAVAILABLE",
1744
+ // Logs
1745
+ "LOGS_AWS_NOT_CONFIGURED",
1746
+ "LOG_NOT_FOUND",
1747
+ // Compute
1748
+ "COMPUTE_CLOUD_UNAVAILABLE",
1749
+ "COMPUTE_NOT_CONFIGURED",
1750
+ "COMPUTE_PROVIDER_ERROR",
1751
+ "COMPUTE_SERVICE_NOT_FOUND",
1752
+ "COMPUTE_MACHINE_NOT_FOUND",
1753
+ "COMPUTE_SERVICE_NOT_CONFIGURED",
1754
+ "COMPUTE_SERVICE_DEPLOY_FAILED",
1755
+ "COMPUTE_SERVICE_ALREADY_EXISTS",
1756
+ "COMPUTE_SERVICE_START_FAILED",
1757
+ "COMPUTE_SERVICE_STOP_FAILED",
1758
+ "COMPUTE_SERVICE_DELETE_FAILED",
1759
+ "COMPUTE_REGION_CHANGE_NOT_SUPPORTED",
1760
+ "COMPUTE_QUOTA_EXCEEDED",
1761
+ // Billing
1762
+ "BILLING_INSUFFICIENT_BALANCE",
1763
+ // Email
1764
+ "EMAIL_PROVIDER_NOT_CONFIGURED",
1765
+ "EMAIL_SMTP_CONNECTION_FAILED",
1766
+ "EMAIL_SMTP_SEND_FAILED",
1767
+ "EMAIL_TEMPLATE_NOT_FOUND",
1768
+ // Deployments
1769
+ "DEPLOYMENT_ALREADY_EXISTS",
1770
+ "DEPLOYMENT_INVALID_FILE",
1771
+ "DEPLOYMENT_NOT_FOUND",
1772
+ "DEPLOYMENT_UPLOAD_CANCELED",
1773
+ "DOMAIN_ALREADY_EXISTS",
1774
+ "DOMAIN_INVALID",
1775
+ "DOMAIN_NOT_FOUND",
1776
+ "ENVIRONMENT_VARIABLE_NOT_FOUND",
1777
+ // Docs
1778
+ "DOCS_NOT_FOUND",
1779
+ // Functions
1780
+ "FUNCTION_ALREADY_EXISTS",
1781
+ "FUNCTION_DEPLOYMENT_NOT_FOUND",
1782
+ "FUNCTION_NOT_FOUND",
1783
+ // Schedules
1784
+ "SCHEDULE_INVALID_CRON",
1785
+ "SCHEDULE_NOT_FOUND",
1786
+ // Payments
1787
+ "PAYMENT_CHECKOUT_ALREADY_EXISTS",
1788
+ "PAYMENT_CONFIG_INVALID",
1789
+ "PAYMENT_CONFIG_NOT_FOUND",
1790
+ "PAYMENT_NOT_FOUND",
1791
+ "PAYMENT_METHOD_DECLINED",
1792
+ "PAYMENT_PRICE_NOT_FOUND",
1793
+ "PAYMENT_PRODUCT_NOT_FOUND",
1794
+ // Secrets
1795
+ "SECRET_ALREADY_EXISTS",
1796
+ "SECRET_NOT_FOUND",
1797
+ // General
1798
+ "MISSING_FIELD",
1799
+ "ALREADY_EXISTS",
1800
+ "INVALID_INPUT",
1801
+ "NOT_FOUND",
1802
+ "UNKNOWN_ERROR",
1803
+ "INTERNAL_ERROR",
1804
+ "TOO_MANY_REQUESTS",
1805
+ "FORBIDDEN",
1806
+ "RATE_LIMITED",
1807
+ "NOT_IMPLEMENTED",
1808
+ "UPSTREAM_FAILURE"
1809
+ ];
1810
+ var ERROR_CODES = Object.fromEntries(
1811
+ ERROR_CODE_VALUES.map((code) => [code, code])
1812
+ );
1813
+ var OAUTH_PROVIDERS = [
1814
+ "google",
1815
+ "github",
1816
+ "discord",
1817
+ "linkedin",
1818
+ "facebook",
1819
+ "instagram",
1820
+ "tiktok",
1821
+ "apple",
1822
+ "x",
1823
+ "spotify",
1824
+ "microsoft"
1825
+ ];
1826
+
1827
+ // src/modules/auth/helpers.ts
1828
+ var PKCE_VERIFIER_KEY = "result_pkce_verifier";
1829
+ async function getWebCrypto() {
1830
+ const webCrypto = globalThis.crypto;
1831
+ if (typeof webCrypto?.getRandomValues === "function" && webCrypto.subtle) {
1832
+ return webCrypto;
1447
1833
  }
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);
1834
+ if (typeof process !== "undefined" && process.versions?.node) {
1835
+ const { webcrypto } = await import("crypto");
1836
+ return webcrypto;
1464
1837
  }
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);
1838
+ throw new Error("Web Crypto API is not available in this environment");
1839
+ }
1840
+ function base64UrlEncode(buffer) {
1841
+ const base64 = btoa(String.fromCharCode(...buffer));
1842
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1843
+ }
1844
+ async function generateCodeVerifier() {
1845
+ const webCrypto = await getWebCrypto();
1846
+ const array = new Uint8Array(32);
1847
+ webCrypto.getRandomValues(array);
1848
+ return base64UrlEncode(array);
1849
+ }
1850
+ async function generateCodeChallenge(verifier) {
1851
+ const webCrypto = await getWebCrypto();
1852
+ const encoder = new TextEncoder();
1853
+ const data = encoder.encode(verifier);
1854
+ const hash = await webCrypto.subtle.digest("SHA-256", data);
1855
+ return base64UrlEncode(new Uint8Array(hash));
1856
+ }
1857
+ function storePkceVerifier(verifier) {
1858
+ if (typeof sessionStorage !== "undefined") {
1859
+ sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
1860
+ }
1861
+ }
1862
+ function retrievePkceVerifier() {
1863
+ if (typeof sessionStorage === "undefined") {
1864
+ return null;
1865
+ }
1866
+ const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
1867
+ if (verifier) {
1868
+ sessionStorage.removeItem(PKCE_VERIFIER_KEY);
1869
+ }
1870
+ return verifier;
1871
+ }
1872
+ function wrapError(error, fallbackMessage) {
1873
+ if (error instanceof ResultError) {
1874
+ return { data: null, error };
1502
1875
  }
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);
1876
+ return {
1877
+ data: null,
1878
+ error: new ResultError(
1879
+ error instanceof Error ? error.message : fallbackMessage,
1880
+ 500,
1881
+ "UNEXPECTED_ERROR"
1882
+ )
1883
+ };
1884
+ }
1885
+ function cleanUrlParams(...params) {
1886
+ if (typeof window === "undefined") {
1887
+ return;
1521
1888
  }
1522
- };
1523
- var StorageBucket = class {
1524
- constructor(bucketName, http) {
1525
- this.bucketName = bucketName;
1889
+ const url = new URL(window.location.href);
1890
+ for (const p of params) {
1891
+ url.searchParams.delete(p);
1892
+ }
1893
+ window.history.replaceState({}, document.title, url.toString());
1894
+ }
1895
+
1896
+ // src/modules/auth/auth.ts
1897
+ var Auth = class {
1898
+ constructor(http, tokenManager, options = {}) {
1526
1899
  this.http = http;
1900
+ this.tokenManager = tokenManager;
1901
+ this.options = options;
1902
+ this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
1903
+ }
1904
+ http;
1905
+ tokenManager;
1906
+ options;
1907
+ authCallbackHandled;
1908
+ isServerMode() {
1909
+ return !!this.options.isServerMode;
1910
+ }
1911
+ /** Subscribe to SDK authentication state changes. */
1912
+ onAuthStateChange(callback) {
1913
+ return this.tokenManager.onAuthStateChange(callback);
1527
1914
  }
1528
1915
  /**
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
1916
+ * Save session from API response
1917
+ * Handles token storage, CSRF token, and HTTP auth header
1533
1918
  */
1534
- async upload(path, file) {
1919
+ saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
1920
+ if (!response.accessToken || !response.user) {
1921
+ return false;
1922
+ }
1923
+ const session = {
1924
+ accessToken: response.accessToken,
1925
+ user: response.user
1926
+ };
1927
+ if (!this.isServerMode() && response.csrfToken) {
1928
+ setCsrfToken(response.csrfToken);
1929
+ }
1930
+ if (!this.isServerMode()) {
1931
+ this.tokenManager.saveSession(session, event);
1932
+ }
1933
+ this.http.setAuthToken(response.accessToken);
1934
+ this.http.setRefreshToken(response.refreshToken ?? null);
1935
+ return true;
1936
+ }
1937
+ // ============================================================================
1938
+ // OAuth Callback Detection (runs on initialization)
1939
+ // ============================================================================
1940
+ /**
1941
+ * Detect and handle OAuth callback parameters in URL
1942
+ * Supports the PKCE flow (OAuth callback code in the URL)
1943
+ */
1944
+ async detectAuthCallback() {
1945
+ if (this.isServerMode() || typeof window === "undefined") {
1946
+ return;
1947
+ }
1535
1948
  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
1949
+ const params = new URLSearchParams(window.location.search);
1950
+ const error = params.get("error");
1951
+ if (error) {
1952
+ cleanUrlParams("error");
1953
+ console.debug("OAuth callback error:", error);
1954
+ return;
1955
+ }
1956
+ const code = params.get("insforge_code");
1957
+ if (code) {
1958
+ cleanUrlParams("insforge_code");
1959
+ const { error: exchangeError } = await this.exchangeOAuthCode(code);
1960
+ if (exchangeError) {
1961
+ console.debug("OAuth code exchange failed:", exchangeError.message);
1542
1962
  }
1963
+ return;
1964
+ }
1965
+ } catch (error) {
1966
+ console.debug("OAuth callback detection skipped:", error);
1967
+ }
1968
+ }
1969
+ // ============================================================================
1970
+ // Sign Up / Sign In / Sign Out
1971
+ // ============================================================================
1972
+ async signUp(request) {
1973
+ try {
1974
+ const response = await this.http.post(
1975
+ this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
1976
+ request,
1977
+ { credentials: "include", skipAuthRefresh: true }
1543
1978
  );
1544
- if (strategyResponse.method === "presigned") {
1545
- return await this.uploadWithPresignedUrl(strategyResponse, file);
1979
+ if (response.accessToken && response.user) {
1980
+ this.saveSessionFromResponse(response);
1546
1981
  }
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 };
1982
+ if (response.refreshToken) {
1983
+ this.http.setRefreshToken(response.refreshToken);
1561
1984
  }
1562
- throw new InsForgeError(
1563
- `Unsupported upload method: ${strategyResponse.method}`,
1564
- 500,
1565
- "STORAGE_ERROR"
1566
- );
1985
+ return { data: response, error: null };
1567
1986
  } catch (error) {
1568
- return {
1569
- data: null,
1570
- error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
1571
- };
1987
+ return wrapError(error, "An unexpected error occurred during sign up");
1572
1988
  }
1573
1989
  }
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) {
1990
+ async signInWithPassword(request) {
1580
1991
  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
- }
1992
+ const response = await this.http.post(
1993
+ this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
1994
+ request,
1995
+ { credentials: "include", skipAuthRefresh: true }
1589
1996
  );
1590
- if (strategyResponse.method === "presigned") {
1591
- return await this.uploadWithPresignedUrl(strategyResponse, file);
1997
+ this.saveSessionFromResponse(response);
1998
+ if (response.refreshToken) {
1999
+ this.http.setRefreshToken(response.refreshToken);
1592
2000
  }
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`,
2001
+ return { data: response, error: null };
2002
+ } catch (error) {
2003
+ return wrapError(error, "An unexpected error occurred during sign in");
2004
+ }
2005
+ }
2006
+ async signOut() {
2007
+ try {
2008
+ try {
2009
+ const serverMode = this.isServerMode();
2010
+ const csrfToken = !serverMode ? getCsrfToken() : null;
2011
+ await this.http.post(
2012
+ serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
2013
+ void 0,
1599
2014
  {
1600
- body: formData,
1601
- headers: {
1602
- // Don't set Content-Type, let browser set multipart boundary
1603
- }
2015
+ credentials: "include",
2016
+ skipAuthRefresh: true,
2017
+ ...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
1604
2018
  }
1605
2019
  );
1606
- return { data: response, error: null };
2020
+ } catch {
1607
2021
  }
1608
- throw new InsForgeError(
1609
- `Unsupported upload method: ${strategyResponse.method}`,
1610
- 500,
1611
- "STORAGE_ERROR"
1612
- );
1613
- } catch (error) {
2022
+ this.tokenManager.clearSession();
2023
+ this.http.setAuthToken(null);
2024
+ this.http.setRefreshToken(null);
2025
+ if (!this.isServerMode()) {
2026
+ clearCsrfToken();
2027
+ }
2028
+ return { error: null };
2029
+ } catch {
1614
2030
  return {
1615
- data: null,
1616
- error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
2031
+ error: new ResultError("Failed to sign out", 500, "SIGNOUT_ERROR")
1617
2032
  };
1618
2033
  }
1619
2034
  }
1620
- /**
1621
- * Internal method to handle presigned URL uploads
1622
- */
1623
- async uploadWithPresignedUrl(strategy, file) {
2035
+ async signInWithOAuth(providerOrOptions, options) {
1624
2036
  try {
1625
- const formData = new FormData();
1626
- if (strategy.fields) {
1627
- Object.entries(strategy.fields).forEach(([key, value]) => {
1628
- formData.append(key, value);
1629
- });
2037
+ let signInOptions;
2038
+ if (typeof providerOrOptions === "object") {
2039
+ signInOptions = providerOrOptions;
2040
+ } else if (options) {
2041
+ signInOptions = { provider: providerOrOptions, ...options };
2042
+ } else {
2043
+ return {
2044
+ data: {},
2045
+ error: new ResultError(
2046
+ "OAuth sign-in options are required",
2047
+ 400,
2048
+ ERROR_CODES.INVALID_INPUT
2049
+ )
2050
+ };
1630
2051
  }
1631
- formData.append("file", file);
1632
- const uploadResponse = await fetch(strategy.uploadUrl, {
1633
- method: "POST",
1634
- body: formData
2052
+ if (!signInOptions || !signInOptions.redirectTo) {
2053
+ return {
2054
+ data: {},
2055
+ error: new ResultError(
2056
+ "Redirect URI is required",
2057
+ 400,
2058
+ ERROR_CODES.INVALID_INPUT
2059
+ )
2060
+ };
2061
+ }
2062
+ const { provider } = signInOptions;
2063
+ const providerKey = encodeURIComponent(provider.toLowerCase());
2064
+ const codeVerifier = await generateCodeVerifier();
2065
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
2066
+ storePkceVerifier(codeVerifier);
2067
+ const params = {
2068
+ ...signInOptions.additionalParams ?? {},
2069
+ redirect_uri: signInOptions.redirectTo,
2070
+ code_challenge: codeChallenge
2071
+ };
2072
+ const isBuiltInProvider = OAUTH_PROVIDERS.includes(
2073
+ providerKey
2074
+ );
2075
+ const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
2076
+ const response = await this.http.get(oauthPath, {
2077
+ params,
2078
+ skipAuthRefresh: true
1635
2079
  });
1636
- if (!uploadResponse.ok) {
1637
- throw new InsForgeError(
1638
- `Upload to storage failed: ${uploadResponse.statusText}`,
1639
- uploadResponse.status,
1640
- "STORAGE_ERROR"
1641
- );
1642
- }
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 };
2080
+ if (!this.isServerMode() && typeof window !== "undefined" && !signInOptions.skipBrowserRedirect) {
2081
+ window.location.href = response.authUrl;
2082
+ return { data: {}, error: null };
1649
2083
  }
1650
2084
  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
- },
2085
+ data: { url: response.authUrl, provider: providerKey, codeVerifier },
1659
2086
  error: null
1660
2087
  };
1661
2088
  } catch (error) {
1662
- throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
2089
+ if (error instanceof ResultError) {
2090
+ return { data: {}, error };
2091
+ }
2092
+ return {
2093
+ data: {},
2094
+ error: new ResultError(
2095
+ "An unexpected error occurred during OAuth initialization",
2096
+ 500,
2097
+ "UNEXPECTED_ERROR"
2098
+ )
2099
+ };
1663
2100
  }
1664
2101
  }
1665
2102
  /**
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
2103
+ * Exchange OAuth authorization code for tokens (PKCE flow)
2104
+ * Called automatically on initialization when the OAuth callback code is in the URL
1670
2105
  */
1671
- async download(path) {
2106
+ async exchangeOAuthCode(code, codeVerifier) {
1672
2107
  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
- }
2108
+ const verifier = codeVerifier ?? retrievePkceVerifier();
2109
+ if (!verifier) {
2110
+ return {
2111
+ data: null,
2112
+ error: new ResultError(
2113
+ "PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
2114
+ 400,
2115
+ "PKCE_VERIFIER_MISSING"
2116
+ )
2117
+ };
1710
2118
  }
1711
- const blob = await response.blob();
1712
- return { data: blob, error: null };
1713
- } catch (error) {
2119
+ const request = {
2120
+ code,
2121
+ code_verifier: verifier
2122
+ };
2123
+ const response = await this.http.post(
2124
+ this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
2125
+ request,
2126
+ { credentials: "include", skipAuthRefresh: true }
2127
+ );
2128
+ this.saveSessionFromResponse(response);
1714
2129
  return {
1715
- data: null,
1716
- error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
2130
+ data: response,
2131
+ error: null
1717
2132
  };
2133
+ } catch (error) {
2134
+ return wrapError(
2135
+ error,
2136
+ "An unexpected error occurred during OAuth code exchange"
2137
+ );
1718
2138
  }
1719
2139
  }
1720
2140
  /**
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}.
2141
+ * Sign in with an ID token from a native SDK (Google One Tap, etc.)
2142
+ * Use this for native mobile apps or Google One Tap on web.
1725
2143
  *
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.
2144
+ * @param credentials.provider - The identity provider (currently only 'google' is supported)
2145
+ * @param credentials.token - The ID token from the native SDK
1740
2146
  */
1741
- async requestDownloadStrategy(path, expiresIn) {
1742
- const encoded = encodeURIComponent(path);
2147
+ async signInWithIdToken(credentials) {
1743
2148
  try {
1744
- return await this.http.get(
1745
- `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encoded}`,
1746
- { params: { expiresIn: expiresIn.toString() } }
2149
+ const { provider, token } = credentials;
2150
+ const response = await this.http.post(
2151
+ "/api/auth/id-token?client_type=mobile",
2152
+ { provider, token },
2153
+ { credentials: "include", skipAuthRefresh: true }
1747
2154
  );
1748
- } 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;
2155
+ this.saveSessionFromResponse(response);
2156
+ if (response.refreshToken) {
2157
+ this.http.setRefreshToken(response.refreshToken);
1753
2158
  }
1754
- return await this.http.post(
1755
- `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
1756
- { expiresIn }
2159
+ return {
2160
+ data: response,
2161
+ error: null
2162
+ };
2163
+ } catch (error) {
2164
+ return wrapError(
2165
+ error,
2166
+ "An unexpected error occurred during ID token sign in"
1757
2167
  );
1758
2168
  }
1759
2169
  }
2170
+ // ============================================================================
2171
+ // Session Management
2172
+ // ============================================================================
1760
2173
  /**
1761
- * Create a signed URL for an object.
2174
+ * Refresh the current auth session.
1762
2175
  *
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.
2176
+ * Browser mode:
2177
+ * - Uses httpOnly refresh cookie and optional CSRF header.
1768
2178
  *
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.
2179
+ * Legacy server mode (`isServerMode: true`):
2180
+ * - Uses mobile auth flow and requires `refreshToken` in request body.
2181
+ *
2182
+ * SSR apps should prefer `createRefreshAuthRouter()` / `refreshAuth()` from
2183
+ * `@resultdev/sdk/ssr`.
1772
2184
  */
1773
- async createSignedUrl(path, expiresIn = 3600) {
2185
+ async refreshSession(options) {
1774
2186
  try {
1775
- const strategy = await this.requestDownloadStrategy(path, expiresIn);
1776
- return {
1777
- data: {
1778
- signedUrl: strategy.url,
1779
- expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
1780
- },
1781
- error: null
1782
- };
2187
+ if (this.isServerMode() && !options?.refreshToken) {
2188
+ return {
2189
+ data: null,
2190
+ error: new ResultError(
2191
+ "refreshToken is required when refreshing session in server mode",
2192
+ 400,
2193
+ ERROR_CODES.AUTH_UNAUTHORIZED
2194
+ )
2195
+ };
2196
+ }
2197
+ const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
2198
+ const response = await this.http.post(
2199
+ this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
2200
+ this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
2201
+ {
2202
+ headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
2203
+ credentials: "include",
2204
+ skipAuthRefresh: true
2205
+ }
2206
+ );
2207
+ if (response.accessToken) {
2208
+ this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
2209
+ }
2210
+ return { data: response, error: null };
1783
2211
  } catch (error) {
1784
- return {
1785
- data: null,
1786
- error: error instanceof InsForgeError ? error : new InsForgeError("Failed to create signed URL", 500, "STORAGE_ERROR")
1787
- };
2212
+ return wrapError(
2213
+ error,
2214
+ "An unexpected error occurred during session refresh"
2215
+ );
1788
2216
  }
1789
2217
  }
1790
2218
  /**
1791
- * Create signed URLs for multiple objects in a single call.
1792
- *
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.
1795
- *
1796
- * @param paths - The object keys/paths
1797
- * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
2219
+ * Get current user, automatically waits for pending OAuth callback
1798
2220
  */
1799
- async createSignedUrls(paths, expiresIn = 3600) {
2221
+ async getCurrentUser() {
2222
+ await this.authCallbackHandled;
1800
2223
  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
- })
1810
- );
1811
- return { data, error: null };
2224
+ if (this.isServerMode()) {
2225
+ const accessToken = this.tokenManager.getAccessToken();
2226
+ if (!accessToken) {
2227
+ return { data: { user: null }, error: null };
2228
+ }
2229
+ this.http.setAuthToken(accessToken);
2230
+ const response = await this.http.get(
2231
+ "/api/auth/sessions/current"
2232
+ );
2233
+ const user = response.user ?? null;
2234
+ return { data: { user }, error: null };
2235
+ }
2236
+ const session = this.tokenManager.getSession();
2237
+ if (session) {
2238
+ this.http.setAuthToken(session.accessToken);
2239
+ return { data: { user: session.user }, error: null };
2240
+ }
2241
+ if (typeof window !== "undefined") {
2242
+ const { data: refreshed, error: refreshError } = await this.refreshSession();
2243
+ if (refreshError) {
2244
+ return { data: { user: null }, error: refreshError };
2245
+ }
2246
+ if (refreshed?.accessToken) {
2247
+ return { data: { user: refreshed.user ?? null }, error: null };
2248
+ }
2249
+ }
2250
+ return { data: { user: null }, error: null };
1812
2251
  } catch (error) {
2252
+ if (error instanceof ResultError) {
2253
+ return { data: { user: null }, error };
2254
+ }
1813
2255
  return {
1814
- data: null,
1815
- error: error instanceof InsForgeError ? error : new InsForgeError("Failed to create signed URLs", 500, "STORAGE_ERROR")
2256
+ data: { user: null },
2257
+ error: new ResultError(
2258
+ "An unexpected error occurred while getting user",
2259
+ 500,
2260
+ "UNEXPECTED_ERROR"
2261
+ )
1816
2262
  };
1817
2263
  }
1818
2264
  }
1819
- /**
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
1825
- */
1826
- async list(options) {
2265
+ // ============================================================================
2266
+ // Profile Management
2267
+ // ============================================================================
2268
+ async getProfile(userId) {
1827
2269
  try {
1828
- const params = {};
1829
- if (options?.prefix) {
1830
- params.prefix = options.prefix;
1831
- }
1832
- if (options?.search) {
1833
- params.search = options.search;
1834
- }
1835
- if (options?.limit) {
1836
- params.limit = options.limit.toString();
1837
- }
1838
- if (options?.offset) {
1839
- params.offset = options.offset.toString();
1840
- }
1841
2270
  const response = await this.http.get(
1842
- `/api/storage/buckets/${this.bucketName}/objects`,
1843
- { params }
2271
+ `/api/auth/profiles/${userId}`
1844
2272
  );
1845
2273
  return { data: response, error: null };
1846
2274
  } catch (error) {
1847
- return {
1848
- data: null,
1849
- error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
1850
- };
2275
+ return wrapError(
2276
+ error,
2277
+ "An unexpected error occurred while fetching user profile"
2278
+ );
1851
2279
  }
1852
2280
  }
1853
- /**
1854
- * Delete a file
1855
- * @param path - The object key/path
1856
- */
1857
- async remove(path) {
2281
+ async setProfile(profile) {
1858
2282
  try {
1859
- const response = await this.http.delete(
1860
- `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`
2283
+ const response = await this.http.patch(
2284
+ "/api/auth/profiles/current",
2285
+ {
2286
+ profile
2287
+ }
1861
2288
  );
2289
+ const currentUser = this.tokenManager.getUser();
2290
+ if (!this.isServerMode() && currentUser && response.profile !== void 0) {
2291
+ this.tokenManager.setUser({
2292
+ ...currentUser,
2293
+ profile: response.profile
2294
+ });
2295
+ }
1862
2296
  return { data: response, error: null };
1863
2297
  } catch (error) {
1864
- return {
1865
- data: null,
1866
- error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
1867
- };
2298
+ return wrapError(
2299
+ error,
2300
+ "An unexpected error occurred while updating user profile"
2301
+ );
1868
2302
  }
1869
2303
  }
1870
- };
1871
- var Storage = class {
1872
- constructor(http) {
1873
- this.http = http;
1874
- }
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);
1881
- }
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);
1889
- }
1890
- };
1891
- var Chat = class {
1892
- constructor(http) {
1893
- this.completions = new ChatCompletions(http);
1894
- }
1895
- };
1896
- var ChatCompletions = class {
1897
- constructor(http) {
1898
- this.http = http;
1899
- }
1900
- /**
1901
- * Create a chat completion - OpenAI-like response format
1902
- *
1903
- * @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);
1911
- *
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
- * });
1923
- *
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
- * });
1936
- *
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
1944
- *
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
- * });
1951
- *
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
- * });
1958
- *
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)
2304
+ // ============================================================================
2305
+ // Email Verification
2306
+ // ============================================================================
2307
+ async resendVerificationEmail(request) {
2308
+ try {
2309
+ const response = await this.http.post("/api/auth/email/send-verification", request, {
2310
+ skipAuthRefresh: true
1990
2311
  });
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);
2312
+ return { data: response, error: null };
2313
+ } catch (error) {
2314
+ return wrapError(
2315
+ error,
2316
+ "An unexpected error occurred while sending verification email"
2317
+ );
1996
2318
  }
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
2319
  }
2028
- /**
2029
- * Parse SSE stream into async iterable of OpenAI-like chunks
2030
- */
2031
- async *parseSSEStream(response, model) {
2032
- const reader = response.body.getReader();
2033
- const decoder = new TextDecoder();
2034
- let buffer = "";
2320
+ async verifyEmail(request) {
2035
2321
  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
- }
2322
+ const response = await this.http.post(
2323
+ this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
2324
+ request,
2325
+ { credentials: "include", skipAuthRefresh: true }
2326
+ );
2327
+ this.saveSessionFromResponse(response);
2328
+ if (response.refreshToken) {
2329
+ this.http.setRefreshToken(response.refreshToken);
2330
+ }
2331
+ return { data: response, error: null };
2332
+ } catch (error) {
2333
+ return wrapError(
2334
+ error,
2335
+ "An unexpected error occurred while verifying email"
2336
+ );
2337
+ }
2338
+ }
2339
+ // ============================================================================
2340
+ // Password Reset
2341
+ // ============================================================================
2342
+ async sendResetPasswordEmail(request) {
2343
+ try {
2344
+ const response = await this.http.post("/api/auth/email/send-reset-password", request, {
2345
+ skipAuthRefresh: true
2346
+ });
2347
+ return { data: response, error: null };
2348
+ } catch (error) {
2349
+ return wrapError(
2350
+ error,
2351
+ "An unexpected error occurred while sending password reset email"
2352
+ );
2353
+ }
2354
+ }
2355
+ async exchangeResetPasswordToken(request) {
2356
+ try {
2357
+ const response = await this.http.post(
2358
+ "/api/auth/email/exchange-reset-password-token",
2359
+ request,
2360
+ { skipAuthRefresh: true }
2361
+ );
2362
+ return { data: response, error: null };
2363
+ } catch (error) {
2364
+ return wrapError(
2365
+ error,
2366
+ "An unexpected error occurred while verifying reset code"
2367
+ );
2368
+ }
2369
+ }
2370
+ async resetPassword(request) {
2371
+ try {
2372
+ const response = await this.http.post(
2373
+ "/api/auth/email/reset-password",
2374
+ request,
2375
+ { skipAuthRefresh: true }
2376
+ );
2377
+ return { data: response, error: null };
2378
+ } catch (error) {
2379
+ return wrapError(
2380
+ error,
2381
+ "An unexpected error occurred while resetting password"
2382
+ );
2383
+ }
2384
+ }
2385
+ // ============================================================================
2386
+ // Configuration
2387
+ // ============================================================================
2388
+ async getPublicAuthConfig() {
2389
+ try {
2390
+ const response = await this.http.get(
2391
+ "/api/auth/public-config",
2392
+ {
2393
+ skipAuthRefresh: true
2093
2394
  }
2094
- }
2095
- } finally {
2096
- reader.releaseLock();
2395
+ );
2396
+ return { data: response, error: null };
2397
+ } catch (error) {
2398
+ return wrapError(
2399
+ error,
2400
+ "An unexpected error occurred while fetching auth configuration"
2401
+ );
2097
2402
  }
2098
2403
  }
2099
2404
  };
2100
- var Embeddings = class {
2101
- constructor(http) {
2102
- this.http = http;
2405
+
2406
+ // src/modules/database-postgrest.ts
2407
+ import { PostgrestClient } from "@supabase/postgrest-js";
2408
+ function createResultPostgrestFetch(httpClient) {
2409
+ return async (input, init) => {
2410
+ const url = typeof input === "string" ? input : input.toString();
2411
+ const urlObj = new URL(url);
2412
+ const pathname = urlObj.pathname.slice(1);
2413
+ const rpcMatch = pathname.match(/^rpc\/(.+)$/);
2414
+ const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
2415
+ const backendUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
2416
+ const headers = new Headers(httpClient.getHeaders());
2417
+ new Headers(init?.headers).forEach((value, key) => {
2418
+ headers.set(key, value);
2419
+ });
2420
+ const response = await httpClient.rawFetch(backendUrl, {
2421
+ ...init,
2422
+ headers
2423
+ });
2424
+ return response;
2425
+ };
2426
+ }
2427
+ var Database = class {
2428
+ postgrest;
2429
+ constructor(httpClient, defaultSchema) {
2430
+ this.postgrest = new PostgrestClient("http://dummy", {
2431
+ fetch: createResultPostgrestFetch(httpClient),
2432
+ headers: {},
2433
+ ...defaultSchema ? { schema: defaultSchema } : {}
2434
+ });
2103
2435
  }
2104
2436
  /**
2105
- * Create embeddings for text input - OpenAI-like response format
2437
+ * Select a non-default Postgres schema for the chained query. Maps to
2438
+ * PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
2439
+ * The schema must be exposed by the backend.
2106
2440
  *
2107
2441
  * @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[]
2442
+ * const { data } = await client.database
2443
+ * .schema('analytics')
2444
+ * .from('events')
2445
+ * .select('*');
2115
2446
  *
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
- * });
2447
+ * @example
2448
+ * await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
2449
+ */
2450
+ schema(schemaName) {
2451
+ return this.postgrest.schema(schemaName);
2452
+ }
2453
+ /**
2454
+ * Create a query builder for a table
2124
2455
  *
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
- * });
2456
+ * @example
2457
+ * // Basic query
2458
+ * const { data, error } = await client.database
2459
+ * .from('posts')
2460
+ * .select('*')
2461
+ * .eq('user_id', userId);
2131
2462
  *
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
- * ```
2463
+ * // With count (Supabase style!)
2464
+ * const { data, error, count } = await client.database
2465
+ * .from('posts')
2466
+ * .select('*', { count: 'exact' })
2467
+ * .range(0, 9);
2468
+ *
2469
+ * // Just get count, no data
2470
+ * const { count } = await client.database
2471
+ * .from('posts')
2472
+ * .select('*', { count: 'exact', head: true });
2473
+ *
2474
+ * // Complex queries with OR
2475
+ * const { data } = await client.database
2476
+ * .from('posts')
2477
+ * .select('*, users!inner(*)')
2478
+ * .or('status.eq.active,status.eq.pending');
2479
+ *
2480
+ * // All features work:
2481
+ * - Nested selects
2482
+ * - Foreign key expansion
2483
+ * - OR/AND/NOT conditions
2484
+ * - Count with head
2485
+ * - Range pagination
2486
+ * - Upserts
2139
2487
  */
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
- };
2488
+ from(table) {
2489
+ return this.postgrest.from(table);
2490
+ }
2491
+ /**
2492
+ * Call a PostgreSQL function (RPC)
2493
+ *
2494
+ * @example
2495
+ * // Call a function with parameters
2496
+ * const { data, error } = await client.database
2497
+ * .rpc('get_user_stats', { user_id: 123 });
2498
+ *
2499
+ * // Call a function with no parameters
2500
+ * const { data, error } = await client.database
2501
+ * .rpc('get_all_active_users');
2502
+ *
2503
+ * // With options (head, count, get)
2504
+ * const { data, count } = await client.database
2505
+ * .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
2506
+ */
2507
+ rpc(fn, args, options) {
2508
+ return this.postgrest.rpc(fn, args, options);
2154
2509
  }
2155
2510
  };
2156
- var Images = class {
2511
+
2512
+ // src/modules/email.ts
2513
+ var Emails = class {
2514
+ http;
2157
2515
  constructor(http) {
2158
2516
  this.http = http;
2159
2517
  }
2160
2518
  /**
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
- * ```
2519
+ * Send a custom HTML email
2520
+ * @param options Email options including recipients, subject, and HTML content
2183
2521
  */
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
- }
2522
+ async send(options) {
2523
+ try {
2524
+ const data = await this.http.post(
2525
+ "/api/email/send-raw",
2526
+ options
2527
+ );
2528
+ return { data, error: null };
2529
+ } catch (error) {
2530
+ if (error instanceof Error && error.name === "AbortError") {
2531
+ throw error;
2207
2532
  }
2208
- };
2533
+ return {
2534
+ data: null,
2535
+ error: error instanceof ResultError ? error : new ResultError(
2536
+ error instanceof Error ? error.message : "Email send failed",
2537
+ 500,
2538
+ "EMAIL_ERROR"
2539
+ )
2540
+ };
2541
+ }
2209
2542
  }
2210
2543
  };
2544
+
2545
+ // src/modules/functions.ts
2211
2546
  var Functions = class _Functions {
2547
+ http;
2548
+ functionsUrl;
2212
2549
  constructor(http, functionsUrl) {
2213
2550
  this.http = http;
2214
2551
  this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
2215
2552
  }
2216
2553
  /**
2217
2554
  * 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.
2555
+ * Rewrites the backend base URL to its functions subhosting URL
2556
+ * ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
2557
+ * Only applies to platform-hosted backends.
2221
2558
  */
2222
2559
  static deriveSubhostingUrl(baseUrl) {
2223
2560
  try {
@@ -2236,7 +2573,7 @@ var Functions = class _Functions {
2236
2573
  * placeholder; the router only reads pathname.
2237
2574
  */
2238
2575
  buildInProcessRequest(slug, method, body, callerHeaders) {
2239
- const url = new URL("/" + slug, "http://insforge.local").toString();
2576
+ const url = new URL(`/${slug}`, "http://backend.local").toString();
2240
2577
  const headers = { ...this.http.getHeaders() };
2241
2578
  const reqBody = serializeBody(method, body, headers);
2242
2579
  Object.assign(headers, callerHeaders);
@@ -2250,7 +2587,7 @@ var Functions = class _Functions {
2250
2587
  * Invoke an Edge Function.
2251
2588
  *
2252
2589
  * Dispatch order:
2253
- * 1. If `globalThis.__insforge_dispatch__` is present, call it in-process.
2590
+ * 1. If the platform's in-process dispatch hook is present, call it directly.
2254
2591
  * This avoids Deno Subhosting's 508 Loop Detected when one bundled
2255
2592
  * function invokes another inside the same deployment.
2256
2593
  * 2. Otherwise, try the configured subhosting URL.
@@ -2275,7 +2612,7 @@ var Functions = class _Functions {
2275
2612
  }
2276
2613
  return {
2277
2614
  data: null,
2278
- error: error instanceof InsForgeError ? error : new InsForgeError(
2615
+ error: error instanceof ResultError ? error : new ResultError(
2279
2616
  error instanceof Error ? error.message : "Function invocation failed",
2280
2617
  500,
2281
2618
  "FUNCTION_ERROR"
@@ -2285,20 +2622,24 @@ var Functions = class _Functions {
2285
2622
  }
2286
2623
  if (this.functionsUrl) {
2287
2624
  try {
2288
- const data = await this.http.request(method, `${this.functionsUrl}/${slug}`, {
2289
- body,
2290
- headers
2291
- });
2625
+ const data = await this.http.request(
2626
+ method,
2627
+ `${this.functionsUrl}/${slug}`,
2628
+ {
2629
+ body,
2630
+ headers
2631
+ }
2632
+ );
2292
2633
  return { data, error: null };
2293
2634
  } catch (error) {
2294
2635
  if (error instanceof Error && error.name === "AbortError") {
2295
2636
  throw error;
2296
2637
  }
2297
- if (error instanceof InsForgeError && error.statusCode === 404) {
2638
+ if (error instanceof ResultError && error.statusCode === 404) {
2298
2639
  } else {
2299
2640
  return {
2300
2641
  data: null,
2301
- error: error instanceof InsForgeError ? error : new InsForgeError(
2642
+ error: error instanceof ResultError ? error : new ResultError(
2302
2643
  error instanceof Error ? error.message : "Function invocation failed",
2303
2644
  500,
2304
2645
  "FUNCTION_ERROR"
@@ -2317,7 +2658,7 @@ var Functions = class _Functions {
2317
2658
  }
2318
2659
  return {
2319
2660
  data: null,
2320
- error: error instanceof InsForgeError ? error : new InsForgeError(
2661
+ error: error instanceof ResultError ? error : new ResultError(
2321
2662
  error instanceof Error ? error.message : "Function invocation failed",
2322
2663
  500,
2323
2664
  "FUNCTION_ERROR"
@@ -2326,6 +2667,8 @@ var Functions = class _Functions {
2326
2667
  }
2327
2668
  }
2328
2669
  };
2670
+
2671
+ // src/modules/realtime.ts
2329
2672
  var CONNECT_TIMEOUT = 1e4;
2330
2673
  var SUBSCRIBE_TIMEOUT = 1e4;
2331
2674
  var Realtime = class {
@@ -2334,18 +2677,22 @@ var Realtime = class {
2334
2677
  this.tokenManager = tokenManager;
2335
2678
  this.anonKey = anonKey;
2336
2679
  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
2680
  this.tokenManager.onAuthStateChange((event) => {
2344
2681
  if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
2345
2682
  this.reconnectForAuthChange();
2346
2683
  }
2347
2684
  });
2348
2685
  }
2686
+ baseUrl;
2687
+ tokenManager;
2688
+ anonKey;
2689
+ getValidAccessToken;
2690
+ socket = null;
2691
+ connectPromise = null;
2692
+ connectionAttempt = null;
2693
+ nextConnectionAttemptId = 0;
2694
+ subscriptions = /* @__PURE__ */ new Map();
2695
+ eventListeners = /* @__PURE__ */ new Map();
2349
2696
  notifyListeners(event, payload) {
2350
2697
  for (const callback of this.eventListeners.get(event) ?? []) {
2351
2698
  try {
@@ -2510,7 +2857,10 @@ var Realtime = class {
2510
2857
  {
2511
2858
  ok: false,
2512
2859
  channel: subscription.channel,
2513
- error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
2860
+ error: {
2861
+ code: "DISCONNECTED",
2862
+ message: "Connection lost before subscription completed"
2863
+ }
2514
2864
  },
2515
2865
  true
2516
2866
  );
@@ -2533,7 +2883,10 @@ var Realtime = class {
2533
2883
  return Promise.resolve({
2534
2884
  ok: false,
2535
2885
  channel,
2536
- error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
2886
+ error: {
2887
+ code: "CONNECTION_FAILED",
2888
+ message: "Not connected to realtime server"
2889
+ }
2537
2890
  });
2538
2891
  }
2539
2892
  subscription.status = "pending";
@@ -2564,333 +2917,702 @@ var Realtime = class {
2564
2917
  );
2565
2918
  }
2566
2919
  }, SUBSCRIBE_TIMEOUT);
2567
- socket.emit("realtime:subscribe", { channel }, (response) => {
2568
- if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2569
- return;
2920
+ socket.emit(
2921
+ "realtime:subscribe",
2922
+ { channel },
2923
+ (response) => {
2924
+ if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
2925
+ return;
2926
+ }
2927
+ if (response.ok) {
2928
+ subscription.status = "subscribed";
2929
+ subscription.members = new Map(
2930
+ response.presence.members.map((member) => [
2931
+ member.presenceId,
2932
+ member
2933
+ ])
2934
+ );
2935
+ } else {
2936
+ subscription.status = "rejected";
2937
+ subscription.members.clear();
2938
+ }
2939
+ this.settleSubscription(subscription, response, false);
2940
+ }
2941
+ );
2942
+ });
2943
+ return subscription.pending;
2944
+ }
2945
+ settleSubscription(subscription, response, incrementEpoch) {
2946
+ if (incrementEpoch) {
2947
+ subscription.epoch++;
2948
+ }
2949
+ subscription.settlePending?.(response);
2950
+ }
2951
+ applyPresenceEvent(event, message) {
2952
+ if (event !== "presence:join" && event !== "presence:leave") {
2953
+ return;
2954
+ }
2955
+ const presenceEvent = message;
2956
+ const channel = presenceEvent.meta?.channel;
2957
+ const member = presenceEvent.member;
2958
+ if (!channel || !member) {
2959
+ return;
2960
+ }
2961
+ const subscription = this.subscriptions.get(channel);
2962
+ if (!subscription) {
2963
+ return;
2964
+ }
2965
+ if (event === "presence:join") {
2966
+ subscription.members.set(member.presenceId, member);
2967
+ } else {
2968
+ subscription.members.delete(member.presenceId);
2969
+ }
2970
+ }
2971
+ get isConnected() {
2972
+ return this.socket?.connected ?? false;
2973
+ }
2974
+ get connectionState() {
2975
+ if (!this.socket) {
2976
+ return "disconnected";
2977
+ }
2978
+ return this.socket.connected ? "connected" : "connecting";
2979
+ }
2980
+ get socketId() {
2981
+ return this.socket?.id;
2982
+ }
2983
+ async subscribe(channel) {
2984
+ let subscription = this.subscriptions.get(channel);
2985
+ if (subscription) {
2986
+ if (subscription.pending) {
2987
+ return subscription.pending;
2988
+ }
2989
+ if (subscription.status === "subscribed") {
2990
+ return {
2991
+ ok: true,
2992
+ channel,
2993
+ presence: { members: [...subscription.members.values()] }
2994
+ };
2995
+ }
2996
+ } else {
2997
+ subscription = {
2998
+ channel,
2999
+ epoch: 0,
3000
+ status: "pending",
3001
+ members: /* @__PURE__ */ new Map()
3002
+ };
3003
+ this.subscriptions.set(channel, subscription);
3004
+ }
3005
+ if (!this.socket?.connected) {
3006
+ try {
3007
+ await this.connect();
3008
+ } catch (error) {
3009
+ if (this.subscriptions.get(channel) === subscription) {
3010
+ this.subscriptions.delete(channel);
3011
+ }
3012
+ const message = error instanceof Error ? error.message : "Connection failed";
3013
+ return {
3014
+ ok: false,
3015
+ channel,
3016
+ error: { code: "CONNECTION_FAILED", message }
3017
+ };
3018
+ }
3019
+ }
3020
+ return subscription.pending ?? this.requestSubscription(channel, subscription);
3021
+ }
3022
+ unsubscribe(channel) {
3023
+ const subscription = this.subscriptions.get(channel);
3024
+ if (!subscription) {
3025
+ return;
3026
+ }
3027
+ this.subscriptions.delete(channel);
3028
+ this.settleSubscription(
3029
+ subscription,
3030
+ {
3031
+ ok: false,
3032
+ channel,
3033
+ error: {
3034
+ code: "SUBSCRIPTION_CANCELLED",
3035
+ message: "Subscription cancelled"
3036
+ }
3037
+ },
3038
+ true
3039
+ );
3040
+ if (this.socket?.connected) {
3041
+ this.socket.emit("realtime:unsubscribe", { channel });
3042
+ }
3043
+ }
3044
+ async publish(channel, event, payload) {
3045
+ if (!this.socket?.connected) {
3046
+ throw new Error(
3047
+ "Not connected to realtime server. Call connect() first."
3048
+ );
3049
+ }
3050
+ this.socket.emit("realtime:publish", { channel, event, payload });
3051
+ }
3052
+ on(event, callback) {
3053
+ const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
3054
+ listeners.add(callback);
3055
+ this.eventListeners.set(event, listeners);
3056
+ }
3057
+ off(event, callback) {
3058
+ const listeners = this.eventListeners.get(event);
3059
+ listeners?.delete(callback);
3060
+ if (listeners?.size === 0) {
3061
+ this.eventListeners.delete(event);
3062
+ }
3063
+ }
3064
+ once(event, callback) {
3065
+ const wrapper = (payload) => {
3066
+ this.off(event, wrapper);
3067
+ callback(payload);
3068
+ };
3069
+ this.on(event, wrapper);
3070
+ }
3071
+ getSubscribedChannels() {
3072
+ return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
3073
+ }
3074
+ getPresenceState(channel) {
3075
+ return [...this.subscriptions.get(channel)?.members.values() ?? []];
3076
+ }
3077
+ };
3078
+
3079
+ // src/modules/storage.ts
3080
+ function generateObjectKey(filename) {
3081
+ const dotIndex = filename.lastIndexOf(".");
3082
+ const hasExt = dotIndex > 0;
3083
+ const ext = hasExt ? filename.slice(dotIndex) : "";
3084
+ const base = hasExt ? filename.slice(0, dotIndex) : filename;
3085
+ const sanitizedBase = base.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 32) || "file";
3086
+ const timestamp = Date.now();
3087
+ const random = Math.random().toString(36).slice(2, 8);
3088
+ return `${sanitizedBase}-${timestamp}-${random}${ext}`;
3089
+ }
3090
+ var StorageBucket = class {
3091
+ constructor(bucketName, http) {
3092
+ this.bucketName = bucketName;
3093
+ this.http = http;
3094
+ }
3095
+ bucketName;
3096
+ http;
3097
+ /**
3098
+ * Upload a file to a specific key.
3099
+ * Uses the upload strategy from the backend (direct or presigned).
3100
+ * Standard PUT semantics: uploading to an existing key replaces the
3101
+ * current object in place.
3102
+ * @param path - The object key/path
3103
+ * @param file - File or Blob to upload
3104
+ */
3105
+ async upload(path, file) {
3106
+ try {
3107
+ const strategyResponse = await this.http.post(
3108
+ `/api/storage/buckets/${this.bucketName}/upload-strategy`,
3109
+ {
3110
+ filename: path,
3111
+ contentType: file.type || "application/octet-stream",
3112
+ size: file.size
2570
3113
  }
2571
- if (response.ok) {
2572
- subscription.status = "subscribed";
2573
- subscription.members = new Map(
2574
- response.presence.members.map((member) => [member.presenceId, member])
3114
+ );
3115
+ if (strategyResponse.method === "presigned") {
3116
+ return await this.uploadWithPresignedUrl(strategyResponse, file);
3117
+ }
3118
+ if (strategyResponse.method === "direct") {
3119
+ const formData = new FormData();
3120
+ formData.append("file", file);
3121
+ const response = await this.http.request(
3122
+ "PUT",
3123
+ `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
3124
+ {
3125
+ body: formData,
3126
+ headers: {
3127
+ // Don't set Content-Type, let browser set multipart boundary
3128
+ }
3129
+ }
3130
+ );
3131
+ return { data: response, error: null };
3132
+ }
3133
+ throw new ResultError(
3134
+ `Unsupported upload method: ${strategyResponse.method}`,
3135
+ 500,
3136
+ "STORAGE_ERROR"
3137
+ );
3138
+ } catch (error) {
3139
+ return {
3140
+ data: null,
3141
+ error: error instanceof ResultError ? error : new ResultError("Upload failed", 500, "STORAGE_ERROR")
3142
+ };
3143
+ }
3144
+ }
3145
+ /**
3146
+ * Upload a file under an automatically generated, collision-free key.
3147
+ * The key is derived client-side from the filename (sanitized base +
3148
+ * timestamp + random suffix) and uploaded through the standard
3149
+ * {@link upload} path, so repeated uploads of the same file never
3150
+ * overwrite each other. Reads the filename structurally to avoid assuming
3151
+ * a global `File` (which Node 18 does not expose).
3152
+ * @param file - File or Blob to upload
3153
+ */
3154
+ async uploadAuto(file) {
3155
+ const filename = "name" in file && typeof file.name === "string" ? file.name : "file";
3156
+ return this.upload(generateObjectKey(filename), file);
3157
+ }
3158
+ /**
3159
+ * Internal method to handle presigned URL uploads
3160
+ */
3161
+ async uploadWithPresignedUrl(strategy, file) {
3162
+ try {
3163
+ const formData = new FormData();
3164
+ if (strategy.fields) {
3165
+ Object.entries(strategy.fields).forEach(([key, value]) => {
3166
+ formData.append(key, value);
3167
+ });
3168
+ }
3169
+ formData.append("file", file);
3170
+ const uploadResponse = await fetch(strategy.uploadUrl, {
3171
+ method: "POST",
3172
+ body: formData
3173
+ });
3174
+ if (!uploadResponse.ok) {
3175
+ throw new ResultError(
3176
+ `Upload to storage failed: ${uploadResponse.statusText}`,
3177
+ uploadResponse.status,
3178
+ "STORAGE_ERROR"
3179
+ );
3180
+ }
3181
+ if (strategy.confirmRequired && strategy.confirmUrl) {
3182
+ const confirmResponse = await this.http.post(
3183
+ strategy.confirmUrl,
3184
+ {
3185
+ size: file.size,
3186
+ contentType: file.type || "application/octet-stream"
3187
+ }
3188
+ );
3189
+ return { data: confirmResponse, error: null };
3190
+ }
3191
+ return {
3192
+ data: {
3193
+ key: strategy.key,
3194
+ bucket: this.bucketName,
3195
+ size: file.size,
3196
+ mimeType: file.type || "application/octet-stream",
3197
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
3198
+ url: this.getPublicUrl(strategy.key).data?.publicUrl
3199
+ },
3200
+ error: null
3201
+ };
3202
+ } catch (error) {
3203
+ throw error instanceof ResultError ? error : new ResultError("Presigned upload failed", 500, "STORAGE_ERROR");
3204
+ }
3205
+ }
3206
+ /**
3207
+ * Download a file
3208
+ * Uses the download strategy from backend (direct or presigned)
3209
+ * @param path - The object key/path
3210
+ * Returns the file as a Blob
3211
+ */
3212
+ async download(path) {
3213
+ try {
3214
+ const encodedKey = encodeURIComponent(path);
3215
+ let strategyResponse;
3216
+ try {
3217
+ strategyResponse = await this.http.get(
3218
+ `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encodedKey}`
3219
+ );
3220
+ } catch (err) {
3221
+ const status = err instanceof ResultError ? err.statusCode : void 0;
3222
+ if (status === 404 || status === 405) {
3223
+ strategyResponse = await this.http.post(
3224
+ `/api/storage/buckets/${this.bucketName}/objects/${encodedKey}/download-strategy`,
3225
+ {}
2575
3226
  );
2576
3227
  } else {
2577
- subscription.status = "rejected";
2578
- subscription.members.clear();
3228
+ throw err;
2579
3229
  }
2580
- this.settleSubscription(subscription, response, false);
2581
- });
2582
- });
2583
- return subscription.pending;
2584
- }
2585
- settleSubscription(subscription, response, incrementEpoch) {
2586
- if (incrementEpoch) {
2587
- subscription.epoch++;
2588
- }
2589
- subscription.settlePending?.(response);
2590
- }
2591
- applyPresenceEvent(event, message) {
2592
- if (event !== "presence:join" && event !== "presence:leave") {
2593
- return;
2594
- }
2595
- const presenceEvent = message;
2596
- const channel = presenceEvent.meta?.channel;
2597
- const member = presenceEvent.member;
2598
- if (!channel || !member) {
2599
- return;
2600
- }
2601
- const subscription = this.subscriptions.get(channel);
2602
- if (!subscription) {
2603
- return;
2604
- }
2605
- if (event === "presence:join") {
2606
- subscription.members.set(member.presenceId, member);
2607
- } else {
2608
- subscription.members.delete(member.presenceId);
2609
- }
2610
- }
2611
- get isConnected() {
2612
- return this.socket?.connected ?? false;
2613
- }
2614
- get connectionState() {
2615
- if (!this.socket) {
2616
- return "disconnected";
2617
- }
2618
- return this.socket.connected ? "connected" : "connecting";
2619
- }
2620
- get socketId() {
2621
- return this.socket?.id;
2622
- }
2623
- async subscribe(channel) {
2624
- let subscription = this.subscriptions.get(channel);
2625
- if (subscription) {
2626
- if (subscription.pending) {
2627
- return subscription.pending;
2628
3230
  }
2629
- if (subscription.status === "subscribed") {
2630
- return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
3231
+ const downloadUrl = strategyResponse.url;
3232
+ const headers = {};
3233
+ if (strategyResponse.method === "direct") {
3234
+ Object.assign(headers, this.http.getHeaders());
2631
3235
  }
2632
- } else {
2633
- subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
2634
- this.subscriptions.set(channel, subscription);
2635
- }
2636
- if (!this.socket?.connected) {
2637
- try {
2638
- await this.connect();
2639
- } catch (error) {
2640
- if (this.subscriptions.get(channel) === subscription) {
2641
- this.subscriptions.delete(channel);
3236
+ const response = await fetch(downloadUrl, {
3237
+ method: "GET",
3238
+ headers
3239
+ });
3240
+ if (!response.ok) {
3241
+ try {
3242
+ const error = await response.json();
3243
+ throw ResultError.fromApiError(error);
3244
+ } catch {
3245
+ throw new ResultError(
3246
+ `Download failed: ${response.statusText}`,
3247
+ response.status,
3248
+ "STORAGE_ERROR"
3249
+ );
2642
3250
  }
2643
- const message = error instanceof Error ? error.message : "Connection failed";
2644
- return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
2645
3251
  }
3252
+ const blob = await response.blob();
3253
+ return { data: blob, error: null };
3254
+ } catch (error) {
3255
+ return {
3256
+ data: null,
3257
+ error: error instanceof ResultError ? error : new ResultError("Download failed", 500, "STORAGE_ERROR")
3258
+ };
2646
3259
  }
2647
- return subscription.pending ?? this.requestSubscription(channel, subscription);
2648
3260
  }
2649
- unsubscribe(channel) {
2650
- const subscription = this.subscriptions.get(channel);
2651
- if (!subscription) {
2652
- return;
2653
- }
2654
- this.subscriptions.delete(channel);
2655
- this.settleSubscription(
2656
- subscription,
2657
- {
2658
- ok: false,
2659
- channel,
2660
- error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
2661
- },
2662
- true
2663
- );
2664
- if (this.socket?.connected) {
2665
- this.socket.emit("realtime:unsubscribe", { channel });
2666
- }
3261
+ /**
3262
+ * Get the public URL for an object in a public bucket.
3263
+ *
3264
+ * Pure string construction — no network call, no auth. The URL only resolves
3265
+ * if the bucket is public; for private objects use {@link createSignedUrl}.
3266
+ *
3267
+ * @param path - The object key/path
3268
+ * @returns `{ data: { publicUrl }, error }` — matches the external SDK pattern,
3269
+ * so `const { data } = getPublicUrl(path)` then `data.publicUrl`.
3270
+ */
3271
+ getPublicUrl(path) {
3272
+ const publicUrl = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
3273
+ return { data: { publicUrl }, error: null };
2667
3274
  }
2668
- async publish(channel, event, payload) {
2669
- if (!this.socket?.connected) {
2670
- throw new Error("Not connected to realtime server. Call connect() first.");
3275
+ /**
3276
+ * Resolve a download strategy (signed or direct URL) for an object with a
3277
+ * caller-supplied TTL. Prefers the canonical GET route and falls back to the
3278
+ * legacy POST alias so signed-URL creation still works against older backends
3279
+ * that predate the GET route (they return 404/405 for it). A genuine
3280
+ * "object not found" (STORAGE_NOT_FOUND) is not retried.
3281
+ */
3282
+ async requestDownloadStrategy(path, expiresIn) {
3283
+ const encoded = encodeURIComponent(path);
3284
+ try {
3285
+ return await this.http.get(
3286
+ `/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encoded}`,
3287
+ { params: { expiresIn: expiresIn.toString() } }
3288
+ );
3289
+ } catch (error) {
3290
+ const status = error instanceof ResultError ? error.statusCode : void 0;
3291
+ const isMissingRoute = (status === 404 || status === 405) && !(error instanceof ResultError && error.error === "STORAGE_NOT_FOUND");
3292
+ if (!isMissingRoute) {
3293
+ throw error;
3294
+ }
3295
+ return await this.http.post(
3296
+ `/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
3297
+ { expiresIn }
3298
+ );
2671
3299
  }
2672
- this.socket.emit("realtime:publish", { channel, event, payload });
2673
- }
2674
- on(event, callback) {
2675
- const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
2676
- listeners.add(callback);
2677
- this.eventListeners.set(event, listeners);
2678
3300
  }
2679
- off(event, callback) {
2680
- const listeners = this.eventListeners.get(event);
2681
- listeners?.delete(callback);
2682
- if (listeners?.size === 0) {
2683
- this.eventListeners.delete(event);
3301
+ /**
3302
+ * Create a signed URL for an object.
3303
+ *
3304
+ * Returns a time-limited, credential-free URL that can be handed directly to
3305
+ * a browser (`<img src>`), an email, or a third party — no SDK or session is
3306
+ * needed to fetch it. Authorization is enforced when the URL is minted (the
3307
+ * caller must be allowed to read the object), so the resulting link is a
3308
+ * pre-authorized capability scoped to this one object until it expires.
3309
+ *
3310
+ * @param path - The object key/path
3311
+ * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d).
3312
+ * Honored for private buckets; public buckets return their long-lived URL.
3313
+ */
3314
+ async createSignedUrl(path, expiresIn = 3600) {
3315
+ try {
3316
+ const strategy = await this.requestDownloadStrategy(path, expiresIn);
3317
+ return {
3318
+ data: {
3319
+ signedUrl: strategy.url,
3320
+ expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
3321
+ },
3322
+ error: null
3323
+ };
3324
+ } catch (error) {
3325
+ return {
3326
+ data: null,
3327
+ error: error instanceof ResultError ? error : new ResultError(
3328
+ "Failed to create signed URL",
3329
+ 500,
3330
+ "STORAGE_ERROR"
3331
+ )
3332
+ };
2684
3333
  }
2685
3334
  }
2686
- once(event, callback) {
2687
- const wrapper = (payload) => {
2688
- this.off(event, wrapper);
2689
- callback(payload);
2690
- };
2691
- this.on(event, wrapper);
2692
- }
2693
- getSubscribedChannels() {
2694
- return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
2695
- }
2696
- getPresenceState(channel) {
2697
- return [...this.subscriptions.get(channel)?.members.values() ?? []];
2698
- }
2699
- };
2700
- var Emails = class {
2701
- constructor(http) {
2702
- this.http = http;
2703
- }
2704
3335
  /**
2705
- * Send a custom HTML email
2706
- * @param options Email options including recipients, subject, and HTML content
3336
+ * Create signed URLs for multiple objects in a single call.
3337
+ *
3338
+ * Each entry resolves independently: a failure on one key (not found / not
3339
+ * permitted) is reported on that entry's `error` without failing the rest.
3340
+ *
3341
+ * @param paths - The object keys/paths
3342
+ * @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
2707
3343
  */
2708
- async send(options) {
3344
+ async createSignedUrls(paths, expiresIn = 3600) {
2709
3345
  try {
2710
- const data = await this.http.post("/api/email/send-raw", options);
3346
+ const data = await Promise.all(
3347
+ paths.map(async (path) => {
3348
+ const { data: signed, error } = await this.createSignedUrl(
3349
+ path,
3350
+ expiresIn
3351
+ );
3352
+ return {
3353
+ path,
3354
+ signedUrl: signed?.signedUrl ?? null,
3355
+ error: error ? error.message : null
3356
+ };
3357
+ })
3358
+ );
2711
3359
  return { data, error: null };
2712
3360
  } catch (error) {
2713
- if (error instanceof Error && error.name === "AbortError") {
2714
- throw error;
2715
- }
2716
3361
  return {
2717
3362
  data: null,
2718
- error: error instanceof InsForgeError ? error : new InsForgeError(
2719
- error instanceof Error ? error.message : "Email send failed",
3363
+ error: error instanceof ResultError ? error : new ResultError(
3364
+ "Failed to create signed URLs",
2720
3365
  500,
2721
- "EMAIL_ERROR"
3366
+ "STORAGE_ERROR"
2722
3367
  )
2723
3368
  };
2724
3369
  }
2725
3370
  }
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
3371
  /**
2735
- * Create a Stripe Checkout Session through the InsForge backend.
2736
- *
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
- * });
2745
- *
2746
- * if (!error && data.checkoutSession.url) {
2747
- * window.location.assign(data.checkoutSession.url);
2748
- * }
2749
- * ```
3372
+ * List objects in the bucket
3373
+ * @param prefix - Filter by key prefix
3374
+ * @param search - Search in file names
3375
+ * @param limit - Maximum number of results (default: 100, max: 1000)
3376
+ * @param offset - Number of results to skip
2750
3377
  */
2751
- async createCheckoutSession(environment, request) {
3378
+ async list(options) {
2752
3379
  try {
2753
- const data = await this.http.post(
2754
- `${providerEnvironmentPath("stripe", environment)}/checkout-sessions`,
2755
- request,
2756
- { idempotent: !!request.idempotencyKey }
3380
+ const params = {};
3381
+ if (options?.prefix) {
3382
+ params.prefix = options.prefix;
3383
+ }
3384
+ if (options?.search) {
3385
+ params.search = options.search;
3386
+ }
3387
+ if (options?.limit) {
3388
+ params.limit = options.limit.toString();
3389
+ }
3390
+ if (options?.offset) {
3391
+ params.offset = options.offset.toString();
3392
+ }
3393
+ const response = await this.http.get(
3394
+ `/api/storage/buckets/${this.bucketName}/objects`,
3395
+ { params }
2757
3396
  );
2758
- return { data, error: null };
3397
+ return { data: response, error: null };
2759
3398
  } catch (error) {
2760
- return wrapError(
2761
- error,
2762
- "Stripe checkout session creation failed"
2763
- );
3399
+ return {
3400
+ data: null,
3401
+ error: error instanceof ResultError ? error : new ResultError("List failed", 500, "STORAGE_ERROR")
3402
+ };
2764
3403
  }
2765
3404
  }
2766
3405
  /**
2767
- * Create a Stripe Billing Portal Session for a mapped billing subject.
3406
+ * Delete one or more files.
3407
+ *
3408
+ * A string uses the single-object endpoint. An array uses the batch endpoint,
3409
+ * which accepts at most 1000 keys and returns one result per key.
3410
+ *
3411
+ * @param pathOrPaths - One object key/path or an array of object keys/paths
2768
3412
  */
2769
- async createCustomerPortalSession(environment, request) {
3413
+ async remove(pathOrPaths) {
2770
3414
  try {
2771
- const data = await this.http.post(
2772
- `${providerEnvironmentPath("stripe", environment)}/customer-portal-sessions`,
2773
- request
3415
+ const response = Array.isArray(pathOrPaths) ? await this.http.delete(
3416
+ `/api/storage/buckets/${this.bucketName}/objects`,
3417
+ { body: { keys: pathOrPaths } }
3418
+ ) : await this.http.delete(
3419
+ `/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(pathOrPaths)}`
2774
3420
  );
2775
- return { data, error: null };
3421
+ return { data: response, error: null };
2776
3422
  } catch (error) {
2777
- return wrapError(
2778
- error,
2779
- "Stripe customer portal session creation failed"
2780
- );
3423
+ return {
3424
+ data: null,
3425
+ error: error instanceof ResultError ? error : new ResultError("Delete failed", 500, "STORAGE_ERROR")
3426
+ };
2781
3427
  }
2782
3428
  }
2783
3429
  };
2784
- var RazorpayPayments = class {
3430
+ var Storage = class {
2785
3431
  constructor(http) {
2786
3432
  this.http = http;
2787
3433
  }
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
- }
3434
+ http;
3435
+ /**
3436
+ * Get a bucket instance for operations
3437
+ * @param bucketName - Name of the bucket
3438
+ */
3439
+ from(bucketName) {
3440
+ return new StorageBucket(bucketName, this.http);
2798
3441
  }
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
- }
3442
+ };
3443
+
3444
+ // src/modules/support.ts
3445
+ var DEFAULT_ENDPOINT2 = "https://beta.result.dev";
3446
+ var Support = class {
3447
+ handle;
3448
+ endpoint;
3449
+ // Fallback for non-browser runtimes and private mode.
3450
+ mem = { threads: [] };
3451
+ constructor(options) {
3452
+ this.handle = options?.handle ?? "";
3453
+ this.endpoint = (options?.endpoint || DEFAULT_ENDPOINT2).replace(/\/$/, "");
2809
3454
  }
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
- }
3455
+ /**
3456
+ * Start a new support conversation. The returned access key is stored
3457
+ * locally so `getThread`/`sendMessage` work without passing it around.
3458
+ */
3459
+ async startThread(options) {
3460
+ const guard = this.requireHandle();
3461
+ if (guard) return { data: null, error: guard };
3462
+ const { data, error } = await this.request(
3463
+ "POST",
3464
+ "/api/support/threads",
3465
+ {
3466
+ username: this.handle,
3467
+ email: options.email,
3468
+ name: options.name,
3469
+ message: options.message
3470
+ }
3471
+ );
3472
+ if (data) {
3473
+ this.remember(data.id, data.accessKey, options);
3474
+ }
3475
+ return { data, error };
3476
+ }
3477
+ /** Fetch a thread (status + full message history) with its stored access key. */
3478
+ async getThread(threadId, accessKey) {
3479
+ const guard = this.requireHandle();
3480
+ if (guard) return { data: null, error: guard };
3481
+ const key = accessKey ?? this.keyFor(threadId);
3482
+ if (!key) {
3483
+ return { data: null, error: this.missingKey(threadId) };
3484
+ }
3485
+ const { data, error } = await this.request(
3486
+ "GET",
3487
+ `/api/support/threads/${encodeURIComponent(threadId)}?key=${encodeURIComponent(key)}`
3488
+ );
3489
+ return { data: data?.thread ?? null, error };
3490
+ }
3491
+ /** Send a visitor reply on an existing thread (reopens it if closed). */
3492
+ async sendMessage(threadId, message, accessKey) {
3493
+ const guard = this.requireHandle();
3494
+ if (guard) return { data: null, error: guard };
3495
+ const key = accessKey ?? this.keyFor(threadId);
3496
+ if (!key) {
3497
+ return { data: null, error: this.missingKey(threadId) };
3498
+ }
3499
+ const { data, error } = await this.request(
3500
+ "POST",
3501
+ `/api/support/threads/${encodeURIComponent(threadId)}/messages`,
3502
+ { message, key }
3503
+ );
3504
+ return { data, error };
3505
+ }
3506
+ /** Threads started from this browser (ids only; fetch each with getThread). */
3507
+ listThreads() {
3508
+ return this.load().threads.map(({ id }) => ({ id }));
3509
+ }
3510
+ /** The contact details used when this browser last started a thread. */
3511
+ getContact() {
3512
+ return this.load().contact;
3513
+ }
3514
+ // -- internals -------------------------------------------------------------
3515
+ requireHandle() {
3516
+ if (this.handle) return null;
3517
+ return new ResultError(
3518
+ "Missing support handle. Pass support: { handle } to createClient().",
3519
+ 400,
3520
+ "MISSING_FIELD"
3521
+ );
2823
3522
  }
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
- }
3523
+ missingKey(threadId) {
3524
+ return new ResultError(
3525
+ `No access key stored for thread ${threadId}. Pass the accessKey returned by startThread().`,
3526
+ 401,
3527
+ "AUTH_UNAUTHORIZED"
3528
+ );
2837
3529
  }
2838
- async cancelSubscription(environment, subscriptionId, request = {}) {
3530
+ async request(method, path, body) {
2839
3531
  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 };
3532
+ const response = await fetch(`${this.endpoint}${path}`, {
3533
+ method,
3534
+ headers: body ? { "Content-Type": "application/json" } : void 0,
3535
+ body: body ? JSON.stringify(body) : void 0
3536
+ });
3537
+ const payload = await response.json().catch(() => null);
3538
+ if (!response.ok) {
3539
+ return {
3540
+ data: null,
3541
+ error: new ResultError(
3542
+ payload?.error?.message ?? `Support request failed (${response.status})`,
3543
+ response.status,
3544
+ payload?.error?.code ?? "UNKNOWN_ERROR"
3545
+ )
3546
+ };
3547
+ }
3548
+ return { data: payload, error: null };
2847
3549
  } catch (error) {
2848
- return wrapError(
2849
- error,
2850
- "Razorpay subscription cancellation failed"
2851
- );
3550
+ if (error instanceof Error && error.name === "AbortError") {
3551
+ throw error;
3552
+ }
3553
+ return {
3554
+ data: null,
3555
+ error: new ResultError(
3556
+ error instanceof Error ? error.message : "Support request failed",
3557
+ 500,
3558
+ "UNKNOWN_ERROR"
3559
+ )
3560
+ };
2852
3561
  }
2853
3562
  }
2854
- async pauseSubscription(environment, subscriptionId) {
3563
+ storageKey() {
3564
+ return `rsup_${this.handle}`;
3565
+ }
3566
+ load() {
2855
3567
  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
- );
3568
+ const raw = localStorage.getItem(this.storageKey());
3569
+ if (raw) {
3570
+ const parsed = JSON.parse(raw);
3571
+ if (parsed && Array.isArray(parsed.threads)) {
3572
+ return parsed;
3573
+ }
3574
+ }
3575
+ } catch {
2868
3576
  }
3577
+ return this.mem;
2869
3578
  }
2870
- async resumeSubscription(environment, subscriptionId) {
3579
+ save(state) {
3580
+ this.mem = state;
2871
3581
  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
- );
3582
+ localStorage.setItem(this.storageKey(), JSON.stringify(state));
3583
+ } catch {
2884
3584
  }
2885
3585
  }
2886
- };
2887
- var Payments = class {
2888
- constructor(http) {
2889
- this.stripe = new StripePayments(http);
2890
- this.razorpay = new RazorpayPayments(http);
3586
+ keyFor(threadId) {
3587
+ return this.load().threads.find((thread) => thread.id === threadId)?.key;
3588
+ }
3589
+ remember(id, key, options) {
3590
+ const state = this.load();
3591
+ const threads = state.threads.filter((thread) => thread.id !== id);
3592
+ threads.unshift({ id, key });
3593
+ this.save({
3594
+ contact: {
3595
+ email: options.email,
3596
+ name: options.name ?? state.contact?.name
3597
+ },
3598
+ threads
3599
+ });
2891
3600
  }
2892
3601
  };
2893
- var InsForgeClient = class {
3602
+
3603
+ // src/client.ts
3604
+ var ResultClient = class {
3605
+ http;
3606
+ tokenManager;
3607
+ auth;
3608
+ database;
3609
+ storage;
3610
+ ai;
3611
+ functions;
3612
+ realtime;
3613
+ emails;
3614
+ analytics;
3615
+ support;
2894
3616
  constructor(config = {}) {
2895
3617
  const logger = new Logger(config.debug);
2896
3618
  this.tokenManager = new TokenManager();
@@ -2915,7 +3637,8 @@ var InsForgeClient = class {
2915
3637
  () => this.http.getValidAccessToken()
2916
3638
  );
2917
3639
  this.emails = new Emails(this.http);
2918
- this.payments = new Payments(this.http);
3640
+ this.analytics = new Analytics(config.analytics);
3641
+ this.support = new Support(config.support);
2919
3642
  }
2920
3643
  /**
2921
3644
  * Get the underlying HTTP client for custom requests
@@ -2939,16 +3662,16 @@ var InsForgeClient = class {
2939
3662
  *
2940
3663
  * Use this when an external auth provider (Better Auth, Clerk, Auth0,
2941
3664
  * 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
3665
+ * long-lived Result client in sync. Without this, you'd have to call
2943
3666
  * `client.getHttpClient().setAuthToken(token)` AND reach into the private
2944
3667
  * realtime token manager separately.
2945
3668
  *
2946
3669
  * @example
2947
3670
  * ```typescript
2948
- * import { AuthChangeEvent } from '@insforge/sdk';
3671
+ * import { AuthChangeEvent } from '@resultdev/sdk';
2949
3672
  *
2950
3673
  * // Refresh a third-party-issued JWT periodically
2951
- * const { token } = await fetch('/api/insforge-token').then((r) => r.json());
3674
+ * const { token } = await fetch('/api/backend-token').then((r) => r.json());
2952
3675
  * client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
2953
3676
  *
2954
3677
  * // Sign-out
@@ -2963,193 +3686,142 @@ var InsForgeClient = class {
2963
3686
  this.tokenManager.setAccessToken(token, event);
2964
3687
  }
2965
3688
  }
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
3689
  };
2975
- var DEFAULT_ACCESS_TOKEN_COOKIE = "insforge_access_token";
2976
- var DEFAULT_REFRESH_TOKEN_COOKIE = "insforge_refresh_token";
2977
- var EXPIRED_DATE = /* @__PURE__ */ new Date(0);
2978
- function getAccessTokenCookieName(names) {
2979
- return names?.accessToken ?? DEFAULT_ACCESS_TOKEN_COOKIE;
2980
- }
2981
- function getRefreshTokenCookieName(names) {
2982
- return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
2983
- }
2984
- function getCookieValue(cookies, name) {
2985
- if (!cookies) {
2986
- return null;
2987
- }
2988
- const value = cookies.get(name);
2989
- if (typeof value === "string") {
2990
- return value || null;
2991
- }
2992
- if (value && typeof value.value === "string") {
2993
- return value.value || null;
2994
- }
2995
- return null;
2996
- }
2997
- function getCookieValueFromHeader(cookieHeader, name) {
2998
- if (!cookieHeader) {
2999
- return null;
3000
- }
3001
- const parts = cookieHeader.split(";");
3002
- for (const part of parts) {
3003
- const [rawName, ...rawValue] = part.trim().split("=");
3004
- if (rawName !== name) {
3005
- continue;
3006
- }
3007
- try {
3008
- return decodeURIComponent(rawValue.join("="));
3009
- } catch {
3010
- return rawValue.join("=");
3011
- }
3012
- }
3013
- return null;
3014
- }
3015
- function getBrowserCookie(name) {
3016
- if (typeof document === "undefined") {
3017
- return null;
3018
- }
3019
- return getCookieValueFromHeader(document.cookie, name);
3020
- }
3021
- function defaultCookieOptions() {
3022
- const secure = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : typeof location !== "undefined" && location.protocol === "https:";
3023
- return {
3024
- path: "/",
3025
- sameSite: "lax",
3026
- secure
3027
- };
3028
- }
3029
- function accessTokenCookieOptions(token, overrides) {
3030
- return {
3031
- ...defaultCookieOptions(),
3032
- httpOnly: false,
3033
- expires: getJwtExpiration(token) ?? void 0,
3034
- ...overrides
3035
- };
3036
- }
3037
- function refreshTokenCookieOptions(token, overrides) {
3038
- return {
3039
- ...defaultCookieOptions(),
3040
- httpOnly: true,
3041
- expires: getJwtExpiration(token) ?? void 0,
3042
- ...overrides
3043
- };
3044
- }
3045
- function expiredCookieOptions(overrides) {
3046
- const { expires: _expires, maxAge: _maxAge, ...safeOverrides } = overrides ?? {};
3047
- return {
3048
- ...defaultCookieOptions(),
3049
- ...safeOverrides,
3050
- expires: EXPIRED_DATE,
3051
- maxAge: 0
3052
- };
3053
- }
3054
- function setCookie(cookies, name, value, options) {
3055
- if (!cookies?.set) {
3056
- return;
3057
- }
3058
- cookies.set(name, value, options);
3059
- }
3060
- function deleteCookie(cookies, name, options) {
3061
- if (!cookies) {
3062
- return;
3690
+
3691
+ // src/ssr/server-client.ts
3692
+ function createServerClient(options = {}) {
3693
+ let { baseUrl, anonKey } = options;
3694
+ try {
3695
+ baseUrl ||= process.env.NEXT_PUBLIC_BACKEND_URL;
3696
+ anonKey ||= process.env.NEXT_PUBLIC_BACKEND_ANON_KEY;
3697
+ } catch {
3063
3698
  }
3064
- if (cookies.set) {
3065
- cookies.set(name, "", expiredCookieOptions(options));
3066
- return;
3699
+ if (!baseUrl || !anonKey) {
3700
+ throw new Error(
3701
+ "Missing Result baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_BACKEND_URL and NEXT_PUBLIC_BACKEND_ANON_KEY."
3702
+ );
3067
3703
  }
3068
- cookies.delete?.(name);
3704
+ const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
3705
+ return new ResultClient({
3706
+ ...options,
3707
+ baseUrl,
3708
+ anonKey,
3709
+ isServerMode: true,
3710
+ accessToken: accessToken ?? void 0,
3711
+ // The cookie/option token is the only credential source here; shadow any
3712
+ // untyped edgeFunctionToken smuggled through the options spread.
3713
+ edgeFunctionToken: void 0
3714
+ });
3069
3715
  }
3070
- function serializeCookie(name, value, options = {}) {
3071
- const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
3072
- if (options.maxAge !== void 0) {
3073
- parts.push(`Max-Age=${options.maxAge}`);
3074
- }
3075
- if (options.domain) {
3076
- parts.push(`Domain=${options.domain}`);
3077
- }
3078
- if (options.path) {
3079
- parts.push(`Path=${options.path}`);
3080
- }
3081
- if (options.expires) {
3082
- parts.push(`Expires=${options.expires.toUTCString()}`);
3083
- }
3084
- if (options.httpOnly) {
3085
- parts.push("HttpOnly");
3086
- }
3087
- if (options.secure) {
3088
- parts.push("Secure");
3089
- }
3090
- if (options.sameSite) {
3091
- const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
3092
- parts.push(`SameSite=${sameSite}`);
3716
+
3717
+ // src/ssr/auth-actions.ts
3718
+ function persistSessionCookies(cookies, data, settings) {
3719
+ if (!data?.accessToken) {
3720
+ return;
3093
3721
  }
3094
- return parts.join("; ");
3095
- }
3096
- function appendSetCookie(headers, name, value, options) {
3097
- headers.append("Set-Cookie", serializeCookie(name, value, options));
3722
+ setAuthCookies(
3723
+ cookies,
3724
+ {
3725
+ accessToken: data.accessToken,
3726
+ refreshToken: data.refreshToken
3727
+ },
3728
+ settings
3729
+ );
3098
3730
  }
3099
- function setAuthCookies(cookies, tokens, settings = {}) {
3100
- const accessName = getAccessTokenCookieName(settings.names);
3101
- const refreshName = getRefreshTokenCookieName(settings.names);
3102
- const accessOptions = accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken);
3103
- setCookie(cookies, accessName, tokens.accessToken, accessOptions);
3104
- if (tokens.refreshToken) {
3105
- setCookie(
3106
- cookies,
3107
- refreshName,
3108
- tokens.refreshToken,
3109
- refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3110
- );
3731
+ function sanitizeAuthData(data) {
3732
+ if (!data) {
3733
+ return null;
3111
3734
  }
3735
+ const {
3736
+ accessToken: _accessToken,
3737
+ refreshToken: _refreshToken,
3738
+ csrfToken: _csrfToken,
3739
+ ...safeData
3740
+ } = data;
3741
+ return safeData;
3112
3742
  }
3113
- function clearAuthCookies(cookies, settings = {}) {
3114
- const accessName = getAccessTokenCookieName(settings.names);
3115
- const refreshName = getRefreshTokenCookieName(settings.names);
3116
- const accessOptions = expiredCookieOptions(settings.options?.accessToken);
3117
- const refreshOptions = expiredCookieOptions(settings.options?.refreshToken);
3118
- deleteCookie(cookies, accessName, accessOptions);
3119
- deleteCookie(cookies, refreshName, refreshOptions);
3743
+ function toSafeAuthResult(result) {
3744
+ return {
3745
+ data: sanitizeAuthData(result.data),
3746
+ error: result.error
3747
+ };
3120
3748
  }
3121
- function setAuthCookieHeaders(headers, tokens, settings = {}) {
3122
- const accessName = getAccessTokenCookieName(settings.names);
3123
- const refreshName = getRefreshTokenCookieName(settings.names);
3124
- appendSetCookie(
3125
- headers,
3126
- accessName,
3127
- tokens.accessToken,
3128
- accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken)
3129
- );
3130
- if (tokens.refreshToken) {
3131
- appendSetCookie(
3132
- headers,
3133
- refreshName,
3134
- tokens.refreshToken,
3135
- refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
3749
+ function createAuthActions(options = {}) {
3750
+ const {
3751
+ cookies,
3752
+ requestCookies,
3753
+ responseCookies,
3754
+ names,
3755
+ options: cookieOptions,
3756
+ ...clientOptions
3757
+ } = options;
3758
+ const readCookies = requestCookies ?? cookies;
3759
+ const writeCookies = responseCookies ?? cookies;
3760
+ if (!writeCookies?.set) {
3761
+ throw new Error(
3762
+ "createAuthActions() requires a writable cookie store. Pass cookies in Server Actions or responseCookies in Route Handlers."
3136
3763
  );
3137
3764
  }
3765
+ const cookieSettings = {
3766
+ names,
3767
+ options: cookieOptions
3768
+ };
3769
+ const createClient = () => createServerClient({
3770
+ ...clientOptions,
3771
+ names,
3772
+ options: cookieOptions,
3773
+ cookies: readCookies
3774
+ });
3775
+ return {
3776
+ signUp: async (request) => {
3777
+ const result = await createClient().auth.signUp(request);
3778
+ persistSessionCookies(writeCookies, result.data, cookieSettings);
3779
+ return toSafeAuthResult(result);
3780
+ },
3781
+ signInWithPassword: async (request) => {
3782
+ const result = await createClient().auth.signInWithPassword(request);
3783
+ persistSessionCookies(writeCookies, result.data, cookieSettings);
3784
+ return toSafeAuthResult(
3785
+ result
3786
+ );
3787
+ },
3788
+ signInWithOAuth: async (providerOrOptions, signInOptions) => {
3789
+ return createClient().auth.signInWithOAuth(
3790
+ providerOrOptions,
3791
+ signInOptions
3792
+ );
3793
+ },
3794
+ signInWithIdToken: async (credentials) => {
3795
+ const result = await createClient().auth.signInWithIdToken(credentials);
3796
+ persistSessionCookies(writeCookies, result.data, cookieSettings);
3797
+ return toSafeAuthResult(
3798
+ result
3799
+ );
3800
+ },
3801
+ exchangeOAuthCode: async (code, codeVerifier) => {
3802
+ const result = await createClient().auth.exchangeOAuthCode(
3803
+ code,
3804
+ codeVerifier
3805
+ );
3806
+ persistSessionCookies(writeCookies, result.data, cookieSettings);
3807
+ return toSafeAuthResult(
3808
+ result
3809
+ );
3810
+ },
3811
+ verifyEmail: async (request) => {
3812
+ const result = await createClient().auth.verifyEmail(request);
3813
+ persistSessionCookies(writeCookies, result.data, cookieSettings);
3814
+ return toSafeAuthResult(result);
3815
+ },
3816
+ signOut: async () => {
3817
+ const result = await createClient().auth.signOut();
3818
+ clearAuthCookies(writeCookies, cookieSettings);
3819
+ return result;
3820
+ }
3821
+ };
3138
3822
  }
3139
- function clearAuthCookieHeaders(headers, settings = {}) {
3140
- appendSetCookie(
3141
- headers,
3142
- getAccessTokenCookieName(settings.names),
3143
- "",
3144
- expiredCookieOptions(settings.options?.accessToken)
3145
- );
3146
- appendSetCookie(
3147
- headers,
3148
- getRefreshTokenCookieName(settings.names),
3149
- "",
3150
- expiredCookieOptions(settings.options?.refreshToken)
3151
- );
3152
- }
3823
+
3824
+ // src/ssr/browser-client.ts
3153
3825
  async function parseRefreshResponse(response) {
3154
3826
  const contentType = response.headers.get("content-type");
3155
3827
  if (contentType?.includes("json")) {
@@ -3160,13 +3832,13 @@ async function parseRefreshResponse(response) {
3160
3832
  function toRefreshError(response, body) {
3161
3833
  if (body && typeof body === "object") {
3162
3834
  const errorBody = body;
3163
- return new InsForgeError(
3835
+ return new ResultError(
3164
3836
  typeof errorBody.message === "string" ? errorBody.message : "Failed to refresh auth session",
3165
3837
  typeof errorBody.statusCode === "number" ? errorBody.statusCode : response.status,
3166
3838
  typeof errorBody.error === "string" ? errorBody.error : ERROR_CODES.UNKNOWN_ERROR
3167
3839
  );
3168
3840
  }
3169
- return new InsForgeError(
3841
+ return new ResultError(
3170
3842
  typeof body === "string" && body ? body : "Failed to refresh auth session",
3171
3843
  response.status,
3172
3844
  ERROR_CODES.UNKNOWN_ERROR
@@ -3210,13 +3882,13 @@ function getRequestUrl(input) {
3210
3882
  function createBrowserClient(options = {}) {
3211
3883
  let { baseUrl, anonKey } = options;
3212
3884
  try {
3213
- baseUrl || (baseUrl = process.env.NEXT_PUBLIC_INSFORGE_URL);
3214
- anonKey || (anonKey = process.env.NEXT_PUBLIC_INSFORGE_ANON_KEY);
3885
+ baseUrl ||= process.env.NEXT_PUBLIC_BACKEND_URL;
3886
+ anonKey ||= process.env.NEXT_PUBLIC_BACKEND_ANON_KEY;
3215
3887
  } catch {
3216
3888
  }
3217
3889
  if (!baseUrl || !anonKey) {
3218
3890
  throw new Error(
3219
- "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3891
+ "Missing Result baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_BACKEND_URL and NEXT_PUBLIC_BACKEND_ANON_KEY."
3220
3892
  );
3221
3893
  }
3222
3894
  let accessToken = getBrowserCookie(getAccessTokenCookieName(options.names));
@@ -3231,7 +3903,9 @@ function createBrowserClient(options = {}) {
3231
3903
  }
3232
3904
  refreshPromise = (async () => {
3233
3905
  if (!fetchImpl) {
3234
- throw new Error("Fetch is not available. Please provide a fetch implementation.");
3906
+ throw new Error(
3907
+ "Fetch is not available. Please provide a fetch implementation."
3908
+ );
3235
3909
  }
3236
3910
  const response = await fetchImpl(refreshUrl, {
3237
3911
  method: "POST",
@@ -3256,7 +3930,10 @@ function createBrowserClient(options = {}) {
3256
3930
  return null;
3257
3931
  }
3258
3932
  accessToken = refreshBody.accessToken;
3259
- client?.setAccessToken(refreshBody.accessToken, AuthChangeEvent.TOKEN_REFRESHED);
3933
+ client?.setAccessToken(
3934
+ refreshBody.accessToken,
3935
+ AuthChangeEvent.TOKEN_REFRESHED
3936
+ );
3260
3937
  return refreshBody;
3261
3938
  })().finally(() => {
3262
3939
  sessionChecked = true;
@@ -3270,7 +3947,9 @@ function createBrowserClient(options = {}) {
3270
3947
  };
3271
3948
  const ssrFetch = async (input, init) => {
3272
3949
  if (!fetchImpl) {
3273
- throw new Error("Fetch is not available. Please provide a fetch implementation.");
3950
+ throw new Error(
3951
+ "Fetch is not available. Please provide a fetch implementation."
3952
+ );
3274
3953
  }
3275
3954
  if (shouldSkipRefresh(input)) {
3276
3955
  return fetchImpl(input, init);
@@ -3294,7 +3973,7 @@ function createBrowserClient(options = {}) {
3294
3973
  }
3295
3974
  return fetchImpl(input, withAuthHeader(init, refreshed.accessToken));
3296
3975
  };
3297
- client = new InsForgeClient({
3976
+ client = new ResultClient({
3298
3977
  ...options,
3299
3978
  baseUrl,
3300
3979
  anonKey,
@@ -3319,30 +3998,8 @@ function createBrowserClient(options = {}) {
3319
3998
  }
3320
3999
  return client;
3321
4000
  }
3322
- function createServerClient(options = {}) {
3323
- let { baseUrl, anonKey } = options;
3324
- try {
3325
- baseUrl || (baseUrl = process.env.NEXT_PUBLIC_INSFORGE_URL);
3326
- anonKey || (anonKey = process.env.NEXT_PUBLIC_INSFORGE_ANON_KEY);
3327
- } catch {
3328
- }
3329
- if (!baseUrl || !anonKey) {
3330
- throw new Error(
3331
- "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
3332
- );
3333
- }
3334
- const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
3335
- return new InsForgeClient({
3336
- ...options,
3337
- baseUrl,
3338
- anonKey,
3339
- isServerMode: true,
3340
- accessToken: accessToken ?? void 0,
3341
- // The cookie/option token is the only credential source here; shadow any
3342
- // untyped edgeFunctionToken smuggled through the options spread.
3343
- edgeFunctionToken: void 0
3344
- });
3345
- }
4001
+
4002
+ // src/ssr/refresh.ts
3346
4003
  function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
3347
4004
  headers.set("Content-Type", "application/json");
3348
4005
  return new Response(JSON.stringify(body), {
@@ -3351,18 +4008,18 @@ function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
3351
4008
  });
3352
4009
  }
3353
4010
  function normalizeError(error) {
3354
- if (error instanceof InsForgeError) {
4011
+ if (error instanceof ResultError) {
3355
4012
  return error;
3356
4013
  }
3357
4014
  if (error && typeof error === "object") {
3358
4015
  const body = error;
3359
- return new InsForgeError(
4016
+ return new ResultError(
3360
4017
  typeof body.message === "string" ? body.message : "Failed to refresh auth session",
3361
4018
  typeof body.statusCode === "number" ? body.statusCode : 500,
3362
4019
  typeof body.error === "string" ? body.error : ERROR_CODES.UNKNOWN_ERROR
3363
4020
  );
3364
4021
  }
3365
- return new InsForgeError(
4022
+ return new ResultError(
3366
4023
  error instanceof Error ? error.message : "Failed to refresh auth session",
3367
4024
  500,
3368
4025
  ERROR_CODES.UNKNOWN_ERROR
@@ -3384,14 +4041,17 @@ function readRefreshToken(options) {
3384
4041
  if (cookieValue) {
3385
4042
  return cookieValue;
3386
4043
  }
3387
- return getCookieValueFromHeader(options.request?.headers.get("cookie"), refreshCookieName);
4044
+ return getCookieValueFromHeader(
4045
+ options.request?.headers.get("cookie"),
4046
+ refreshCookieName
4047
+ );
3388
4048
  }
3389
4049
  async function refreshAuth(options = {}) {
3390
4050
  const headers = new Headers();
3391
4051
  const refreshToken = readRefreshToken(options);
3392
4052
  if (!refreshToken) {
3393
4053
  clearAuthCookieHeaders(headers, options);
3394
- const error2 = new InsForgeError(
4054
+ const error2 = new ResultError(
3395
4055
  "Refresh token cookie is missing",
3396
4056
  401,
3397
4057
  ERROR_CODES.AUTH_UNAUTHORIZED
@@ -3414,18 +4074,20 @@ async function refreshAuth(options = {}) {
3414
4074
  }
3415
4075
  let { baseUrl, anonKey } = options;
3416
4076
  try {
3417
- baseUrl || (baseUrl = process.env.NEXT_PUBLIC_INSFORGE_URL);
3418
- anonKey || (anonKey = process.env.NEXT_PUBLIC_INSFORGE_ANON_KEY);
4077
+ baseUrl ||= process.env.NEXT_PUBLIC_BACKEND_URL;
4078
+ anonKey ||= process.env.NEXT_PUBLIC_BACKEND_ANON_KEY;
3419
4079
  } catch {
3420
4080
  }
3421
4081
  if (!baseUrl || !anonKey) {
3422
4082
  throw new Error(
3423
- "Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to refreshAuth() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
4083
+ "Missing Result baseUrl or anonKey. Pass baseUrl and anonKey to refreshAuth() or set NEXT_PUBLIC_BACKEND_URL and NEXT_PUBLIC_BACKEND_ANON_KEY."
3424
4084
  );
3425
4085
  }
3426
4086
  const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
3427
4087
  if (!fetchImpl) {
3428
- throw new Error("Fetch is not available. Please provide a fetch implementation.");
4088
+ throw new Error(
4089
+ "Fetch is not available. Please provide a fetch implementation."
4090
+ );
3429
4091
  }
3430
4092
  const requestHeaders = new Headers(options.headers);
3431
4093
  requestHeaders.set("Authorization", `Bearer ${anonKey}`);
@@ -3503,99 +4165,8 @@ function createRefreshAuthRouter(options = {}) {
3503
4165
  POST: async (request) => (await refreshAuth({ ...options, request })).response
3504
4166
  };
3505
4167
  }
3506
- function persistSessionCookies(cookies, data, settings) {
3507
- if (!data?.accessToken) {
3508
- return;
3509
- }
3510
- setAuthCookies(
3511
- cookies,
3512
- {
3513
- accessToken: data.accessToken,
3514
- refreshToken: data.refreshToken
3515
- },
3516
- settings
3517
- );
3518
- }
3519
- function sanitizeAuthData(data) {
3520
- if (!data) {
3521
- return null;
3522
- }
3523
- const {
3524
- accessToken: _accessToken,
3525
- refreshToken: _refreshToken,
3526
- csrfToken: _csrfToken,
3527
- ...safeData
3528
- } = data;
3529
- return safeData;
3530
- }
3531
- function toSafeAuthResult(result) {
3532
- return {
3533
- data: sanitizeAuthData(result.data),
3534
- error: result.error
3535
- };
3536
- }
3537
- function createAuthActions(options = {}) {
3538
- const {
3539
- cookies,
3540
- requestCookies,
3541
- responseCookies,
3542
- names,
3543
- options: cookieOptions,
3544
- ...clientOptions
3545
- } = options;
3546
- const readCookies = requestCookies ?? cookies;
3547
- const writeCookies = responseCookies ?? cookies;
3548
- if (!writeCookies?.set) {
3549
- throw new Error(
3550
- "createAuthActions() requires a writable cookie store. Pass cookies in Server Actions or responseCookies in Route Handlers."
3551
- );
3552
- }
3553
- const cookieSettings = {
3554
- names,
3555
- options: cookieOptions
3556
- };
3557
- const createClient = () => createServerClient({
3558
- ...clientOptions,
3559
- names,
3560
- options: cookieOptions,
3561
- cookies: readCookies
3562
- });
3563
- return {
3564
- signUp: async (request) => {
3565
- const result = await createClient().auth.signUp(request);
3566
- persistSessionCookies(writeCookies, result.data, cookieSettings);
3567
- return toSafeAuthResult(result);
3568
- },
3569
- signInWithPassword: async (request) => {
3570
- const result = await createClient().auth.signInWithPassword(request);
3571
- persistSessionCookies(writeCookies, result.data, cookieSettings);
3572
- return toSafeAuthResult(result);
3573
- },
3574
- signInWithOAuth: async (providerOrOptions, signInOptions) => {
3575
- return createClient().auth.signInWithOAuth(providerOrOptions, signInOptions);
3576
- },
3577
- signInWithIdToken: async (credentials) => {
3578
- const result = await createClient().auth.signInWithIdToken(credentials);
3579
- persistSessionCookies(writeCookies, result.data, cookieSettings);
3580
- return toSafeAuthResult(result);
3581
- },
3582
- exchangeOAuthCode: async (code, codeVerifier) => {
3583
- const result = await createClient().auth.exchangeOAuthCode(code, codeVerifier);
3584
- persistSessionCookies(writeCookies, result.data, cookieSettings);
3585
- return toSafeAuthResult(result);
3586
- },
3587
- verifyEmail: async (request) => {
3588
- const result = await createClient().auth.verifyEmail(request);
3589
- persistSessionCookies(writeCookies, result.data, cookieSettings);
3590
- return toSafeAuthResult(result);
3591
- },
3592
- signOut: async () => {
3593
- const result = await createClient().auth.signOut();
3594
- clearAuthCookies(writeCookies, cookieSettings);
3595
- return result;
3596
- }
3597
- };
3598
- }
4168
+
4169
+ // src/ssr/update-session.ts
3599
4170
  async function updateSession(options) {
3600
4171
  const accessCookieName = getAccessTokenCookieName(options.names);
3601
4172
  const refreshCookieName = getRefreshTokenCookieName(options.names);
@@ -3607,7 +4178,10 @@ async function updateSession(options) {
3607
4178
  error: null
3608
4179
  };
3609
4180
  }
3610
- const refreshToken = getCookieValue(options.requestCookies, refreshCookieName);
4181
+ const refreshToken = getCookieValue(
4182
+ options.requestCookies,
4183
+ refreshCookieName
4184
+ );
3611
4185
  if (!refreshToken) {
3612
4186
  if (accessToken) {
3613
4187
  clearAuthCookies(options.requestCookies, options);