@debros/network-ts-sdk 0.3.4 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40,6 +40,13 @@ var HttpClient = class {
40
40
  this.maxRetries = config.maxRetries ?? 3;
41
41
  this.retryDelayMs = config.retryDelayMs ?? 1e3;
42
42
  this.fetch = config.fetch ?? createFetchWithTLSConfig();
43
+ this.onNetworkError = config.onNetworkError;
44
+ }
45
+ /**
46
+ * Set the network error callback
47
+ */
48
+ setOnNetworkError(callback) {
49
+ this.onNetworkError = callback;
43
50
  }
44
51
  setApiKey(apiKey) {
45
52
  this.apiKey = apiKey;
@@ -91,6 +98,12 @@ var HttpClient = class {
91
98
  getApiKey() {
92
99
  return this.apiKey;
93
100
  }
101
+ /**
102
+ * Get the base URL
103
+ */
104
+ getBaseURL() {
105
+ return this.baseURL;
106
+ }
94
107
  async request(method, path, options = {}) {
95
108
  const startTime = performance.now();
96
109
  const url = new URL(this.baseURL + path);
@@ -189,6 +202,21 @@ var HttpClient = class {
189
202
  }
190
203
  }
191
204
  }
205
+ if (this.onNetworkError) {
206
+ const sdkError = error instanceof SDKError ? error : new SDKError(
207
+ error instanceof Error ? error.message : String(error),
208
+ 0,
209
+ // httpStatus 0 indicates network-level failure
210
+ "NETWORK_ERROR"
211
+ );
212
+ this.onNetworkError(sdkError, {
213
+ method,
214
+ path,
215
+ isRetry: false,
216
+ attempt: this.maxRetries
217
+ // All retries exhausted
218
+ });
219
+ }
192
220
  throw error;
193
221
  } finally {
194
222
  clearTimeout(timeoutId);
@@ -212,7 +240,13 @@ var HttpClient = class {
212
240
  }
213
241
  return response.text();
214
242
  } catch (error) {
215
- if (error instanceof SDKError && attempt < this.maxRetries && [408, 429, 500, 502, 503, 504].includes(error.httpStatus)) {
243
+ const isRetryableError = error instanceof SDKError && [408, 429, 500, 502, 503, 504].includes(error.httpStatus);
244
+ if (isRetryableError && attempt < this.maxRetries) {
245
+ if (typeof console !== "undefined") {
246
+ console.warn(
247
+ `[HttpClient] Retrying request (attempt ${attempt + 1}/${this.maxRetries})`
248
+ );
249
+ }
216
250
  await new Promise(
217
251
  (resolve) => setTimeout(resolve, this.retryDelayMs * (attempt + 1))
218
252
  );
@@ -279,6 +313,19 @@ var HttpClient = class {
279
313
  error
280
314
  );
281
315
  }
316
+ if (this.onNetworkError) {
317
+ const sdkError = error instanceof SDKError ? error : new SDKError(
318
+ error instanceof Error ? error.message : String(error),
319
+ 0,
320
+ "NETWORK_ERROR"
321
+ );
322
+ this.onNetworkError(sdkError, {
323
+ method: "POST",
324
+ path,
325
+ isRetry: false,
326
+ attempt: this.maxRetries
327
+ });
328
+ }
282
329
  throw error;
283
330
  } finally {
284
331
  clearTimeout(timeoutId);
@@ -303,16 +350,26 @@ var HttpClient = class {
303
350
  const response = await this.fetch(url.toString(), fetchOptions);
304
351
  if (!response.ok) {
305
352
  clearTimeout(timeoutId);
306
- const error = await response.json().catch(() => ({
353
+ const errorBody = await response.json().catch(() => ({
307
354
  error: response.statusText
308
355
  }));
309
- throw SDKError.fromResponse(response.status, error);
356
+ throw SDKError.fromResponse(response.status, errorBody);
310
357
  }
311
358
  return response;
312
359
  } catch (error) {
313
360
  clearTimeout(timeoutId);
314
- if (error instanceof SDKError) {
315
- throw error;
361
+ if (this.onNetworkError) {
362
+ const sdkError = error instanceof SDKError ? error : new SDKError(
363
+ error instanceof Error ? error.message : String(error),
364
+ 0,
365
+ "NETWORK_ERROR"
366
+ );
367
+ this.onNetworkError(sdkError, {
368
+ method: "GET",
369
+ path,
370
+ isRetry: false,
371
+ attempt: 0
372
+ });
316
373
  }
317
374
  throw error;
318
375
  }
@@ -798,10 +855,23 @@ var WSClient = class {
798
855
  this.closeHandlers = /* @__PURE__ */ new Set();
799
856
  this.openHandlers = /* @__PURE__ */ new Set();
800
857
  this.isClosed = false;
801
- this.url = config.wsURL;
858
+ this.wsURL = config.wsURL;
802
859
  this.timeout = config.timeout ?? 3e4;
803
860
  this.authToken = config.authToken;
804
861
  this.WebSocketClass = config.WebSocket ?? WebSocket;
862
+ this.onNetworkError = config.onNetworkError;
863
+ }
864
+ /**
865
+ * Set the network error callback
866
+ */
867
+ setOnNetworkError(callback) {
868
+ this.onNetworkError = callback;
869
+ }
870
+ /**
871
+ * Get the current WebSocket URL
872
+ */
873
+ get url() {
874
+ return this.wsURL;
805
875
  }
806
876
  /**
807
877
  * Connect to WebSocket server
@@ -814,13 +884,20 @@ var WSClient = class {
814
884
  this.isClosed = false;
815
885
  const timeout = setTimeout(() => {
816
886
  this.ws?.close();
817
- reject(
818
- new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT")
819
- );
887
+ const error = new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT");
888
+ if (this.onNetworkError) {
889
+ this.onNetworkError(error, {
890
+ method: "WS",
891
+ path: this.wsURL,
892
+ isRetry: false,
893
+ attempt: 0
894
+ });
895
+ }
896
+ reject(error);
820
897
  }, this.timeout);
821
898
  this.ws.addEventListener("open", () => {
822
899
  clearTimeout(timeout);
823
- console.log("[WSClient] Connected to", this.url);
900
+ console.log("[WSClient] Connected to", this.wsURL);
824
901
  this.openHandlers.forEach((handler) => handler());
825
902
  resolve();
826
903
  });
@@ -832,7 +909,16 @@ var WSClient = class {
832
909
  console.error("[WSClient] WebSocket error:", event);
833
910
  clearTimeout(timeout);
834
911
  const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
912
+ if (this.onNetworkError) {
913
+ this.onNetworkError(error, {
914
+ method: "WS",
915
+ path: this.wsURL,
916
+ isRetry: false,
917
+ attempt: 0
918
+ });
919
+ }
835
920
  this.errorHandlers.forEach((handler) => handler(error));
921
+ reject(error);
836
922
  });
837
923
  this.ws.addEventListener("close", () => {
838
924
  clearTimeout(timeout);
@@ -848,7 +934,7 @@ var WSClient = class {
848
934
  * Build WebSocket URL with auth token
849
935
  */
850
936
  buildWSUrl() {
851
- let url = this.url;
937
+ let url = this.wsURL;
852
938
  if (this.authToken) {
853
939
  const separator = url.includes("?") ? "&" : "?";
854
940
  const paramName = this.authToken.startsWith("ak_") ? "api_key" : "token";
@@ -1009,14 +1095,32 @@ var PubSubClient = class {
1009
1095
  );
1010
1096
  return response.topics || [];
1011
1097
  }
1098
+ /**
1099
+ * Get current presence for a topic without subscribing
1100
+ */
1101
+ async getPresence(topic) {
1102
+ const response = await this.httpClient.get(
1103
+ `/v1/pubsub/presence?topic=${encodeURIComponent(topic)}`
1104
+ );
1105
+ return response;
1106
+ }
1012
1107
  /**
1013
1108
  * Subscribe to a topic via WebSocket
1014
1109
  * Creates one WebSocket connection per topic
1015
1110
  */
1016
- async subscribe(topic, handlers = {}) {
1111
+ async subscribe(topic, options = {}) {
1017
1112
  const wsUrl = new URL(this.wsConfig.wsURL || "ws://127.0.0.1:6001");
1018
1113
  wsUrl.pathname = "/v1/pubsub/ws";
1019
1114
  wsUrl.searchParams.set("topic", topic);
1115
+ let presence;
1116
+ if (options.presence?.enabled) {
1117
+ presence = options.presence;
1118
+ wsUrl.searchParams.set("presence", "true");
1119
+ wsUrl.searchParams.set("member_id", presence.memberId);
1120
+ if (presence.meta) {
1121
+ wsUrl.searchParams.set("member_meta", JSON.stringify(presence.meta));
1122
+ }
1123
+ }
1020
1124
  const authToken = this.httpClient.getApiKey() ?? this.httpClient.getToken();
1021
1125
  const wsClient = new WSClient({
1022
1126
  ...this.wsConfig,
@@ -1024,21 +1128,26 @@ var PubSubClient = class {
1024
1128
  authToken
1025
1129
  });
1026
1130
  await wsClient.connect();
1027
- const subscription = new Subscription(wsClient, topic);
1028
- if (handlers.onMessage) {
1029
- subscription.onMessage(handlers.onMessage);
1131
+ const subscription = new Subscription(
1132
+ wsClient,
1133
+ topic,
1134
+ presence,
1135
+ () => this.getPresence(topic)
1136
+ );
1137
+ if (options.onMessage) {
1138
+ subscription.onMessage(options.onMessage);
1030
1139
  }
1031
- if (handlers.onError) {
1032
- subscription.onError(handlers.onError);
1140
+ if (options.onError) {
1141
+ subscription.onError(options.onError);
1033
1142
  }
1034
- if (handlers.onClose) {
1035
- subscription.onClose(handlers.onClose);
1143
+ if (options.onClose) {
1144
+ subscription.onClose(options.onClose);
1036
1145
  }
1037
1146
  return subscription;
1038
1147
  }
1039
1148
  };
1040
1149
  var Subscription = class {
1041
- constructor(wsClient, topic) {
1150
+ constructor(wsClient, topic, presenceOptions, getPresenceFn) {
1042
1151
  this.messageHandlers = /* @__PURE__ */ new Set();
1043
1152
  this.errorHandlers = /* @__PURE__ */ new Set();
1044
1153
  this.closeHandlers = /* @__PURE__ */ new Set();
@@ -1048,12 +1157,31 @@ var Subscription = class {
1048
1157
  this.wsCloseHandler = null;
1049
1158
  this.wsClient = wsClient;
1050
1159
  this.topic = topic;
1160
+ this.presenceOptions = presenceOptions;
1161
+ this.getPresenceFn = getPresenceFn;
1051
1162
  this.wsMessageHandler = (data) => {
1052
1163
  try {
1053
1164
  const envelope = JSON.parse(data);
1054
1165
  if (!envelope || typeof envelope !== "object") {
1055
1166
  throw new Error("Invalid envelope: not an object");
1056
1167
  }
1168
+ if (envelope.type === "presence.join" || envelope.type === "presence.leave") {
1169
+ if (!envelope.member_id) {
1170
+ console.warn("[Subscription] Presence event missing member_id");
1171
+ return;
1172
+ }
1173
+ const presenceMember = {
1174
+ memberId: envelope.member_id,
1175
+ joinedAt: envelope.timestamp,
1176
+ meta: envelope.meta
1177
+ };
1178
+ if (envelope.type === "presence.join" && this.presenceOptions?.onJoin) {
1179
+ this.presenceOptions.onJoin(presenceMember);
1180
+ } else if (envelope.type === "presence.leave" && this.presenceOptions?.onLeave) {
1181
+ this.presenceOptions.onLeave(presenceMember);
1182
+ }
1183
+ return;
1184
+ }
1057
1185
  if (!envelope.data || typeof envelope.data !== "string") {
1058
1186
  throw new Error("Invalid envelope: missing or invalid data field");
1059
1187
  }
@@ -1090,6 +1218,22 @@ var Subscription = class {
1090
1218
  };
1091
1219
  this.wsClient.onClose(this.wsCloseHandler);
1092
1220
  }
1221
+ /**
1222
+ * Get current presence (requires presence.enabled on subscribe)
1223
+ */
1224
+ async getPresence() {
1225
+ if (!this.presenceOptions?.enabled) {
1226
+ throw new Error("Presence is not enabled for this subscription");
1227
+ }
1228
+ const response = await this.getPresenceFn();
1229
+ return response.members;
1230
+ }
1231
+ /**
1232
+ * Check if presence is enabled for this subscription
1233
+ */
1234
+ hasPresence() {
1235
+ return !!this.presenceOptions?.enabled;
1236
+ }
1093
1237
  /**
1094
1238
  * Register message handler
1095
1239
  */
@@ -1511,6 +1655,38 @@ var StorageClient = class {
1511
1655
  }
1512
1656
  };
1513
1657
 
1658
+ // src/functions/client.ts
1659
+ var FunctionsClient = class {
1660
+ constructor(httpClient, config) {
1661
+ this.httpClient = httpClient;
1662
+ this.gatewayURL = config?.gatewayURL;
1663
+ this.namespace = config?.namespace ?? "default";
1664
+ }
1665
+ /**
1666
+ * Invoke a serverless function by name
1667
+ *
1668
+ * @param functionName - Name of the function to invoke
1669
+ * @param input - Input payload for the function
1670
+ * @returns The function response
1671
+ */
1672
+ async invoke(functionName, input) {
1673
+ const url = this.gatewayURL ? `${this.gatewayURL}/v1/invoke/${this.namespace}/${functionName}` : `/v1/invoke/${this.namespace}/${functionName}`;
1674
+ try {
1675
+ const response = await this.httpClient.post(url, input);
1676
+ return response;
1677
+ } catch (error) {
1678
+ if (error instanceof SDKError) {
1679
+ throw error;
1680
+ }
1681
+ throw new SDKError(
1682
+ `Function ${functionName} failed`,
1683
+ 500,
1684
+ error instanceof Error ? error.message : String(error)
1685
+ );
1686
+ }
1687
+ }
1688
+ };
1689
+
1514
1690
  // src/index.ts
1515
1691
  function createClient(config) {
1516
1692
  const httpClient = new HttpClient({
@@ -1518,7 +1694,8 @@ function createClient(config) {
1518
1694
  timeout: config.timeout,
1519
1695
  maxRetries: config.maxRetries,
1520
1696
  retryDelayMs: config.retryDelayMs,
1521
- fetch: config.fetch
1697
+ fetch: config.fetch,
1698
+ onNetworkError: config.onNetworkError
1522
1699
  });
1523
1700
  const auth = new AuthClient({
1524
1701
  httpClient,
@@ -1526,28 +1703,32 @@ function createClient(config) {
1526
1703
  apiKey: config.apiKey,
1527
1704
  jwt: config.jwt
1528
1705
  });
1529
- const wsURL = config.wsConfig?.wsURL ?? config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
1706
+ const wsURL = config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
1530
1707
  const db = new DBClient(httpClient);
1531
1708
  const pubsub = new PubSubClient(httpClient, {
1532
1709
  ...config.wsConfig,
1533
- wsURL
1710
+ wsURL,
1711
+ onNetworkError: config.onNetworkError
1534
1712
  });
1535
1713
  const network = new NetworkClient(httpClient);
1536
1714
  const cache = new CacheClient(httpClient);
1537
1715
  const storage = new StorageClient(httpClient);
1716
+ const functions = new FunctionsClient(httpClient, config.functionsConfig);
1538
1717
  return {
1539
1718
  auth,
1540
1719
  db,
1541
1720
  pubsub,
1542
1721
  network,
1543
1722
  cache,
1544
- storage
1723
+ storage,
1724
+ functions
1545
1725
  };
1546
1726
  }
1547
1727
  export {
1548
1728
  AuthClient,
1549
1729
  CacheClient,
1550
1730
  DBClient,
1731
+ FunctionsClient,
1551
1732
  HttpClient,
1552
1733
  LocalStorageAdapter,
1553
1734
  MemoryStorage,