@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/src/core/http.ts CHANGED
@@ -1,11 +1,35 @@
1
1
  import { SDKError } from "../errors";
2
2
 
3
+ /**
4
+ * Context provided to the onNetworkError callback
5
+ */
6
+ export interface NetworkErrorContext {
7
+ method: "GET" | "POST" | "PUT" | "DELETE" | "WS";
8
+ path: string;
9
+ isRetry: boolean;
10
+ attempt: number;
11
+ }
12
+
13
+ /**
14
+ * Callback invoked when a network error occurs.
15
+ * Use this to trigger gateway failover or other error handling.
16
+ */
17
+ export type NetworkErrorCallback = (
18
+ error: SDKError,
19
+ context: NetworkErrorContext
20
+ ) => void;
21
+
3
22
  export interface HttpClientConfig {
4
23
  baseURL: string;
5
24
  timeout?: number;
6
25
  maxRetries?: number;
7
26
  retryDelayMs?: number;
8
27
  fetch?: typeof fetch;
28
+ /**
29
+ * Callback invoked on network errors (after all retries exhausted).
30
+ * Use this to trigger gateway failover at the application layer.
31
+ */
32
+ onNetworkError?: NetworkErrorCallback;
9
33
  }
10
34
 
11
35
  /**
@@ -39,14 +63,23 @@ export class HttpClient {
39
63
  private fetch: typeof fetch;
40
64
  private apiKey?: string;
41
65
  private jwt?: string;
66
+ private onNetworkError?: NetworkErrorCallback;
42
67
 
43
68
  constructor(config: HttpClientConfig) {
44
69
  this.baseURL = config.baseURL.replace(/\/$/, "");
45
- this.timeout = config.timeout ?? 60000; // Increased from 30s to 60s for pub/sub operations
70
+ this.timeout = config.timeout ?? 60000;
46
71
  this.maxRetries = config.maxRetries ?? 3;
47
72
  this.retryDelayMs = config.retryDelayMs ?? 1000;
48
73
  // Use provided fetch or create one with proper TLS configuration for staging certificates
49
74
  this.fetch = config.fetch ?? createFetchWithTLSConfig();
75
+ this.onNetworkError = config.onNetworkError;
76
+ }
77
+
78
+ /**
79
+ * Set the network error callback
80
+ */
81
+ setOnNetworkError(callback: NetworkErrorCallback | undefined): void {
82
+ this.onNetworkError = callback;
50
83
  }
51
84
 
52
85
  setApiKey(apiKey?: string) {
@@ -121,6 +154,13 @@ export class HttpClient {
121
154
  return this.apiKey;
122
155
  }
123
156
 
157
+ /**
158
+ * Get the base URL
159
+ */
160
+ getBaseURL(): string {
161
+ return this.baseURL;
162
+ }
163
+
124
164
  async request<T = any>(
125
165
  method: "GET" | "POST" | "PUT" | "DELETE",
126
166
  path: string,
@@ -251,6 +291,27 @@ export class HttpClient {
251
291
  }
252
292
  }
253
293
  }
294
+
295
+ // Call the network error callback if configured
296
+ // This allows the app to trigger gateway failover
297
+ if (this.onNetworkError) {
298
+ // Convert native errors (TypeError, AbortError) to SDKError for the callback
299
+ const sdkError =
300
+ error instanceof SDKError
301
+ ? error
302
+ : new SDKError(
303
+ error instanceof Error ? error.message : String(error),
304
+ 0, // httpStatus 0 indicates network-level failure
305
+ "NETWORK_ERROR"
306
+ );
307
+ this.onNetworkError(sdkError, {
308
+ method,
309
+ path,
310
+ isRetry: false,
311
+ attempt: this.maxRetries, // All retries exhausted
312
+ });
313
+ }
314
+
254
315
  throw error;
255
316
  } finally {
256
317
  clearTimeout(timeoutId);
@@ -276,22 +337,31 @@ export class HttpClient {
276
337
  throw SDKError.fromResponse(response.status, body);
277
338
  }
278
339
 
340
+ // Request succeeded - return response
279
341
  const contentType = response.headers.get("content-type");
280
342
  if (contentType?.includes("application/json")) {
281
343
  return response.json();
282
344
  }
283
345
  return response.text();
284
346
  } catch (error) {
285
- if (
347
+ const isRetryableError =
286
348
  error instanceof SDKError &&
287
- attempt < this.maxRetries &&
288
- [408, 429, 500, 502, 503, 504].includes(error.httpStatus)
289
- ) {
349
+ [408, 429, 500, 502, 503, 504].includes(error.httpStatus);
350
+
351
+ // Retry on same gateway for retryable HTTP errors
352
+ if (isRetryableError && attempt < this.maxRetries) {
353
+ if (typeof console !== "undefined") {
354
+ console.warn(
355
+ `[HttpClient] Retrying request (attempt ${attempt + 1}/${this.maxRetries})`
356
+ );
357
+ }
290
358
  await new Promise((resolve) =>
291
359
  setTimeout(resolve, this.retryDelayMs * (attempt + 1))
292
360
  );
293
361
  return this.requestWithRetry(url, options, attempt + 1, startTime);
294
362
  }
363
+
364
+ // All retries exhausted - throw error for app to handle
295
365
  throw error;
296
366
  }
297
367
  }
