@debros/network-ts-sdk 0.3.2 → 0.4.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.
package/README.md CHANGED
@@ -215,6 +215,55 @@ const topics = await client.pubsub.topics();
215
215
  console.log("Active topics:", topics);
216
216
  ```
217
217
 
218
+ ### Presence Support
219
+
220
+ The SDK supports real-time presence tracking, allowing you to see who is currently subscribed to a topic.
221
+
222
+ #### Subscribe with Presence
223
+
224
+ Enable presence by providing `presence` options in `subscribe`:
225
+
226
+ ```typescript
227
+ const subscription = await client.pubsub.subscribe("room.123", {
228
+ onMessage: (msg) => console.log("Message:", msg.data),
229
+ presence: {
230
+ enabled: true,
231
+ memberId: "user-alice",
232
+ meta: { displayName: "Alice", avatar: "URL" },
233
+ onJoin: (member) => {
234
+ console.log(`${member.memberId} joined at ${new Date(member.joinedAt)}`);
235
+ console.log("Meta:", member.meta);
236
+ },
237
+ onLeave: (member) => {
238
+ console.log(`${member.memberId} left`);
239
+ },
240
+ },
241
+ });
242
+ ```
243
+
244
+ #### Get Presence for a Topic
245
+
246
+ Query current members without subscribing:
247
+
248
+ ```typescript
249
+ const presence = await client.pubsub.getPresence("room.123");
250
+ console.log(`Total members: ${presence.count}`);
251
+ presence.members.forEach((member) => {
252
+ console.log(`- ${member.memberId} (joined: ${new Date(member.joinedAt)})`);
253
+ });
254
+ ```
255
+
256
+ #### Subscription Helpers
257
+
258
+ Get presence information from an active subscription:
259
+
260
+ ```typescript
261
+ if (subscription.hasPresence()) {
262
+ const members = await subscription.getPresence();
263
+ console.log("Current members:", members);
264
+ }
265
+ ```
266
+
218
267
  ### Authentication
219
268
 
220
269
  #### Switch API Key
package/dist/index.d.ts CHANGED
@@ -21,6 +21,10 @@ declare class HttpClient {
21
21
  private getAuthHeaders;
22
22
  private getAuthToken;
23
23
  getApiKey(): string | undefined;
24
+ /**
25
+ * Get the base URL
26
+ */
27
+ getBaseURL(): string;
24
28
  request<T = any>(method: "GET" | "POST" | "PUT" | "DELETE", path: string, options?: {
25
29
  body?: any;
26
30
  headers?: Record<string, string>;
@@ -290,10 +294,11 @@ type WSCloseHandler = () => void;
290
294
  type WSOpenHandler = () => void;
291
295
  /**
292
296
  * Simple WebSocket client with minimal abstractions
293
- * No complex reconnection, no heartbeats - keep it simple
297
+ * No complex reconnection, no failover - keep it simple
298
+ * Gateway failover is handled at the application layer
294
299
  */
295
300
  declare class WSClient {
296
- private url;
301
+ private wsURL;
297
302
  private timeout;
298
303
  private authToken?;
299
304
  private WebSocketClass;
@@ -304,6 +309,10 @@ declare class WSClient {
304
309
  private openHandlers;
305
310
  private isClosed;
306
311
  constructor(config: WSClientConfig);
312
+ /**
313
+ * Get the current WebSocket URL
314
+ */
315
+ get url(): string;
307
316
  /**
308
317
  * Connect to WebSocket server
309
318
  */
@@ -358,17 +367,41 @@ declare class WSClient {
358
367
  setAuthToken(token?: string): void;
359
368
  }
360
369
 
361
- interface Message {
370
+ interface PubSubMessage {
362
371
  data: string;
363
372
  topic: string;
364
373
  timestamp: number;
365
374
  }
366
- type MessageHandler = (message: Message) => void;
375
+ interface PresenceMember {
376
+ memberId: string;
377
+ joinedAt: number;
378
+ meta?: Record<string, unknown>;
379
+ }
380
+ interface PresenceResponse {
381
+ topic: string;
382
+ members: PresenceMember[];
383
+ count: number;
384
+ }
385
+ interface PresenceOptions {
386
+ enabled: boolean;
387
+ memberId: string;
388
+ meta?: Record<string, unknown>;
389
+ onJoin?: (member: PresenceMember) => void;
390
+ onLeave?: (member: PresenceMember) => void;
391
+ }
392
+ interface SubscribeOptions {
393
+ onMessage?: MessageHandler;
394
+ onError?: ErrorHandler;
395
+ onClose?: CloseHandler;
396
+ presence?: PresenceOptions;
397
+ }
398
+ type MessageHandler = (message: PubSubMessage) => void;
367
399
  type ErrorHandler = (error: Error) => void;
368
400
  type CloseHandler = () => void;
401
+
369
402
  /**
370
403
  * Simple PubSub client - one WebSocket connection per topic
371
- * No connection pooling, no reference counting - keep it simple
404
+ * Gateway failover is handled at the application layer
372
405
  */
373
406
  declare class PubSubClient {
374
407
  private httpClient;
@@ -382,15 +415,15 @@ declare class PubSubClient {
382
415
  * List active topics in the current namespace
383
416
  */
384
417
  topics(): Promise<string[]>;
418
+ /**
419
+ * Get current presence for a topic without subscribing
420
+ */
421
+ getPresence(topic: string): Promise<PresenceResponse>;
385
422
  /**
386
423
  * Subscribe to a topic via WebSocket
387
424
  * Creates one WebSocket connection per topic
388
425
  */
389
- subscribe(topic: string, handlers?: {
390
- onMessage?: MessageHandler;
391
- onError?: ErrorHandler;
392
- onClose?: CloseHandler;
393
- }): Promise<Subscription>;
426
+ subscribe(topic: string, options?: SubscribeOptions): Promise<Subscription>;
394
427
  }
395
428
  /**
396
429
  * Subscription represents an active WebSocket subscription to a topic
@@ -398,6 +431,7 @@ declare class PubSubClient {
398
431
  declare class Subscription {
399
432
  private wsClient;
400
433
  private topic;
434
+ private presenceOptions?;
401
435
  private messageHandlers;
402
436
  private errorHandlers;
403
437
  private closeHandlers;
@@ -405,7 +439,16 @@ declare class Subscription {
405
439
  private wsMessageHandler;
406
440
  private wsErrorHandler;
407
441
  private wsCloseHandler;
408
- constructor(wsClient: WSClient, topic: string);
442
+ private getPresenceFn;
443
+ constructor(wsClient: WSClient, topic: string, presenceOptions: PresenceOptions | undefined, getPresenceFn: () => Promise<PresenceResponse>);
444
+ /**
445
+ * Get current presence (requires presence.enabled on subscribe)
446
+ */
447
+ getPresence(): Promise<PresenceMember[]>;
448
+ /**
449
+ * Check if presence is enabled for this subscription
450
+ */
451
+ hasPresence(): boolean;
409
452
  /**
410
453
  * Register message handler
411
454
  */
@@ -695,6 +738,37 @@ declare class StorageClient {
695
738
  unpin(cid: string): Promise<void>;
696
739
  }
697
740
 
741
+ /**
742
+ * Functions Client
743
+ * Client for calling serverless functions on the Orama Network
744
+ */
745
+
746
+ interface FunctionsClientConfig {
747
+ /**
748
+ * Base URL for the functions gateway
749
+ * Defaults to using the same baseURL as the HTTP client
750
+ */
751
+ gatewayURL?: string;
752
+ /**
753
+ * Namespace for the functions
754
+ */
755
+ namespace: string;
756
+ }
757
+ declare class FunctionsClient {
758
+ private httpClient;
759
+ private gatewayURL?;
760
+ private namespace;
761
+ constructor(httpClient: HttpClient, config?: FunctionsClientConfig);
762
+ /**
763
+ * Invoke a serverless function by name
764
+ *
765
+ * @param functionName - Name of the function to invoke
766
+ * @param input - Input payload for the function
767
+ * @returns The function response
768
+ */
769
+ invoke<TInput = any, TOutput = any>(functionName: string, input: TInput): Promise<TOutput>;
770
+ }
771
+
698
772
  declare class SDKError extends Error {
699
773
  readonly httpStatus: number;
700
774
  readonly code: string;
@@ -710,11 +784,32 @@ declare class SDKError extends Error {
710
784
  };
711
785
  }
712
786
 
787
+ /**
788
+ * Serverless Functions Types
789
+ * Type definitions for calling serverless functions on the Orama Network
790
+ */
791
+ /**
792
+ * Generic response from a serverless function
793
+ */
794
+ interface FunctionResponse<T = unknown> {
795
+ success: boolean;
796
+ error?: string;
797
+ data?: T;
798
+ }
799
+ /**
800
+ * Standard success/error response used by many functions
801
+ */
802
+ interface SuccessResponse {
803
+ success: boolean;
804
+ error?: string;
805
+ }
806
+
713
807
  interface ClientConfig extends Omit<HttpClientConfig, "fetch"> {
714
808
  apiKey?: string;
715
809
  jwt?: string;
716
810
  storage?: StorageAdapter;
717
- wsConfig?: Partial<WSClientConfig>;
811
+ wsConfig?: Partial<Omit<WSClientConfig, "wsURL">>;
812
+ functionsConfig?: FunctionsClientConfig;
718
813
  fetch?: typeof fetch;
719
814
  }
720
815
  interface Client {
@@ -724,7 +819,8 @@ interface Client {
724
819
  network: NetworkClient;
725
820
  cache: CacheClient;
726
821
  storage: StorageClient;
822
+ functions: FunctionsClient;
727
823
  }
728
824
  declare function createClient(config: ClientConfig): Client;
729
825
 
730
- export { AuthClient, type AuthConfig, CacheClient, type CacheDeleteRequest, type CacheDeleteResponse, type CacheGetRequest, type CacheGetResponse, type CacheHealthResponse, type CacheMultiGetRequest, type CacheMultiGetResponse, type CachePutRequest, type CachePutResponse, type CacheScanRequest, type CacheScanResponse, type Client, type ClientConfig, type CloseHandler, type ColumnDefinition, DBClient, type Entity, type ErrorHandler, type FindOptions, HttpClient, LocalStorageAdapter, MemoryStorage, type Message, type MessageHandler, NetworkClient, type NetworkStatus, type PeerInfo, type ProxyRequest, type ProxyResponse, PubSubClient, QueryBuilder, type QueryResponse, Repository, SDKError, type SelectOptions, type StorageAdapter, StorageClient, type StoragePinRequest, type StoragePinResponse, type StorageStatus, type StorageUploadResponse, Subscription, type TransactionOp, type TransactionRequest, WSClient, type WhoAmI, createClient, extractPrimaryKey, extractTableName };
826
+ export { AuthClient, type AuthConfig, CacheClient, type CacheDeleteRequest, type CacheDeleteResponse, type CacheGetRequest, type CacheGetResponse, type CacheHealthResponse, type CacheMultiGetRequest, type CacheMultiGetResponse, type CachePutRequest, type CachePutResponse, type CacheScanRequest, type CacheScanResponse, type Client, type ClientConfig, type CloseHandler, type ColumnDefinition, DBClient, type Entity, type ErrorHandler, type FindOptions, type FunctionResponse, FunctionsClient, type FunctionsClientConfig, HttpClient, LocalStorageAdapter, MemoryStorage, type MessageHandler, NetworkClient, type NetworkStatus, type PeerInfo, type PresenceMember, type PresenceOptions, type PresenceResponse, type ProxyRequest, type ProxyResponse, PubSubClient, type PubSubMessage, QueryBuilder, type QueryResponse, Repository, SDKError, type SelectOptions, type StorageAdapter, StorageClient, type StoragePinRequest, type StoragePinResponse, type StorageStatus, type StorageUploadResponse, type SubscribeOptions, Subscription, type SuccessResponse, type TransactionOp, type TransactionRequest, WSClient, type WhoAmI, createClient, extractPrimaryKey, extractTableName };
package/dist/index.js CHANGED
@@ -24,13 +24,22 @@ var SDKError = class _SDKError extends Error {
24
24
  };
25
25
 
26
26
  // src/core/http.ts
27
+ function createFetchWithTLSConfig() {
28
+ if (typeof process !== "undefined" && process.versions?.node) {
29
+ const isDevelopmentOrStaging = process.env.NODE_ENV !== "production" || process.env.DEBROS_ALLOW_STAGING_CERTS === "true" || process.env.DEBROS_USE_HTTPS === "true";
30
+ if (isDevelopmentOrStaging) {
31
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
32
+ }
33
+ }
34
+ return globalThis.fetch;
35
+ }
27
36
  var HttpClient = class {
28
37
  constructor(config) {
29
38
  this.baseURL = config.baseURL.replace(/\/$/, "");
30
39
  this.timeout = config.timeout ?? 6e4;
31
40
  this.maxRetries = config.maxRetries ?? 3;
32
41
  this.retryDelayMs = config.retryDelayMs ?? 1e3;
33
- this.fetch = config.fetch ?? globalThis.fetch;
42
+ this.fetch = config.fetch ?? createFetchWithTLSConfig();
34
43
  }
35
44
  setApiKey(apiKey) {
36
45
  this.apiKey = apiKey;
@@ -82,6 +91,12 @@ var HttpClient = class {
82
91
  getApiKey() {
83
92
  return this.apiKey;
84
93
  }
94
+ /**
95
+ * Get the base URL
96
+ */
97
+ getBaseURL() {
98
+ return this.baseURL;
99
+ }
85
100
  async request(method, path, options = {}) {
86
101
  const startTime = performance.now();
87
102
  const url = new URL(this.baseURL + path);
@@ -203,7 +218,13 @@ var HttpClient = class {
203
218
  }
204
219
  return response.text();
205
220
  } catch (error) {
206
- if (error instanceof SDKError && attempt < this.maxRetries && [408, 429, 500, 502, 503, 504].includes(error.httpStatus)) {
221
+ const isRetryableError = error instanceof SDKError && [408, 429, 500, 502, 503, 504].includes(error.httpStatus);
222
+ if (isRetryableError && attempt < this.maxRetries) {
223
+ if (typeof console !== "undefined") {
224
+ console.warn(
225
+ `[HttpClient] Retrying request (attempt ${attempt + 1}/${this.maxRetries})`
226
+ );
227
+ }
207
228
  await new Promise(
208
229
  (resolve) => setTimeout(resolve, this.retryDelayMs * (attempt + 1))
209
230
  );
@@ -789,11 +810,17 @@ var WSClient = class {
789
810
  this.closeHandlers = /* @__PURE__ */ new Set();
790
811
  this.openHandlers = /* @__PURE__ */ new Set();
791
812
  this.isClosed = false;
792
- this.url = config.wsURL;
813
+ this.wsURL = config.wsURL;
793
814
  this.timeout = config.timeout ?? 3e4;
794
815
  this.authToken = config.authToken;
795
816
  this.WebSocketClass = config.WebSocket ?? WebSocket;
796
817
  }
818
+ /**
819
+ * Get the current WebSocket URL
820
+ */
821
+ get url() {
822
+ return this.wsURL;
823
+ }
797
824
  /**
798
825
  * Connect to WebSocket server
799
826
  */
@@ -811,7 +838,7 @@ var WSClient = class {
811
838
  }, this.timeout);
812
839
  this.ws.addEventListener("open", () => {
813
840
  clearTimeout(timeout);
814
- console.log("[WSClient] Connected to", this.url);
841
+ console.log("[WSClient] Connected to", this.wsURL);
815
842
  this.openHandlers.forEach((handler) => handler());
816
843
  resolve();
817
844
  });
@@ -824,6 +851,7 @@ var WSClient = class {
824
851
  clearTimeout(timeout);
825
852
  const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
826
853
  this.errorHandlers.forEach((handler) => handler(error));
854
+ reject(error);
827
855
  });
828
856
  this.ws.addEventListener("close", () => {
829
857
  clearTimeout(timeout);
@@ -839,7 +867,7 @@ var WSClient = class {
839
867
  * Build WebSocket URL with auth token
840
868
  */
841
869
  buildWSUrl() {
842
- let url = this.url;
870
+ let url = this.wsURL;
843
871
  if (this.authToken) {
844
872
  const separator = url.includes("?") ? "&" : "?";
845
873
  const paramName = this.authToken.startsWith("ak_") ? "api_key" : "token";
@@ -1000,14 +1028,32 @@ var PubSubClient = class {
1000
1028
  );
1001
1029
  return response.topics || [];
1002
1030
  }
1031
+ /**
1032
+ * Get current presence for a topic without subscribing
1033
+ */
1034
+ async getPresence(topic) {
1035
+ const response = await this.httpClient.get(
1036
+ `/v1/pubsub/presence?topic=${encodeURIComponent(topic)}`
1037
+ );
1038
+ return response;
1039
+ }
1003
1040
  /**
1004
1041
  * Subscribe to a topic via WebSocket
1005
1042
  * Creates one WebSocket connection per topic
1006
1043
  */
1007
- async subscribe(topic, handlers = {}) {
1044
+ async subscribe(topic, options = {}) {
1008
1045
  const wsUrl = new URL(this.wsConfig.wsURL || "ws://127.0.0.1:6001");
1009
1046
  wsUrl.pathname = "/v1/pubsub/ws";
1010
1047
  wsUrl.searchParams.set("topic", topic);
1048
+ let presence;
1049
+ if (options.presence?.enabled) {
1050
+ presence = options.presence;
1051
+ wsUrl.searchParams.set("presence", "true");
1052
+ wsUrl.searchParams.set("member_id", presence.memberId);
1053
+ if (presence.meta) {
1054
+ wsUrl.searchParams.set("member_meta", JSON.stringify(presence.meta));
1055
+ }
1056
+ }
1011
1057
  const authToken = this.httpClient.getApiKey() ?? this.httpClient.getToken();
1012
1058
  const wsClient = new WSClient({
1013
1059
  ...this.wsConfig,
@@ -1015,21 +1061,26 @@ var PubSubClient = class {
1015
1061
  authToken
1016
1062
  });
1017
1063
  await wsClient.connect();
1018
- const subscription = new Subscription(wsClient, topic);
1019
- if (handlers.onMessage) {
1020
- subscription.onMessage(handlers.onMessage);
1064
+ const subscription = new Subscription(
1065
+ wsClient,
1066
+ topic,
1067
+ presence,
1068
+ () => this.getPresence(topic)
1069
+ );
1070
+ if (options.onMessage) {
1071
+ subscription.onMessage(options.onMessage);
1021
1072
  }
1022
- if (handlers.onError) {
1023
- subscription.onError(handlers.onError);
1073
+ if (options.onError) {
1074
+ subscription.onError(options.onError);
1024
1075
  }
1025
- if (handlers.onClose) {
1026
- subscription.onClose(handlers.onClose);
1076
+ if (options.onClose) {
1077
+ subscription.onClose(options.onClose);
1027
1078
  }
1028
1079
  return subscription;
1029
1080
  }
1030
1081
  };
1031
1082
  var Subscription = class {
1032
- constructor(wsClient, topic) {
1083
+ constructor(wsClient, topic, presenceOptions, getPresenceFn) {
1033
1084
  this.messageHandlers = /* @__PURE__ */ new Set();
1034
1085
  this.errorHandlers = /* @__PURE__ */ new Set();
1035
1086
  this.closeHandlers = /* @__PURE__ */ new Set();
@@ -1039,12 +1090,31 @@ var Subscription = class {
1039
1090
  this.wsCloseHandler = null;
1040
1091
  this.wsClient = wsClient;
1041
1092
  this.topic = topic;
1093
+ this.presenceOptions = presenceOptions;
1094
+ this.getPresenceFn = getPresenceFn;
1042
1095
  this.wsMessageHandler = (data) => {
1043
1096
  try {
1044
1097
  const envelope = JSON.parse(data);
1045
1098
  if (!envelope || typeof envelope !== "object") {
1046
1099
  throw new Error("Invalid envelope: not an object");
1047
1100
  }
1101
+ if (envelope.type === "presence.join" || envelope.type === "presence.leave") {
1102
+ if (!envelope.member_id) {
1103
+ console.warn("[Subscription] Presence event missing member_id");
1104
+ return;
1105
+ }
1106
+ const presenceMember = {
1107
+ memberId: envelope.member_id,
1108
+ joinedAt: envelope.timestamp,
1109
+ meta: envelope.meta
1110
+ };
1111
+ if (envelope.type === "presence.join" && this.presenceOptions?.onJoin) {
1112
+ this.presenceOptions.onJoin(presenceMember);
1113
+ } else if (envelope.type === "presence.leave" && this.presenceOptions?.onLeave) {
1114
+ this.presenceOptions.onLeave(presenceMember);
1115
+ }
1116
+ return;
1117
+ }
1048
1118
  if (!envelope.data || typeof envelope.data !== "string") {
1049
1119
  throw new Error("Invalid envelope: missing or invalid data field");
1050
1120
  }
@@ -1081,6 +1151,22 @@ var Subscription = class {
1081
1151
  };
1082
1152
  this.wsClient.onClose(this.wsCloseHandler);
1083
1153
  }
1154
+ /**
1155
+ * Get current presence (requires presence.enabled on subscribe)
1156
+ */
1157
+ async getPresence() {
1158
+ if (!this.presenceOptions?.enabled) {
1159
+ throw new Error("Presence is not enabled for this subscription");
1160
+ }
1161
+ const response = await this.getPresenceFn();
1162
+ return response.members;
1163
+ }
1164
+ /**
1165
+ * Check if presence is enabled for this subscription
1166
+ */
1167
+ hasPresence() {
1168
+ return !!this.presenceOptions?.enabled;
1169
+ }
1084
1170
  /**
1085
1171
  * Register message handler
1086
1172
  */
@@ -1502,6 +1588,38 @@ var StorageClient = class {
1502
1588
  }
1503
1589
  };
1504
1590
 
1591
+ // src/functions/client.ts
1592
+ var FunctionsClient = class {
1593
+ constructor(httpClient, config) {
1594
+ this.httpClient = httpClient;
1595
+ this.gatewayURL = config?.gatewayURL;
1596
+ this.namespace = config?.namespace ?? "default";
1597
+ }
1598
+ /**
1599
+ * Invoke a serverless function by name
1600
+ *
1601
+ * @param functionName - Name of the function to invoke
1602
+ * @param input - Input payload for the function
1603
+ * @returns The function response
1604
+ */
1605
+ async invoke(functionName, input) {
1606
+ const url = this.gatewayURL ? `${this.gatewayURL}/v1/invoke/${this.namespace}/${functionName}` : `/v1/invoke/${this.namespace}/${functionName}`;
1607
+ try {
1608
+ const response = await this.httpClient.post(url, input);
1609
+ return response;
1610
+ } catch (error) {
1611
+ if (error instanceof SDKError) {
1612
+ throw error;
1613
+ }
1614
+ throw new SDKError(
1615
+ `Function ${functionName} failed`,
1616
+ 500,
1617
+ error instanceof Error ? error.message : String(error)
1618
+ );
1619
+ }
1620
+ }
1621
+ };
1622
+
1505
1623
  // src/index.ts
1506
1624
  function createClient(config) {
1507
1625
  const httpClient = new HttpClient({
@@ -1517,7 +1635,7 @@ function createClient(config) {
1517
1635
  apiKey: config.apiKey,
1518
1636
  jwt: config.jwt
1519
1637
  });
1520
- const wsURL = config.wsConfig?.wsURL ?? config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
1638
+ const wsURL = config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
1521
1639
  const db = new DBClient(httpClient);
1522
1640
  const pubsub = new PubSubClient(httpClient, {
1523
1641
  ...config.wsConfig,
@@ -1526,19 +1644,22 @@ function createClient(config) {
1526
1644
  const network = new NetworkClient(httpClient);
1527
1645
  const cache = new CacheClient(httpClient);
1528
1646
  const storage = new StorageClient(httpClient);
1647
+ const functions = new FunctionsClient(httpClient, config.functionsConfig);
1529
1648
  return {
1530
1649
  auth,
1531
1650
  db,
1532
1651
  pubsub,
1533
1652
  network,
1534
1653
  cache,
1535
- storage
1654
+ storage,
1655
+ functions
1536
1656
  };
1537
1657
  }
1538
1658
  export {
1539
1659
  AuthClient,
1540
1660
  CacheClient,
1541
1661
  DBClient,
1662
+ FunctionsClient,
1542
1663
  HttpClient,
1543
1664
  LocalStorageAdapter,
1544
1665
  MemoryStorage,