@blinkdotnew/dev-sdk 0.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,6 +5,33 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
+ // ../core/src/platform.ts
9
+ function detectPlatform() {
10
+ if (typeof Deno !== "undefined") {
11
+ return "deno";
12
+ }
13
+ if (typeof process !== "undefined" && process.versions?.node) {
14
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
15
+ return "react-native";
16
+ }
17
+ return "node";
18
+ }
19
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
20
+ return "react-native";
21
+ }
22
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
23
+ return "web";
24
+ }
25
+ return "node";
26
+ }
27
+ var platform = detectPlatform();
28
+ var isWeb = platform === "web";
29
+ var isReactNative = platform === "react-native";
30
+ var isNode = platform === "node";
31
+ var isDeno = platform === "deno";
32
+ var isBrowser = isWeb || isReactNative;
33
+ var isServer = isNode || isDeno;
34
+
8
35
  // ../core/src/storage-adapter.ts
9
36
  var WebStorageAdapter = class {
10
37
  getItem(key) {
@@ -90,6 +117,9 @@ var NoOpStorageAdapter = class {
90
117
  }
91
118
  };
92
119
  function getDefaultStorageAdapter() {
120
+ if (isDeno) {
121
+ return new NoOpStorageAdapter();
122
+ }
93
123
  if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
94
124
  try {
95
125
  localStorage.setItem("__test__", "test");
@@ -101,28 +131,6 @@ function getDefaultStorageAdapter() {
101
131
  return new NoOpStorageAdapter();
102
132
  }
103
133
 
104
- // ../core/src/platform.ts
105
- function detectPlatform() {
106
- if (typeof process !== "undefined" && process.versions?.node) {
107
- if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
108
- return "react-native";
109
- }
110
- return "node";
111
- }
112
- if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
113
- return "react-native";
114
- }
115
- if (typeof window !== "undefined" && typeof document !== "undefined") {
116
- return "web";
117
- }
118
- return "node";
119
- }
120
- var platform = detectPlatform();
121
- var isWeb = platform === "web";
122
- var isReactNative = platform === "react-native";
123
- var isNode = platform === "node";
124
- var isBrowser = isWeb || isReactNative;
125
-
126
134
  // ../core/src/types.ts
127
135
  var BlinkError = class extends Error {
128
136
  constructor(message, code, status, details) {
@@ -386,17 +394,44 @@ function convertKeysToCamelCase(obj) {
386
394
  return converted;
387
395
  }
388
396
  var HttpClient = class {
389
- authUrl;
390
- coreUrl;
397
+ authUrl = "https://blink.new";
398
+ coreUrl = "https://core.blink.new";
391
399
  projectId;
400
+ publishableKey;
401
+ secretKey;
402
+ // Permanent, non-expiring key (like Stripe's sk_live_...)
392
403
  getToken;
393
404
  getValidToken;
394
405
  constructor(config, getToken, getValidToken) {
395
406
  this.projectId = config.projectId;
407
+ this.publishableKey = config.publishableKey;
408
+ this.secretKey = config.secretKey || config.serviceToken;
396
409
  this.getToken = getToken;
397
410
  this.getValidToken = getValidToken;
398
- this.authUrl = config.auth?.authUrl ?? "https://blink.new";
399
- this.coreUrl = config.auth?.coreUrl ?? "https://core.blink.new";
411
+ }
412
+ shouldAttachPublishableKey(path, method) {
413
+ if (method !== "GET" && method !== "POST") return false;
414
+ if (path.includes("/api/analytics/")) return true;
415
+ if (path.includes("/api/storage/")) return true;
416
+ if (path.includes("/api/db/") && path.includes("/rest/v1/")) return method === "GET";
417
+ return false;
418
+ }
419
+ shouldSkipSecretKey(url) {
420
+ try {
421
+ const parsed = new URL(url);
422
+ return parsed.hostname.endsWith(".functions.blink.new");
423
+ } catch {
424
+ return false;
425
+ }
426
+ }
427
+ getAuthorizationHeader(url, token) {
428
+ if (this.secretKey && !this.shouldSkipSecretKey(url)) {
429
+ return `Bearer ${this.secretKey}`;
430
+ }
431
+ if (token) {
432
+ return `Bearer ${token}`;
433
+ }
434
+ return null;
400
435
  }
401
436
  /**
402
437
  * Make an authenticated request to the Blink API
@@ -404,19 +439,23 @@ var HttpClient = class {
404
439
  async request(path, options = {}) {
405
440
  const url = this.buildUrl(path, options.searchParams);
406
441
  const token = this.getValidToken ? await this.getValidToken() : this.getToken();
442
+ const method = options.method || "GET";
407
443
  const headers = {
408
444
  "Content-Type": "application/json",
409
445
  ...options.headers
410
446
  };
411
- if (token) {
412
- headers.Authorization = `Bearer ${token}`;
447
+ const auth = this.getAuthorizationHeader(url, token);
448
+ if (auth) {
449
+ headers.Authorization = auth;
450
+ } else if (this.publishableKey && !headers["x-blink-publishable-key"] && this.shouldAttachPublishableKey(path, method)) {
451
+ headers["x-blink-publishable-key"] = this.publishableKey;
413
452
  }
414
453
  const requestInit = {
415
- method: options.method || "GET",
454
+ method,
416
455
  headers,
417
456
  signal: options.signal
418
457
  };
419
- if (options.body && options.method !== "GET") {
458
+ if (options.body && method !== "GET") {
420
459
  requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
421
460
  }
422
461
  try {
@@ -570,12 +609,12 @@ var HttpClient = class {
570
609
  throw new BlinkValidationError("Unsupported file type");
571
610
  }
572
611
  formData.append("path", filePath);
573
- if (options.upsert !== void 0) {
574
- formData.append("options", JSON.stringify({ upsert: options.upsert }));
575
- }
576
612
  const headers = {};
577
- if (token) {
578
- headers.Authorization = `Bearer ${token}`;
613
+ const auth = this.getAuthorizationHeader(url, token);
614
+ if (auth) {
615
+ headers.Authorization = auth;
616
+ } else if (this.publishableKey && path.includes("/api/storage/") && !headers["x-blink-publishable-key"]) {
617
+ headers["x-blink-publishable-key"] = this.publishableKey;
579
618
  }
580
619
  try {
581
620
  if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
@@ -676,7 +715,7 @@ var HttpClient = class {
676
715
  });
677
716
  }
678
717
  /**
679
- * Stream AI text generation with Vercel AI SDK data stream format
718
+ * Stream AI text generation - uses Vercel AI SDK's pipeUIMessageStreamToResponse (Data Stream Protocol)
680
719
  */
681
720
  async streamAiText(prompt, options = {}, onChunk) {
682
721
  const url = this.buildUrl(`/api/ai/${this.projectId}/text`);
@@ -684,9 +723,8 @@ var HttpClient = class {
684
723
  const headers = {
685
724
  "Content-Type": "application/json"
686
725
  };
687
- if (token) {
688
- headers.Authorization = `Bearer ${token}`;
689
- }
726
+ const auth = this.getAuthorizationHeader(url, token);
727
+ if (auth) headers.Authorization = auth;
690
728
  const body = {
691
729
  prompt,
692
730
  stream: true,
@@ -706,7 +744,7 @@ var HttpClient = class {
706
744
  if (!response.body) {
707
745
  throw new BlinkNetworkError("No response body for streaming");
708
746
  }
709
- return this.parseDataStream(response.body, onChunk);
747
+ return this.parseDataStreamProtocol(response.body, onChunk);
710
748
  } catch (error) {
711
749
  if (error instanceof BlinkError) {
712
750
  throw error;
@@ -731,7 +769,7 @@ var HttpClient = class {
731
769
  });
732
770
  }
733
771
  /**
734
- * Stream AI object generation with Vercel AI SDK data stream format
772
+ * Stream AI object generation - uses Vercel AI SDK's pipeTextStreamToResponse
735
773
  */
736
774
  async streamAiObject(prompt, options = {}, onPartial) {
737
775
  const url = this.buildUrl(`/api/ai/${this.projectId}/object`);
@@ -739,9 +777,8 @@ var HttpClient = class {
739
777
  const headers = {
740
778
  "Content-Type": "application/json"
741
779
  };
742
- if (token) {
743
- headers.Authorization = `Bearer ${token}`;
744
- }
780
+ const auth = this.getAuthorizationHeader(url, token);
781
+ if (auth) headers.Authorization = auth;
745
782
  const body = {
746
783
  prompt,
747
784
  stream: true,
@@ -761,7 +798,35 @@ var HttpClient = class {
761
798
  if (!response.body) {
762
799
  throw new BlinkNetworkError("No response body for streaming");
763
800
  }
764
- return this.parseDataStream(response.body, void 0, onPartial);
801
+ const reader = response.body.getReader();
802
+ const decoder = new TextDecoder();
803
+ let buffer = "";
804
+ let latestObject = {};
805
+ try {
806
+ while (true) {
807
+ const { done, value } = await reader.read();
808
+ if (done) break;
809
+ const chunk = decoder.decode(value, { stream: true });
810
+ buffer += chunk;
811
+ try {
812
+ const parsed = JSON.parse(buffer);
813
+ latestObject = parsed;
814
+ if (onPartial) {
815
+ onPartial(parsed);
816
+ }
817
+ } catch {
818
+ }
819
+ }
820
+ if (buffer) {
821
+ try {
822
+ latestObject = JSON.parse(buffer);
823
+ } catch {
824
+ }
825
+ }
826
+ return { object: latestObject };
827
+ } finally {
828
+ reader.releaseLock();
829
+ }
765
830
  } catch (error) {
766
831
  if (error instanceof BlinkError) {
767
832
  throw error;
@@ -859,6 +924,38 @@ var HttpClient = class {
859
924
  async dataSearch(projectId, request) {
860
925
  return this.post(`/api/data/${projectId}/search`, request);
861
926
  }
927
+ /**
928
+ * Connector requests
929
+ */
930
+ formatProviderForPath(provider) {
931
+ return provider.replace("_", "-");
932
+ }
933
+ async connectorStatus(provider) {
934
+ return this.request(`/api/connectors/${this.formatProviderForPath(provider)}/${this.projectId}/status`, {
935
+ method: "GET"
936
+ });
937
+ }
938
+ async connectorExecute(provider, request) {
939
+ const path = request.method.startsWith("/") ? request.method : `/${request.method}`;
940
+ const url = `/api/connectors/${this.formatProviderForPath(provider)}/${this.projectId}${path}`;
941
+ const method = (request.http_method || "GET").toUpperCase();
942
+ if (method === "GET") {
943
+ return this.request(url, {
944
+ method: "GET",
945
+ searchParams: request.params
946
+ });
947
+ }
948
+ return this.request(url, {
949
+ method,
950
+ body: request.params || {}
951
+ });
952
+ }
953
+ async connectorSaveApiKey(provider, request) {
954
+ return this.request(`/api/connectors/${this.formatProviderForPath(provider)}/${this.projectId}/api-key`, {
955
+ method: "POST",
956
+ body: request
957
+ });
958
+ }
862
959
  /**
863
960
  * Realtime-specific requests
864
961
  */
@@ -924,93 +1021,94 @@ var HttpClient = class {
924
1021
  }
925
1022
  }
926
1023
  /**
927
- * Parse Vercel AI SDK data stream format
928
- * Handles text chunks (0:"text"), partial objects (2:[...]), and metadata (d:, e:)
1024
+ * Parse Vercel AI SDK v5 Data Stream Protocol (Server-Sent Events)
1025
+ * Supports all event types from the UI Message Stream protocol
929
1026
  */
930
- async parseDataStream(body, onChunk, onPartial) {
1027
+ async parseDataStreamProtocol(body, onChunk) {
931
1028
  const reader = body.getReader();
932
1029
  const decoder = new TextDecoder();
1030
+ const finalResult = {
1031
+ text: "",
1032
+ toolCalls: [],
1033
+ toolResults: [],
1034
+ sources: [],
1035
+ files: [],
1036
+ reasoning: []
1037
+ };
933
1038
  let buffer = "";
934
- let finalResult = {};
935
1039
  try {
936
1040
  while (true) {
937
1041
  const { done, value } = await reader.read();
938
1042
  if (done) break;
939
1043
  buffer += decoder.decode(value, { stream: true });
940
- const lines = buffer.split(/\r?\n/);
1044
+ const lines = buffer.split("\n");
941
1045
  buffer = lines.pop() || "";
942
1046
  for (const line of lines) {
943
1047
  if (!line.trim()) continue;
1048
+ if (line === "[DONE]") {
1049
+ continue;
1050
+ }
1051
+ if (!line.startsWith("data: ")) continue;
944
1052
  try {
945
- if (line.startsWith("f:")) {
946
- const metadata = JSON.parse(line.slice(2));
947
- finalResult.messageId = metadata.messageId;
948
- } else if (line.startsWith("0:")) {
949
- const textChunk = JSON.parse(line.slice(2));
950
- if (onChunk) {
951
- onChunk(textChunk);
952
- }
953
- finalResult.text = (finalResult.text || "") + textChunk;
954
- } else if (line.startsWith("2:")) {
955
- const data = JSON.parse(line.slice(2));
956
- if (Array.isArray(data) && data.length > 0) {
957
- const item = data[0];
958
- if (typeof item === "string") {
959
- finalResult.status = item;
960
- } else if (typeof item === "object") {
961
- if (onPartial) {
962
- onPartial(item);
963
- }
964
- finalResult.object = item;
1053
+ const jsonStr = line.slice(6);
1054
+ const part = JSON.parse(jsonStr);
1055
+ switch (part.type) {
1056
+ case "text-start":
1057
+ break;
1058
+ case "text-delta":
1059
+ if (part.delta) {
1060
+ finalResult.text += part.delta;
1061
+ if (onChunk) onChunk(part.delta);
965
1062
  }
966
- }
967
- } else if (line.startsWith("d:")) {
968
- const metadata = JSON.parse(line.slice(2));
969
- if (metadata.usage) {
970
- finalResult.usage = metadata.usage;
971
- }
972
- if (metadata.finishReason) {
973
- finalResult.finishReason = metadata.finishReason;
974
- }
975
- } else if (line.startsWith("e:")) {
976
- const errorData = JSON.parse(line.slice(2));
977
- finalResult.error = errorData;
978
- }
979
- } catch (error) {
980
- console.warn("Failed to parse stream line:", line, error);
981
- }
982
- }
983
- }
984
- if (buffer.trim()) {
985
- try {
986
- if (buffer.startsWith("0:")) {
987
- const textChunk = JSON.parse(buffer.slice(2));
988
- if (onChunk) {
989
- onChunk(textChunk);
990
- }
991
- finalResult.text = (finalResult.text || "") + textChunk;
992
- } else if (buffer.startsWith("2:")) {
993
- const data = JSON.parse(buffer.slice(2));
994
- if (Array.isArray(data) && data.length > 0) {
995
- const item = data[0];
996
- if (typeof item === "object") {
997
- if (onPartial) {
998
- onPartial(item);
1063
+ if (part.textDelta) {
1064
+ finalResult.text += part.textDelta;
1065
+ if (onChunk) onChunk(part.textDelta);
999
1066
  }
1000
- finalResult.object = item;
1001
- }
1002
- }
1003
- } else if (buffer.startsWith("d:")) {
1004
- const metadata = JSON.parse(buffer.slice(2));
1005
- if (metadata.usage) {
1006
- finalResult.usage = metadata.usage;
1007
- }
1008
- if (metadata.finishReason) {
1009
- finalResult.finishReason = metadata.finishReason;
1067
+ break;
1068
+ case "text-end":
1069
+ break;
1070
+ case "tool-call":
1071
+ finalResult.toolCalls.push({
1072
+ toolCallId: part.toolCallId,
1073
+ toolName: part.toolName,
1074
+ args: part.args
1075
+ });
1076
+ break;
1077
+ case "tool-result":
1078
+ finalResult.toolResults.push({
1079
+ toolCallId: part.toolCallId,
1080
+ toolName: part.toolName,
1081
+ result: part.result
1082
+ });
1083
+ break;
1084
+ case "source-url":
1085
+ finalResult.sources.push({
1086
+ id: part.id,
1087
+ url: part.url,
1088
+ title: part.title
1089
+ });
1090
+ break;
1091
+ case "file":
1092
+ finalResult.files.push(part.file);
1093
+ break;
1094
+ case "reasoning":
1095
+ finalResult.reasoning.push(part.content);
1096
+ break;
1097
+ case "finish":
1098
+ finalResult.finishReason = part.finishReason;
1099
+ finalResult.usage = part.usage;
1100
+ if (part.response) finalResult.response = part.response;
1101
+ break;
1102
+ case "error":
1103
+ finalResult.error = part.error;
1104
+ throw new Error(part.error);
1105
+ case "data":
1106
+ if (!finalResult.customData) finalResult.customData = [];
1107
+ finalResult.customData.push(part.value);
1108
+ break;
1010
1109
  }
1110
+ } catch (e) {
1011
1111
  }
1012
- } catch (error) {
1013
- console.warn("Failed to parse final buffer:", buffer, error);
1014
1112
  }
1015
1113
  }
1016
1114
  return finalResult;
@@ -2937,6 +3035,11 @@ var BlinkAuth = class {
2937
3035
  };
2938
3036
 
2939
3037
  // src/database.ts
3038
+ function assertServerOnly(methodName) {
3039
+ if (typeof window !== "undefined") {
3040
+ throw new Error(`${methodName} is server-only. Use Blink CRUD methods (blink.db.<table>.*) instead.`);
3041
+ }
3042
+ }
2940
3043
  function camelToSnake3(str) {
2941
3044
  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
2942
3045
  }
@@ -3155,6 +3258,7 @@ var BlinkTable = class {
3155
3258
  * Raw SQL query on this table (for advanced use cases)
3156
3259
  */
3157
3260
  async sql(query, params) {
3261
+ assertServerOnly("blink.db.<table>.sql");
3158
3262
  const response = await this.httpClient.dbSql(query, params);
3159
3263
  return response.data;
3160
3264
  }
@@ -3203,6 +3307,7 @@ var BlinkDatabase = class {
3203
3307
  * Execute raw SQL query
3204
3308
  */
3205
3309
  async sql(query, params) {
3310
+ assertServerOnly("blink.db.sql");
3206
3311
  const response = await this.httpClient.dbSql(query, params);
3207
3312
  return response.data;
3208
3313
  }
@@ -3210,6 +3315,7 @@ var BlinkDatabase = class {
3210
3315
  * Execute batch SQL operations
3211
3316
  */
3212
3317
  async batch(statements, mode = "write") {
3318
+ assertServerOnly("blink.db.batch");
3213
3319
  const response = await this.httpClient.dbBatch(statements, mode);
3214
3320
  return response.data;
3215
3321
  }
@@ -3272,7 +3378,6 @@ var BlinkStorageImpl = class {
3272
3378
  correctedPath,
3273
3379
  // Use corrected path with proper extension
3274
3380
  {
3275
- upsert: options.upsert,
3276
3381
  onProgress: options.onProgress,
3277
3382
  contentType: detectedContentType
3278
3383
  // Pass detected content type
@@ -3292,7 +3397,7 @@ var BlinkStorageImpl = class {
3292
3397
  if (error instanceof Error && "status" in error) {
3293
3398
  const status = error.status;
3294
3399
  if (status === 409) {
3295
- throw new BlinkStorageError("File already exists. Set upsert: true to overwrite.", 409);
3400
+ throw new BlinkStorageError("File already exists.", 409);
3296
3401
  }
3297
3402
  if (status === 400) {
3298
3403
  throw new BlinkStorageError("Invalid request parameters", 400);
@@ -3337,7 +3442,6 @@ var BlinkStorageImpl = class {
3337
3442
  detectedContentType
3338
3443
  };
3339
3444
  } catch (error) {
3340
- console.warn("File type detection failed, using original path:", error);
3341
3445
  return {
3342
3446
  correctedPath: originalPath,
3343
3447
  detectedContentType: "application/octet-stream"
@@ -3727,13 +3831,7 @@ var BlinkAIImpl = class {
3727
3831
  options.prompt || "",
3728
3832
  requestBody
3729
3833
  );
3730
- if (response.data?.result) {
3731
- return response.data.result;
3732
- } else if (response.data?.text) {
3733
- return response.data;
3734
- } else {
3735
- throw new BlinkAIError("Invalid response format: missing text");
3736
- }
3834
+ return response.data;
3737
3835
  } catch (error) {
3738
3836
  if (error instanceof BlinkAIError) {
3739
3837
  throw error;
@@ -3802,9 +3900,14 @@ var BlinkAIImpl = class {
3802
3900
  );
3803
3901
  return {
3804
3902
  text: result.text || "",
3805
- finishReason: "stop",
3903
+ finishReason: result.finishReason || "stop",
3806
3904
  usage: result.usage,
3807
- ...result
3905
+ toolCalls: result.toolCalls,
3906
+ toolResults: result.toolResults,
3907
+ sources: result.sources,
3908
+ files: result.files,
3909
+ reasoningDetails: result.reasoning,
3910
+ response: result.response
3808
3911
  };
3809
3912
  } catch (error) {
3810
3913
  if (error instanceof BlinkAIError) {
@@ -3883,13 +3986,7 @@ var BlinkAIImpl = class {
3883
3986
  signal: options.signal
3884
3987
  }
3885
3988
  );
3886
- if (response.data?.result) {
3887
- return response.data.result;
3888
- } else if (response.data?.object) {
3889
- return response.data;
3890
- } else {
3891
- throw new BlinkAIError("Invalid response format: missing object");
3892
- }
3989
+ return response.data;
3893
3990
  } catch (error) {
3894
3991
  if (error instanceof BlinkAIError) {
3895
3992
  throw error;
@@ -3951,8 +4048,7 @@ var BlinkAIImpl = class {
3951
4048
  return {
3952
4049
  object: result.object || {},
3953
4050
  finishReason: "stop",
3954
- usage: result.usage,
3955
- ...result
4051
+ usage: result.usage
3956
4052
  };
3957
4053
  } catch (error) {
3958
4054
  if (error instanceof BlinkAIError) {
@@ -5271,7 +5367,6 @@ var BlinkAnalyticsImpl = class {
5271
5367
  } catch (error) {
5272
5368
  this.queue = [...events, ...this.queue];
5273
5369
  this.persistQueue();
5274
- console.error("Failed to send analytics events:", error);
5275
5370
  }
5276
5371
  if (this.queue.length > 0) {
5277
5372
  this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
@@ -5462,6 +5557,64 @@ var BlinkAnalyticsImpl = class {
5462
5557
  }
5463
5558
  };
5464
5559
 
5560
+ // src/connectors.ts
5561
+ var BlinkConnectorsImpl = class {
5562
+ constructor(httpClient) {
5563
+ this.httpClient = httpClient;
5564
+ }
5565
+ async status(provider, options) {
5566
+ const response = await this.httpClient.connectorStatus(provider);
5567
+ return response.data;
5568
+ }
5569
+ async execute(provider, request) {
5570
+ const response = await this.httpClient.connectorExecute(provider, request);
5571
+ return response.data;
5572
+ }
5573
+ async saveApiKey(provider, request) {
5574
+ const response = await this.httpClient.connectorSaveApiKey(provider, request);
5575
+ return response.data;
5576
+ }
5577
+ };
5578
+
5579
+ // src/functions.ts
5580
+ var BlinkFunctionsImpl = class {
5581
+ httpClient;
5582
+ projectId;
5583
+ constructor(httpClient, projectId, _getToken) {
5584
+ this.httpClient = httpClient;
5585
+ this.projectId = projectId;
5586
+ }
5587
+ /**
5588
+ * Get the project suffix from the full project ID.
5589
+ * Project IDs are formatted as: prj_xxxxx
5590
+ * The suffix is the last 8 characters used in function URLs.
5591
+ */
5592
+ getProjectSuffix() {
5593
+ return this.projectId.slice(-8);
5594
+ }
5595
+ /**
5596
+ * Build the full function URL
5597
+ */
5598
+ buildFunctionUrl(functionSlug, searchParams) {
5599
+ const suffix = this.getProjectSuffix();
5600
+ const baseUrl = `https://${suffix}--${functionSlug}.functions.blink.new`;
5601
+ if (!searchParams || Object.keys(searchParams).length === 0) {
5602
+ return baseUrl;
5603
+ }
5604
+ const url = new URL(baseUrl);
5605
+ Object.entries(searchParams).forEach(([key, value]) => {
5606
+ url.searchParams.set(key, value);
5607
+ });
5608
+ return url.toString();
5609
+ }
5610
+ async invoke(functionSlug, options = {}) {
5611
+ const { method = "POST", body, headers = {}, searchParams } = options;
5612
+ const url = this.buildFunctionUrl(functionSlug, searchParams);
5613
+ const res = await this.httpClient.request(url, { method, body, headers });
5614
+ return { data: res.data, status: res.status, headers: res.headers };
5615
+ }
5616
+ };
5617
+
5465
5618
  // src/client.ts
5466
5619
  var BlinkClientImpl = class {
5467
5620
  auth;
@@ -5472,8 +5625,13 @@ var BlinkClientImpl = class {
5472
5625
  realtime;
5473
5626
  notifications;
5474
5627
  analytics;
5628
+ connectors;
5629
+ functions;
5475
5630
  httpClient;
5476
5631
  constructor(config) {
5632
+ if ((config.secretKey || config.serviceToken) && isBrowser) {
5633
+ throw new Error("secretKey/serviceToken is server-only. Do not provide it in browser/React Native clients.");
5634
+ }
5477
5635
  this.auth = new BlinkAuth(config);
5478
5636
  this.httpClient = new HttpClient(
5479
5637
  config,
@@ -5487,6 +5645,12 @@ var BlinkClientImpl = class {
5487
5645
  this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
5488
5646
  this.notifications = new BlinkNotificationsImpl(this.httpClient);
5489
5647
  this.analytics = new BlinkAnalyticsImpl(this.httpClient, config.projectId);
5648
+ this.connectors = new BlinkConnectorsImpl(this.httpClient);
5649
+ this.functions = new BlinkFunctionsImpl(
5650
+ this.httpClient,
5651
+ config.projectId,
5652
+ () => this.auth.getValidToken()
5653
+ );
5490
5654
  this.auth.onAuthStateChanged((state) => {
5491
5655
  if (state.isAuthenticated && state.user) {
5492
5656
  this.analytics.setUserId(state.user.id);
@@ -5505,6 +5669,6 @@ function createClient(config) {
5505
5669
  return new BlinkClientImpl(config);
5506
5670
  }
5507
5671
 
5508
- export { AsyncStorageAdapter, BlinkAIImpl, BlinkAnalyticsImpl, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, NoOpStorageAdapter, WebStorageAdapter, createClient, getDefaultStorageAdapter, isBrowser, isNode, isReactNative, isWeb, platform };
5672
+ export { AsyncStorageAdapter, BlinkAIImpl, BlinkAnalyticsImpl, BlinkConnectorsImpl, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, NoOpStorageAdapter, WebStorageAdapter, createClient, getDefaultStorageAdapter, isBrowser, isDeno, isNode, isReactNative, isServer, isWeb, platform };
5509
5673
  //# sourceMappingURL=index.mjs.map
5510
5674
  //# sourceMappingURL=index.mjs.map