@@ -381,6 +451,25 @@ export class HttpClient {
381
451
  error
382
452
  );
383
453
  }
454
+
455
+ // Call the network error callback if configured
456
+ if (this.onNetworkError) {
457
+ const sdkError =
458
+ error instanceof SDKError
459
+ ? error
460
+ : new SDKError(
461
+ error instanceof Error ? error.message : String(error),
462
+ 0,
463
+ "NETWORK_ERROR"
464
+ );
465
+ this.onNetworkError(sdkError, {
466
+ method: "POST",
467
+ path,
468
+ isRetry: false,
469
+ attempt: this.maxRetries,
470
+ });
471
+ }
472
+
384
473
  throw error;
385
474
  } finally {
386
475
  clearTimeout(timeoutId);
@@ -409,17 +498,33 @@ export class HttpClient {
409
498
  const response = await this.fetch(url.toString(), fetchOptions);
410
499
  if (!response.ok) {
411
500
  clearTimeout(timeoutId);
412
- const error = await response.json().catch(() => ({
501
+ const errorBody = await response.json().catch(() => ({
413
502
  error: response.statusText,
414
503
  }));
415
- throw SDKError.fromResponse(response.status, error);
504
+ throw SDKError.fromResponse(response.status, errorBody);
416
505
  }
417
506
  return response;
418
507
  } catch (error) {
419
508
  clearTimeout(timeoutId);
420
- if (error instanceof SDKError) {
421
- throw error;
509
+
510
+ // Call the network error callback if configured
511
+ if (this.onNetworkError) {
512
+ const sdkError =
513
+ error instanceof SDKError
514
+ ? error
515
+ : new SDKError(
516
+ error instanceof Error ? error.message : String(error),
517
+ 0,
518
+ "NETWORK_ERROR"
519
+ );
520
+ this.onNetworkError(sdkError, {
521
+ method: "GET",
522
+ path,
523
+ isRetry: false,
524
+ attempt: 0,
525
+ });
422
526
  }
527
+
423
528
  throw error;
424
529
  }
425
530
  }
package/src/core/ws.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import WebSocket from "isomorphic-ws";
2
2
  import { SDKError } from "../errors";
3
+ import { NetworkErrorCallback } from "./http";
3
4
 
4
5
  export interface WSClientConfig {
5
6
  wsURL: string;
6
7
  timeout?: number;
7
8
  authToken?: string;
8
9
  WebSocket?: typeof WebSocket;
10
+ /**
11
+ * Callback invoked on WebSocket errors.
12
+ * Use this to trigger gateway failover at the application layer.
13
+ */
14
+ onNetworkError?: NetworkErrorCallback;
9
15
  }
10
16
 
11
17
  export type WSMessageHandler = (data: string) => void;
@@ -15,13 +21,15 @@ export type WSOpenHandler = () => void;
15
21
 
16
22
  /**
17
23
  * Simple WebSocket client with minimal abstractions
18
- * No complex reconnection, no heartbeats - keep it simple
24
+ * No complex reconnection, no failover - keep it simple
25
+ * Gateway failover is handled at the application layer
19
26
  */
20
27
  export class WSClient {
21
- private url: string;
28
+ private wsURL: string;
22
29
  private timeout: number;
23
30
  private authToken?: string;
24
31
  private WebSocketClass: typeof WebSocket;
32
+ private onNetworkError?: NetworkErrorCallback;
25
33
 
26
34
  private ws?: WebSocket;
27
35
  private messageHandlers: Set<WSMessageHandler> = new Set();
@@ -31,10 +39,25 @@ export class WSClient {
31
39
  private isClosed = false;
32
40
 
33
41
  constructor(config: WSClientConfig) {
34
- this.url = config.wsURL;
42
+ this.wsURL = config.wsURL;
35
43
  this.timeout = config.timeout ?? 30000;
36
44
  this.authToken = config.authToken;
37
45
  this.WebSocketClass = config.WebSocket ?? WebSocket;
46
+ this.onNetworkError = config.onNetworkError;
47
+ }
48
+
49
+ /**
50
+ * Set the network error callback
51
+ */
52
+ setOnNetworkError(callback: NetworkErrorCallback | undefined): void {
53
+ this.onNetworkError = callback;
54
+ }
55
+
56
+ /**
57
+ * Get the current WebSocket URL
58
+ */
59
+ get url(): string {
60
+ return this.wsURL;
38
61
  }
39
62
 
40
63
  /**
@@ -49,14 +72,24 @@ export class WSClient {
49
72
 
50
73
  const timeout = setTimeout(() => {
51
74
  this.ws?.close();
52
- reject(
53
- new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT")
54
- );
75
+ const error = new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT");
76
+
77
+ // Call the network error callback if configured
78
+ if (this.onNetworkError) {
79
+ this.onNetworkError(error, {
80
+ method: "WS",
81
+ path: this.wsURL,
82
+ isRetry: false,
83
+ attempt: 0,
84
+ });
85
+ }
86
+
87
+ reject(error);
55
88
  }, this.timeout);
56
89
 
57
90
  this.ws.addEventListener("open", () => {
58
91
  clearTimeout(timeout);
59
- console.log("[WSClient] Connected to", this.url);
92
+ console.log("[WSClient] Connected to", this.wsURL);
60
93
  this.openHandlers.forEach((handler) => handler());
61
94
  resolve();
62
95
  });
@@ -70,7 +103,19 @@ export class WSClient {
70
103
  console.error("[WSClient] WebSocket error:", event);
71
104
  clearTimeout(timeout);
72
105
  const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
106
+
107
+ // Call the network error callback if configured
108
+ if (this.onNetworkError) {
109
+ this.onNetworkError(error, {
110
+ method: "WS",
111
+ path: this.wsURL,
112
+ isRetry: false,
113
+ attempt: 0,
114
+ });
115
+ }
116
+
73
117
  this.errorHandlers.forEach((handler) => handler(error));
118
+ reject(error);
74
119
  });
75
120
 
76
121
  this.ws.addEventListener("close", () => {
@@ -88,7 +133,7 @@ export class WSClient {
88
133
  * Build WebSocket URL with auth token
89
134
  */
90
135
  private buildWSUrl(): string {
91
- let url = this.url;
136
+ let url = this.wsURL;
92
137
 
93
138
  if (this.authToken) {
94
139
  const separator = url.includes("?") ? "&" : "?";
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Functions Client
3
+ * Client for calling serverless functions on the Orama Network
4
+ */
5
+
6
+ import { HttpClient } from "../core/http";
7
+ import { SDKError } from "../errors";
8
+
9
+ export interface FunctionsClientConfig {
10
+ /**
11
+ * Base URL for the functions gateway
12
+ * Defaults to using the same baseURL as the HTTP client
13
+ */
14
+ gatewayURL?: string;
15
+
16
+ /**
17
+ * Namespace for the functions
18
+ */
19
+ namespace: string;
20
+ }
21
+
22
+ export class FunctionsClient {
23
+ private httpClient: HttpClient;
24
+ private gatewayURL?: string;
25
+ private namespace: string;
26
+
27
+ constructor(httpClient: HttpClient, config?: FunctionsClientConfig) {
28
+ this.httpClient = httpClient;
29
+ this.gatewayURL = config?.gatewayURL;
30
+ this.namespace = config?.namespace ?? "default";
31
+ }
32
+
33
+ /**
34
+ * Invoke a serverless function by name
35
+ *
36
+ * @param functionName - Name of the function to invoke
37
+ * @param input - Input payload for the function
38
+ * @returns The function response
39
+ */
40
+ async invoke<TInput = any, TOutput = any>(
41
+ functionName: string,
42
+ input: TInput
43
+ ): Promise<TOutput> {
44
+ const url = this.gatewayURL
45
+ ? `${this.gatewayURL}/v1/invoke/${this.namespace}/${functionName}`
46
+ : `/v1/invoke/${this.namespace}/${functionName}`;
47
+
48
+ try {
49
+ const response = await this.httpClient.post<TOutput>(url, input);
50
+ return response;
51
+ } catch (error) {
52
+ if (error instanceof SDKError) {
53
+ throw error;
54
+ }
55
+ throw new SDKError(
56
+ `Function ${functionName} failed`,
57
+ 500,
58
+ error instanceof Error ? error.message : String(error)
59
+ );
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Serverless Functions Types
3
+ * Type definitions for calling serverless functions on the Orama Network
4
+ */
5
+
6
+ /**
7
+ * Generic response from a serverless function
8
+ */
9
+ export interface FunctionResponse<T = unknown> {
10
+ success: boolean;
11
+ error?: string;
12
+ data?: T;
13
+ }
14
+
15
+ /**
16
+ * Standard success/error response used by many functions
17
+ */
18
+ export interface SuccessResponse {
19
+ success: boolean;
20
+ error?: string;
21
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { HttpClient, HttpClientConfig } from "./core/http";
1
+ import { HttpClient, HttpClientConfig, NetworkErrorCallback } from "./core/http";
2
2
  import { AuthClient } from "./auth/client";
3
3
  import { DBClient } from "./db/client";
4
4
  import { PubSubClient } from "./pubsub/client";
5
5
  import { NetworkClient } from "./network/client";
6
6
  import { CacheClient } from "./cache/client";
7
7
  import { StorageClient } from "./storage/client";
8
+ import { FunctionsClient, FunctionsClientConfig } from "./functions/client";
8
9
  import { WSClientConfig } from "./core/ws";
9
10
  import {
10
11
  StorageAdapter,
@@ -16,8 +17,14 @@ export interface ClientConfig extends Omit<HttpClientConfig, "fetch"> {
16
17
  apiKey?: string;
17
18
  jwt?: string;
18
19
  storage?: StorageAdapter;
19
- wsConfig?: Partial<WSClientConfig>;
20
+ wsConfig?: Partial<Omit<WSClientConfig, "wsURL">>;
21
+ functionsConfig?: FunctionsClientConfig;
20
22
  fetch?: typeof fetch;
23
+ /**
24
+ * Callback invoked on network errors (HTTP and WebSocket).
25
+ * Use this to trigger gateway failover at the application layer.
26
+ */
27
+ onNetworkError?: NetworkErrorCallback;
21
28
  }
22
29
 
23
30
  export interface Client {
@@ -27,6 +34,7 @@ export interface Client {
27
34
  network: NetworkClient;
28
35
  cache: CacheClient;
29
36
  storage: StorageClient;
37
+ functions: FunctionsClient;
30
38
  }
31
39
 
32
40
  export function createClient(config: ClientConfig): Client {
@@ -36,6 +44,7 @@ export function createClient(config: ClientConfig): Client {
36
44
  maxRetries: config.maxRetries,
37
45
  retryDelayMs: config.retryDelayMs,
38
46
  fetch: config.fetch,
47
+ onNetworkError: config.onNetworkError,
39
48
  });
40
49
 
41
50
  const auth = new AuthClient({
@@ -45,19 +54,19 @@ export function createClient(config: ClientConfig): Client {
45
54
  jwt: config.jwt,
46
55
  });
47
56
 
48
- // Derive WebSocket URL from baseURL if not explicitly provided
49
- const wsURL =
50
- config.wsConfig?.wsURL ??
51
- config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
57
+ // Derive WebSocket URL from baseURL
58
+ const wsURL = config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
52
59
 
53
60
  const db = new DBClient(httpClient);
54
61
  const pubsub = new PubSubClient(httpClient, {
55
62
  ...config.wsConfig,
56
63
  wsURL,
64
+ onNetworkError: config.onNetworkError,
57
65
  });
58
66
  const network = new NetworkClient(httpClient);
59
67
  const cache = new CacheClient(httpClient);
60
68
  const storage = new StorageClient(httpClient);
69
+ const functions = new FunctionsClient(httpClient, config.functionsConfig);
61
70
 
62
71
  return {
63
72
  auth,
@@ -66,10 +75,12 @@ export function createClient(config: ClientConfig): Client {
66
75
  network,
67
76
  cache,
68
77
  storage,
78
+ functions,
69
79
  };
70
80
  }
71
81
 
72
82
  export { HttpClient } from "./core/http";
83
+ export type { NetworkErrorCallback, NetworkErrorContext } from "./core/http";
73
84
  export { WSClient } from "./core/ws";
74
85
  export { AuthClient } from "./auth/client";
75
86
  export { DBClient } from "./db/client";
@@ -79,16 +90,21 @@ export { PubSubClient, Subscription } from "./pubsub/client";
79
90
  export { NetworkClient } from "./network/client";
80
91
  export { CacheClient } from "./cache/client";
81
92
  export { StorageClient } from "./storage/client";
93
+ export { FunctionsClient } from "./functions/client";
82
94
  export { SDKError } from "./errors";
83
95
  export { MemoryStorage, LocalStorageAdapter } from "./auth/types";
84
96
  export type { StorageAdapter, AuthConfig, WhoAmI } from "./auth/types";
85
97
  export type * from "./db/types";
86
98
  export type {
87
- Message,
88
99
  MessageHandler,
89
100
  ErrorHandler,
90
101
  CloseHandler,
91
- } from "./pubsub/client";
102
+ PresenceMember,
103
+ PresenceResponse,
104
+ PresenceOptions,
105
+ SubscribeOptions,
106
+ } from "./pubsub/types";
107
+ export { type PubSubMessage } from "./pubsub/types";
92
108
  export type {
93
109
  PeerInfo,
94
110
  NetworkStatus,
@@ -114,3 +130,5 @@ export type {
114
130
  StoragePinResponse,
115
131
  StorageStatus,
116
132
  } from "./storage/client";
133
+ export type { FunctionsClientConfig } from "./functions/client";
134
+ export type * from "./functions/types";