@debros/network-ts-sdk 0.3.4 → 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
@@ -91,6 +91,12 @@ var HttpClient = class {
91
91
  getApiKey() {
92
92
  return this.apiKey;
93
93
  }
94
+ /**
95
+ * Get the base URL
96
+ */
97
+ getBaseURL() {
98
+ return this.baseURL;
99
+ }
94
100
  async request(method, path, options = {}) {
95
101
  const startTime = performance.now();
96
102
  const url = new URL(this.baseURL + path);
@@ -212,7 +218,13 @@ var HttpClient = class {
212
218
  }
213
219
  return response.text();
214
220
  } catch (error) {
215
- 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
+ }
216
228
  await new Promise(
217
229
  (resolve) => setTimeout(resolve, this.retryDelayMs * (attempt + 1))
218
230
  );
@@ -798,11 +810,17 @@ var WSClient = class {
798
810
  this.closeHandlers = /* @__PURE__ */ new Set();
799
811
  this.openHandlers = /* @__PURE__ */ new Set();
800
812
  this.isClosed = false;
801
- this.url = config.wsURL;
813
+ this.wsURL = config.wsURL;
802
814
  this.timeout = config.timeout ?? 3e4;
803
815
  this.authToken = config.authToken;
804
816
  this.WebSocketClass = config.WebSocket ?? WebSocket;
805
817
  }
818
+ /**
819
+ * Get the current WebSocket URL
820
+ */
821
+ get url() {
822
+ return this.wsURL;
823
+ }
806
824
  /**
807
825
  * Connect to WebSocket server
808
826
  */
@@ -820,7 +838,7 @@ var WSClient = class {
820
838
  }, this.timeout);
821
839
  this.ws.addEventListener("open", () => {
822
840
  clearTimeout(timeout);
823
- console.log("[WSClient] Connected to", this.url);
841
+ console.log("[WSClient] Connected to", this.wsURL);
824
842
  this.openHandlers.forEach((handler) => handler());
825
843
  resolve();
826
844
  });
@@ -833,6 +851,7 @@ var WSClient = class {
833
851
  clearTimeout(timeout);
834
852
  const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
835
853
  this.errorHandlers.forEach((handler) => handler(error));
854
+ reject(error);
836
855
  });
837
856
  this.ws.addEventListener("close", () => {
838
857
  clearTimeout(timeout);
@@ -848,7 +867,7 @@ var WSClient = class {
848
867
  * Build WebSocket URL with auth token
849
868
  */
850
869
  buildWSUrl() {
851
- let url = this.url;
870
+ let url = this.wsURL;
852
871
  if (this.authToken) {
853
872
  const separator = url.includes("?") ? "&" : "?";
854
873
  const paramName = this.authToken.startsWith("ak_") ? "api_key" : "token";
@@ -1009,14 +1028,32 @@ var PubSubClient = class {
1009
1028
  );
1010
1029
  return response.topics || [];
1011
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
+ }
1012
1040
  /**
1013
1041
  * Subscribe to a topic via WebSocket
1014
1042
  * Creates one WebSocket connection per topic
1015
1043
  */
1016
- async subscribe(topic, handlers = {}) {
1044
+ async subscribe(topic, options = {}) {
1017
1045
  const wsUrl = new URL(this.wsConfig.wsURL || "ws://127.0.0.1:6001");
1018
1046
  wsUrl.pathname = "/v1/pubsub/ws";
1019
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
+ }
1020
1057
  const authToken = this.httpClient.getApiKey() ?? this.httpClient.getToken();
1021
1058
  const wsClient = new WSClient({
1022
1059
  ...this.wsConfig,
@@ -1024,21 +1061,26 @@ var PubSubClient = class {
1024
1061
  authToken
1025
1062
  });
1026
1063
  await wsClient.connect();
1027
- const subscription = new Subscription(wsClient, topic);
1028
- if (handlers.onMessage) {
1029
- 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);
1030
1072
  }
1031
- if (handlers.onError) {
1032
- subscription.onError(handlers.onError);
1073
+ if (options.onError) {
1074
+ subscription.onError(options.onError);
1033
1075
  }
1034
- if (handlers.onClose) {
1035
- subscription.onClose(handlers.onClose);
1076
+ if (options.onClose) {
1077
+ subscription.onClose(options.onClose);
1036
1078
  }
1037
1079
  return subscription;
1038
1080
  }
1039
1081
  };
1040
1082
  var Subscription = class {
1041
- constructor(wsClient, topic) {
1083
+ constructor(wsClient, topic, presenceOptions, getPresenceFn) {
1042
1084
  this.messageHandlers = /* @__PURE__ */ new Set();
1043
1085
  this.errorHandlers = /* @__PURE__ */ new Set();
1044
1086
  this.closeHandlers = /* @__PURE__ */ new Set();
@@ -1048,12 +1090,31 @@ var Subscription = class {
1048
1090
  this.wsCloseHandler = null;
1049
1091
  this.wsClient = wsClient;
1050
1092
  this.topic = topic;
1093
+ this.presenceOptions = presenceOptions;
1094
+ this.getPresenceFn = getPresenceFn;
1051
1095
  this.wsMessageHandler = (data) => {
1052
1096
  try {
1053
1097
  const envelope = JSON.parse(data);
1054
1098
  if (!envelope || typeof envelope !== "object") {
1055
1099
  throw new Error("Invalid envelope: not an object");
1056
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
+ }
1057
1118
  if (!envelope.data || typeof envelope.data !== "string") {
1058
1119
  throw new Error("Invalid envelope: missing or invalid data field");
1059
1120
  }
@@ -1090,6 +1151,22 @@ var Subscription = class {
1090
1151
  };
1091
1152
  this.wsClient.onClose(this.wsCloseHandler);
1092
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
+ }
1093
1170
  /**
1094
1171
  * Register message handler
1095
1172
  */
@@ -1511,6 +1588,38 @@ var StorageClient = class {
1511
1588
  }
1512
1589
  };
1513
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
+
1514
1623
  // src/index.ts
1515
1624
  function createClient(config) {
1516
1625
  const httpClient = new HttpClient({
@@ -1526,7 +1635,7 @@ function createClient(config) {
1526
1635
  apiKey: config.apiKey,
1527
1636
  jwt: config.jwt
1528
1637
  });
1529
- const wsURL = config.wsConfig?.wsURL ?? config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
1638
+ const wsURL = config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
1530
1639
  const db = new DBClient(httpClient);
1531
1640
  const pubsub = new PubSubClient(httpClient, {
1532
1641
  ...config.wsConfig,
@@ -1535,19 +1644,22 @@ function createClient(config) {
1535
1644
  const network = new NetworkClient(httpClient);
1536
1645
  const cache = new CacheClient(httpClient);
1537
1646
  const storage = new StorageClient(httpClient);
1647
+ const functions = new FunctionsClient(httpClient, config.functionsConfig);
1538
1648
  return {
1539
1649
  auth,
1540
1650
  db,
1541
1651
  pubsub,
1542
1652
  network,
1543
1653
  cache,
1544
- storage
1654
+ storage,
1655
+ functions
1545
1656
  };
1546
1657
  }
1547
1658
  export {
1548
1659
  AuthClient,
1549
1660
  CacheClient,
1550
1661
  DBClient,
1662
+ FunctionsClient,
1551
1663
  HttpClient,
1552
1664
  LocalStorageAdapter,
1553
1665
  MemoryStorage,