@resultdev/sdk 0.3.0 → 0.3.2

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.
@@ -1,4 +1,4 @@
1
- import { a as ResultConfig, o as ResultError } from './types-DSVtGaIA.js';
1
+ import { a as ResultConfig, o as ResultError } from './types-BTdjI7Yj.js';
2
2
 
3
3
  declare const DEFAULT_ACCESS_TOKEN_COOKIE = "result_access_token";
4
4
  declare const DEFAULT_REFRESH_TOKEN_COOKIE = "result_refresh_token";
@@ -1,4 +1,4 @@
1
- import { a as ResultConfig, o as ResultError } from './types-DSVtGaIA.cjs';
1
+ import { a as ResultConfig, o as ResultError } from './types-BTdjI7Yj.cjs';
2
2
 
3
3
  declare const DEFAULT_ACCESS_TOKEN_COOKIE = "result_access_token";
4
4
  declare const DEFAULT_REFRESH_TOKEN_COOKIE = "result_refresh_token";
@@ -1,2 +1,2 @@
1
- export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-C1Z6aMvn.cjs';
2
- import '../types-DSVtGaIA.cjs';
1
+ export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-CGn44ocX.cjs';
2
+ import '../types-BTdjI7Yj.cjs';
@@ -1,2 +1,2 @@
1
- export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-BJl9JRg3.js';
2
- import '../types-DSVtGaIA.js';
1
+ export { b as AuthCookieNames, c as AuthCookieOptions, A as AuthCookieSettings, d as CookieOptions, e as CookieReader, C as CookieStore, a as CookieWriter, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, s as setAuthCookies, u as updateSession } from '../middleware-8UNqItQG.js';
2
+ import '../types-BTdjI7Yj.js';
package/dist/ssr.cjs CHANGED
@@ -690,7 +690,15 @@ var HttpClient = class {
690
690
  */
691
691
  constructor(config, tokenManager, logger) {
692
692
  this.config = config;
693
- this.baseUrl = config.baseUrl || "http://localhost:7130";
693
+ if (!config.baseUrl) {
694
+ throw new ResultError(
695
+ "createClient needs a baseUrl, but it was undefined at runtime.",
696
+ 0,
697
+ "MISSING_BASE_URL",
698
+ "Pass the NEXT_PUBLIC_BACKEND_URL value from .env.local. In browser code, write process.env.NEXT_PUBLIC_BACKEND_URL as a literal expression (never through an imported process module) so the bundler inlines it, and set it as a deployments build variable before deploying."
699
+ );
700
+ }
701
+ this.baseUrl = config.baseUrl;
694
702
  this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
695
703
  this.anonKey = config.anonKey;
696
704
  this.defaultHeaders = {
@@ -781,6 +789,7 @@ var HttpClient = class {
781
789
  }
782
790
  let controller;
783
791
  let timer;
792
+ let removeCallerAbortListener;
784
793
  if (this.timeout > 0 || callerSignal) {
785
794
  controller = new AbortController();
786
795
  if (this.timeout > 0) {
@@ -794,16 +803,11 @@ var HttpClient = class {
794
803
  callerSignal.addEventListener("abort", onCallerAbort, {
795
804
  once: true
796
805
  });
797
- controller.signal.addEventListener(
798
- "abort",
799
- () => {
800
- callerSignal.removeEventListener("abort", onCallerAbort);
801
- },
802
- { once: true }
803
- );
806
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
804
807
  }
805
808
  }
806
809
  }
810
+ let detachOnExit = true;
807
811
  try {
808
812
  const response = await this.fetch(url, {
809
813
  method,
@@ -824,14 +828,9 @@ var HttpClient = class {
824
828
  );
825
829
  continue;
826
830
  }
827
- if (timer !== void 0) {
828
- clearTimeout(timer);
829
- }
830
- return response;
831
+ detachOnExit = false;
832
+ return { response, releaseCallerSignal: removeCallerAbortListener };
831
833
  } catch (err) {
832
- if (timer !== void 0) {
833
- clearTimeout(timer);
834
- }
835
834
  if (err?.name === "AbortError") {
836
835
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
837
836
  throw new ResultError(
@@ -851,6 +850,13 @@ var HttpClient = class {
851
850
  0,
852
851
  "NETWORK_ERROR"
853
852
  );
853
+ } finally {
854
+ if (timer !== void 0) {
855
+ clearTimeout(timer);
856
+ }
857
+ if (detachOnExit) {
858
+ removeCallerAbortListener?.();
859
+ }
854
860
  }
855
861
  }
856
862
  throw lastError || new ResultError(
@@ -913,7 +919,7 @@ var HttpClient = class {
913
919
  });
914
920
  }
915
921
  this.logger.logRequest(method, url, requestHeaders, processedBody);
916
- const response = await this.fetchWithRetry({
922
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
917
923
  method,
918
924
  url,
919
925
  headers: requestHeaders,
@@ -936,6 +942,8 @@ var HttpClient = class {
936
942
  );
937
943
  }
938
944
  throw err;
945
+ } finally {
946
+ releaseCallerSignal?.();
939
947
  }
940
948
  this.logger.logResponse(
941
949
  method,
@@ -1037,7 +1045,7 @@ var HttpClient = class {
1037
1045
  const callerSignal = initSignal ?? request?.signal;
1038
1046
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
1039
1047
  this.logger.logRequest(method, url, requestHeaders, body);
1040
- const response = await this.fetchWithRetry({
1048
+ const { response } = await this.fetchWithRetry({
1041
1049
  method,
1042
1050
  url,
1043
1051
  headers: requestHeaders,
@@ -1138,6 +1146,10 @@ var HttpClient = class {
1138
1146
  }
1139
1147
  return headers;
1140
1148
  }
1149
+ /** Returns the publishable project key used to identify SDK usage reports. */
1150
+ getAnonKey() {
1151
+ return this.anonKey;
1152
+ }
1141
1153
  async refreshAccessToken() {
1142
1154
  if (this.isRefreshing) {
1143
1155
  return this.refreshPromise;
@@ -1207,6 +1219,47 @@ var HttpClient = class {
1207
1219
  }
1208
1220
  };
1209
1221
 
1222
+ // src/modules/ai-usage.ts
1223
+ var USAGE_ENDPOINT = "https://beta.result.dev/api/usage/ai";
1224
+ async function reportAiUsage(http, requestId, model, kind, tokens) {
1225
+ const anonKey = http.getAnonKey();
1226
+ if (!anonKey || !Number.isSafeInteger(tokens) || tokens <= 0) {
1227
+ return;
1228
+ }
1229
+ const controller = new AbortController();
1230
+ const timeout = setTimeout(() => controller.abort(), 2e3);
1231
+ try {
1232
+ const request = {
1233
+ method: "POST",
1234
+ headers: { "content-type": "application/json" },
1235
+ body: JSON.stringify({
1236
+ requestId,
1237
+ backendUrl: http.baseUrl,
1238
+ anonKey,
1239
+ model,
1240
+ kind,
1241
+ tokens
1242
+ }),
1243
+ keepalive: true,
1244
+ signal: controller.signal
1245
+ };
1246
+ for (let attempt = 0; attempt < 2; attempt++) {
1247
+ try {
1248
+ const response = await http.fetch(USAGE_ENDPOINT, request);
1249
+ if (response.status < 500) return;
1250
+ } catch {
1251
+ if (controller.signal.aborted) return;
1252
+ }
1253
+ }
1254
+ } catch {
1255
+ } finally {
1256
+ clearTimeout(timeout);
1257
+ }
1258
+ }
1259
+ function aiUsageRequestId() {
1260
+ return globalThis.crypto?.randomUUID?.() ?? null;
1261
+ }
1262
+
1210
1263
  // src/modules/ai.ts
1211
1264
  var SUPPORTED_AUDIO_FORMATS = [
1212
1265
  "wav",
@@ -1294,6 +1347,7 @@ var ChatCompletions = class {
1294
1347
  }
1295
1348
  http;
1296
1349
  async create(params) {
1350
+ const usageRequestId = aiUsageRequestId();
1297
1351
  const contentSummary = summarizeContent(params.messages ?? []);
1298
1352
  assertSupportedContent(params.model, contentSummary);
1299
1353
  const backendParams = {
@@ -1338,7 +1392,7 @@ var ChatCompletions = class {
1338
1392
  contentSummary
1339
1393
  );
1340
1394
  }
1341
- return this.parseSSEStream(response2, params.model);
1395
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1342
1396
  }
1343
1397
  let response;
1344
1398
  try {
@@ -1347,6 +1401,16 @@ var ChatCompletions = class {
1347
1401
  throw enrichUpstreamError(error, contentSummary);
1348
1402
  }
1349
1403
  const content = response.text || "";
1404
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1405
+ if (usageRequestId) {
1406
+ await reportAiUsage(
1407
+ this.http,
1408
+ usageRequestId,
1409
+ params.model,
1410
+ "chat",
1411
+ totalTokens
1412
+ );
1413
+ }
1350
1414
  return {
1351
1415
  id: `chatcmpl-${Date.now()}`,
1352
1416
  object: "chat.completion",
@@ -1373,14 +1437,14 @@ var ChatCompletions = class {
1373
1437
  usage: {
1374
1438
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1375
1439
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1376
- total_tokens: response.metadata?.usage?.totalTokens || 0
1440
+ total_tokens: totalTokens
1377
1441
  }
1378
1442
  };
1379
1443
  }
1380
1444
  /**
1381
1445
  * Parse SSE stream into async iterable of OpenAI-like chunks
1382
1446
  */
1383
- async *parseSSEStream(response, model) {
1447
+ async *parseSSEStream(response, model, usageRequestId) {
1384
1448
  const reader = response.body?.getReader();
1385
1449
  if (!reader) {
1386
1450
  throw new ResultError(
@@ -1458,6 +1522,15 @@ var ChatCompletions = class {
1458
1522
  };
1459
1523
  }
1460
1524
  if (data.tokenUsage) {
1525
+ if (usageRequestId) {
1526
+ await reportAiUsage(
1527
+ this.http,
1528
+ usageRequestId,
1529
+ model,
1530
+ "chat",
1531
+ data.tokenUsage.totalTokens || 0
1532
+ );
1533
+ }
1461
1534
  yield {
1462
1535
  id: `chatcmpl-${Date.now()}`,
1463
1536
  object: "chat.completion.chunk",
@@ -1529,17 +1602,28 @@ var Embeddings = class {
1529
1602
  * ```
1530
1603
  */
1531
1604
  async create(params) {
1605
+ const usageRequestId = aiUsageRequestId();
1532
1606
  const response = await this.http.post(
1533
1607
  "/api/ai/embeddings",
1534
1608
  params
1535
1609
  );
1610
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1611
+ if (usageRequestId) {
1612
+ await reportAiUsage(
1613
+ this.http,
1614
+ usageRequestId,
1615
+ params.model,
1616
+ "embeddings",
1617
+ totalTokens
1618
+ );
1619
+ }
1536
1620
  return {
1537
1621
  object: response.object,
1538
1622
  data: response.data,
1539
1623
  model: response.metadata?.model,
1540
1624
  usage: response.metadata?.usage ? {
1541
1625
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1542
- total_tokens: response.metadata.usage.totalTokens || 0
1626
+ total_tokens: totalTokens
1543
1627
  } : {
1544
1628
  prompt_tokens: 0,
1545
1629
  total_tokens: 0
@@ -1577,6 +1661,7 @@ var Images = class {
1577
1661
  * ```
1578
1662
  */
1579
1663
  async generate(params) {
1664
+ const usageRequestId = aiUsageRequestId();
1580
1665
  const response = await this.http.post(
1581
1666
  "/api/ai/image/generation",
1582
1667
  params
@@ -1590,6 +1675,16 @@ var Images = class {
1590
1675
  } else if (response.text) {
1591
1676
  data = [{ content: response.text }];
1592
1677
  }
1678
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1679
+ if (usageRequestId) {
1680
+ await reportAiUsage(
1681
+ this.http,
1682
+ usageRequestId,
1683
+ params.model,
1684
+ "image",
1685
+ totalTokens
1686
+ );
1687
+ }
1593
1688
  return {
1594
1689
  created: Math.floor(Date.now() / 1e3),
1595
1690
  data,
package/dist/ssr.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client-CJv_awPt.cjs';
2
- import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-DSVtGaIA.cjs';
3
- import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-C1Z6aMvn.cjs';
4
- export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-C1Z6aMvn.cjs';
1
+ import { R as ResultClient } from './client-N6MW9IE1.cjs';
2
+ import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BTdjI7Yj.cjs';
3
+ import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-CGn44ocX.cjs';
4
+ export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-CGn44ocX.cjs';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  interface CreateAuthActionsOptions extends Omit<ResultConfig, "accessToken" | "edgeFunctionToken" | "isServerMode" | "auth">, AuthCookieSettings {
package/dist/ssr.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { R as ResultClient } from './client-DrsZ1qIm.js';
2
- import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-DSVtGaIA.js';
3
- import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-BJl9JRg3.js';
4
- export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-BJl9JRg3.js';
1
+ import { R as ResultClient } from './client-D5IGLPW4.js';
2
+ import { o as ResultError, a as ResultConfig, G as AuthRefreshResponse } from './types-BTdjI7Yj.js';
3
+ import { A as AuthCookieSettings, C as CookieStore, a as CookieWriter } from './middleware-8UNqItQG.js';
4
+ export { b as AuthCookieNames, c as AuthCookieOptions, d as CookieOptions, e as CookieReader, D as DEFAULT_ACCESS_TOKEN_COOKIE, f as DEFAULT_REFRESH_TOKEN_COOKIE, U as UpdateSessionOptions, g as UpdateSessionResult, h as accessTokenCookieOptions, i as clearAuthCookies, j as getAccessTokenCookieName, k as getRefreshTokenCookieName, r as refreshTokenCookieOptions, s as setAuthCookies, u as updateSession } from './middleware-8UNqItQG.js';
5
5
  import '@supabase/postgrest-js';
6
6
 
7
7
  interface CreateAuthActionsOptions extends Omit<ResultConfig, "accessToken" | "edgeFunctionToken" | "isServerMode" | "auth">, AuthCookieSettings {
package/dist/ssr.js CHANGED
@@ -641,7 +641,15 @@ var HttpClient = class {
641
641
  */
642
642
  constructor(config, tokenManager, logger) {
643
643
  this.config = config;
644
- this.baseUrl = config.baseUrl || "http://localhost:7130";
644
+ if (!config.baseUrl) {
645
+ throw new ResultError(
646
+ "createClient needs a baseUrl, but it was undefined at runtime.",
647
+ 0,
648
+ "MISSING_BASE_URL",
649
+ "Pass the NEXT_PUBLIC_BACKEND_URL value from .env.local. In browser code, write process.env.NEXT_PUBLIC_BACKEND_URL as a literal expression (never through an imported process module) so the bundler inlines it, and set it as a deployments build variable before deploying."
650
+ );
651
+ }
652
+ this.baseUrl = config.baseUrl;
645
653
  this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
646
654
  this.anonKey = config.anonKey;
647
655
  this.defaultHeaders = {
@@ -732,6 +740,7 @@ var HttpClient = class {
732
740
  }
733
741
  let controller;
734
742
  let timer;
743
+ let removeCallerAbortListener;
735
744
  if (this.timeout > 0 || callerSignal) {
736
745
  controller = new AbortController();
737
746
  if (this.timeout > 0) {
@@ -745,16 +754,11 @@ var HttpClient = class {
745
754
  callerSignal.addEventListener("abort", onCallerAbort, {
746
755
  once: true
747
756
  });
748
- controller.signal.addEventListener(
749
- "abort",
750
- () => {
751
- callerSignal.removeEventListener("abort", onCallerAbort);
752
- },
753
- { once: true }
754
- );
757
+ removeCallerAbortListener = () => callerSignal.removeEventListener("abort", onCallerAbort);
755
758
  }
756
759
  }
757
760
  }
761
+ let detachOnExit = true;
758
762
  try {
759
763
  const response = await this.fetch(url, {
760
764
  method,
@@ -775,14 +779,9 @@ var HttpClient = class {
775
779
  );
776
780
  continue;
777
781
  }
778
- if (timer !== void 0) {
779
- clearTimeout(timer);
780
- }
781
- return response;
782
+ detachOnExit = false;
783
+ return { response, releaseCallerSignal: removeCallerAbortListener };
782
784
  } catch (err) {
783
- if (timer !== void 0) {
784
- clearTimeout(timer);
785
- }
786
785
  if (err?.name === "AbortError") {
787
786
  if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
788
787
  throw new ResultError(
@@ -802,6 +801,13 @@ var HttpClient = class {
802
801
  0,
803
802
  "NETWORK_ERROR"
804
803
  );
804
+ } finally {
805
+ if (timer !== void 0) {
806
+ clearTimeout(timer);
807
+ }
808
+ if (detachOnExit) {
809
+ removeCallerAbortListener?.();
810
+ }
805
811
  }
806
812
  }
807
813
  throw lastError || new ResultError(
@@ -864,7 +870,7 @@ var HttpClient = class {
864
870
  });
865
871
  }
866
872
  this.logger.logRequest(method, url, requestHeaders, processedBody);
867
- const response = await this.fetchWithRetry({
873
+ const { response, releaseCallerSignal } = await this.fetchWithRetry({
868
874
  method,
869
875
  url,
870
876
  headers: requestHeaders,
@@ -887,6 +893,8 @@ var HttpClient = class {
887
893
  );
888
894
  }
889
895
  throw err;
896
+ } finally {
897
+ releaseCallerSignal?.();
890
898
  }
891
899
  this.logger.logResponse(
892
900
  method,
@@ -988,7 +996,7 @@ var HttpClient = class {
988
996
  const callerSignal = initSignal ?? request?.signal;
989
997
  const maxAttempts = IDEMPOTENT_METHODS.has(method.toUpperCase()) ? this.retryCount : 0;
990
998
  this.logger.logRequest(method, url, requestHeaders, body);
991
- const response = await this.fetchWithRetry({
999
+ const { response } = await this.fetchWithRetry({
992
1000
  method,
993
1001
  url,
994
1002
  headers: requestHeaders,
@@ -1089,6 +1097,10 @@ var HttpClient = class {
1089
1097
  }
1090
1098
  return headers;
1091
1099
  }
1100
+ /** Returns the publishable project key used to identify SDK usage reports. */
1101
+ getAnonKey() {
1102
+ return this.anonKey;
1103
+ }
1092
1104
  async refreshAccessToken() {
1093
1105
  if (this.isRefreshing) {
1094
1106
  return this.refreshPromise;
@@ -1158,6 +1170,47 @@ var HttpClient = class {
1158
1170
  }
1159
1171
  };
1160
1172
 
1173
+ // src/modules/ai-usage.ts
1174
+ var USAGE_ENDPOINT = "https://beta.result.dev/api/usage/ai";
1175
+ async function reportAiUsage(http, requestId, model, kind, tokens) {
1176
+ const anonKey = http.getAnonKey();
1177
+ if (!anonKey || !Number.isSafeInteger(tokens) || tokens <= 0) {
1178
+ return;
1179
+ }
1180
+ const controller = new AbortController();
1181
+ const timeout = setTimeout(() => controller.abort(), 2e3);
1182
+ try {
1183
+ const request = {
1184
+ method: "POST",
1185
+ headers: { "content-type": "application/json" },
1186
+ body: JSON.stringify({
1187
+ requestId,
1188
+ backendUrl: http.baseUrl,
1189
+ anonKey,
1190
+ model,
1191
+ kind,
1192
+ tokens
1193
+ }),
1194
+ keepalive: true,
1195
+ signal: controller.signal
1196
+ };
1197
+ for (let attempt = 0; attempt < 2; attempt++) {
1198
+ try {
1199
+ const response = await http.fetch(USAGE_ENDPOINT, request);
1200
+ if (response.status < 500) return;
1201
+ } catch {
1202
+ if (controller.signal.aborted) return;
1203
+ }
1204
+ }
1205
+ } catch {
1206
+ } finally {
1207
+ clearTimeout(timeout);
1208
+ }
1209
+ }
1210
+ function aiUsageRequestId() {
1211
+ return globalThis.crypto?.randomUUID?.() ?? null;
1212
+ }
1213
+
1161
1214
  // src/modules/ai.ts
1162
1215
  var SUPPORTED_AUDIO_FORMATS = [
1163
1216
  "wav",
@@ -1245,6 +1298,7 @@ var ChatCompletions = class {
1245
1298
  }
1246
1299
  http;
1247
1300
  async create(params) {
1301
+ const usageRequestId = aiUsageRequestId();
1248
1302
  const contentSummary = summarizeContent(params.messages ?? []);
1249
1303
  assertSupportedContent(params.model, contentSummary);
1250
1304
  const backendParams = {
@@ -1289,7 +1343,7 @@ var ChatCompletions = class {
1289
1343
  contentSummary
1290
1344
  );
1291
1345
  }
1292
- return this.parseSSEStream(response2, params.model);
1346
+ return this.parseSSEStream(response2, params.model, usageRequestId);
1293
1347
  }
1294
1348
  let response;
1295
1349
  try {
@@ -1298,6 +1352,16 @@ var ChatCompletions = class {
1298
1352
  throw enrichUpstreamError(error, contentSummary);
1299
1353
  }
1300
1354
  const content = response.text || "";
1355
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1356
+ if (usageRequestId) {
1357
+ await reportAiUsage(
1358
+ this.http,
1359
+ usageRequestId,
1360
+ params.model,
1361
+ "chat",
1362
+ totalTokens
1363
+ );
1364
+ }
1301
1365
  return {
1302
1366
  id: `chatcmpl-${Date.now()}`,
1303
1367
  object: "chat.completion",
@@ -1324,14 +1388,14 @@ var ChatCompletions = class {
1324
1388
  usage: {
1325
1389
  prompt_tokens: response.metadata?.usage?.promptTokens || 0,
1326
1390
  completion_tokens: response.metadata?.usage?.completionTokens || 0,
1327
- total_tokens: response.metadata?.usage?.totalTokens || 0
1391
+ total_tokens: totalTokens
1328
1392
  }
1329
1393
  };
1330
1394
  }
1331
1395
  /**
1332
1396
  * Parse SSE stream into async iterable of OpenAI-like chunks
1333
1397
  */
1334
- async *parseSSEStream(response, model) {
1398
+ async *parseSSEStream(response, model, usageRequestId) {
1335
1399
  const reader = response.body?.getReader();
1336
1400
  if (!reader) {
1337
1401
  throw new ResultError(
@@ -1409,6 +1473,15 @@ var ChatCompletions = class {
1409
1473
  };
1410
1474
  }
1411
1475
  if (data.tokenUsage) {
1476
+ if (usageRequestId) {
1477
+ await reportAiUsage(
1478
+ this.http,
1479
+ usageRequestId,
1480
+ model,
1481
+ "chat",
1482
+ data.tokenUsage.totalTokens || 0
1483
+ );
1484
+ }
1412
1485
  yield {
1413
1486
  id: `chatcmpl-${Date.now()}`,
1414
1487
  object: "chat.completion.chunk",
@@ -1480,17 +1553,28 @@ var Embeddings = class {
1480
1553
  * ```
1481
1554
  */
1482
1555
  async create(params) {
1556
+ const usageRequestId = aiUsageRequestId();
1483
1557
  const response = await this.http.post(
1484
1558
  "/api/ai/embeddings",
1485
1559
  params
1486
1560
  );
1561
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1562
+ if (usageRequestId) {
1563
+ await reportAiUsage(
1564
+ this.http,
1565
+ usageRequestId,
1566
+ params.model,
1567
+ "embeddings",
1568
+ totalTokens
1569
+ );
1570
+ }
1487
1571
  return {
1488
1572
  object: response.object,
1489
1573
  data: response.data,
1490
1574
  model: response.metadata?.model,
1491
1575
  usage: response.metadata?.usage ? {
1492
1576
  prompt_tokens: response.metadata.usage.promptTokens || 0,
1493
- total_tokens: response.metadata.usage.totalTokens || 0
1577
+ total_tokens: totalTokens
1494
1578
  } : {
1495
1579
  prompt_tokens: 0,
1496
1580
  total_tokens: 0
@@ -1528,6 +1612,7 @@ var Images = class {
1528
1612
  * ```
1529
1613
  */
1530
1614
  async generate(params) {
1615
+ const usageRequestId = aiUsageRequestId();
1531
1616
  const response = await this.http.post(
1532
1617
  "/api/ai/image/generation",
1533
1618
  params
@@ -1541,6 +1626,16 @@ var Images = class {
1541
1626
  } else if (response.text) {
1542
1627
  data = [{ content: response.text }];
1543
1628
  }
1629
+ const totalTokens = response.metadata?.usage?.totalTokens || 0;
1630
+ if (usageRequestId) {
1631
+ await reportAiUsage(
1632
+ this.http,
1633
+ usageRequestId,
1634
+ params.model,
1635
+ "image",
1636
+ totalTokens
1637
+ );
1638
+ }
1544
1639
  return {
1545
1640
  created: Math.floor(Date.now() / 1e3),
1546
1641
  data,
@@ -526,8 +526,10 @@ interface SocketMessage {
526
526
  type ResultErrorCode = ErrorCode | (string & {});
527
527
  interface ResultConfig {
528
528
  /**
529
- * The base URL of the Result backend API
530
- * @default "http://localhost:7130"
529
+ * The base URL of the Result backend API (the NEXT_PUBLIC_BACKEND_URL
530
+ * value from .env.local). Required at runtime: constructing a client
531
+ * without it throws MISSING_BASE_URL. (Kept optional in the type because
532
+ * the SSR entrypoints intentionally fill it from process.env.)
531
533
  */
532
534
  baseUrl?: string;
533
535
  /**
@@ -526,8 +526,10 @@ interface SocketMessage {
526
526
  type ResultErrorCode = ErrorCode | (string & {});
527
527
  interface ResultConfig {
528
528
  /**
529
- * The base URL of the Result backend API
530
- * @default "http://localhost:7130"
529
+ * The base URL of the Result backend API (the NEXT_PUBLIC_BACKEND_URL
530
+ * value from .env.local). Required at runtime: constructing a client
531
+ * without it throws MISSING_BASE_URL. (Kept optional in the type because
532
+ * the SSR entrypoints intentionally fill it from process.env.)
531
533
  */
532
534
  baseUrl?: string;
533
535
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resultdev/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Result Backend SDK - authentication, database, storage, functions, realtime, AI, email, web analytics and customer support for apps built on Result.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://docs.result.dev",
@@ -59,8 +59,19 @@
59
59
  }
60
60
  }
61
61
  },
62
+ "scripts": {
63
+ "build": "tsup",
64
+ "test": "vitest run",
65
+ "check-types": "tsc --noEmit -p tsconfig.json"
66
+ },
62
67
  "dependencies": {
63
68
  "@supabase/postgrest-js": "^1.21.3",
64
69
  "socket.io-client": "^4.8.1"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^24.0.0",
73
+ "tsup": "^8.5.0",
74
+ "typescript": "^5.9.2",
75
+ "vitest": "^3.2.4"
65
76
  }
66
- }
77
+ }