@debros/network-ts-sdk 0.1.5 → 0.3.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/src/core/ws.ts CHANGED
@@ -4,10 +4,6 @@ import { SDKError } from "../errors";
4
4
  export interface WSClientConfig {
5
5
  wsURL: string;
6
6
  timeout?: number;
7
- maxReconnectAttempts?: number;
8
- reconnectDelayMs?: number;
9
- heartbeatIntervalMs?: number;
10
- authMode?: "header" | "query";
11
7
  authToken?: string;
12
8
  WebSocket?: typeof WebSocket;
13
9
  }
@@ -15,54 +11,53 @@ export interface WSClientConfig {
15
11
  export type WSMessageHandler = (data: string) => void;
16
12
  export type WSErrorHandler = (error: Error) => void;
17
13
  export type WSCloseHandler = () => void;
14
+ export type WSOpenHandler = () => void;
18
15
 
16
+ /**
17
+ * Simple WebSocket client with minimal abstractions
18
+ * No complex reconnection, no heartbeats - keep it simple
19
+ */
19
20
  export class WSClient {
20
21
  private url: string;
21
22
  private timeout: number;
22
- private maxReconnectAttempts: number;
23
- private reconnectDelayMs: number;
24
- private heartbeatIntervalMs: number;
25
- private authMode: "header" | "query";
26
23
  private authToken?: string;
27
24
  private WebSocketClass: typeof WebSocket;
28
25
 
29
26
  private ws?: WebSocket;
30
- private reconnectAttempts = 0;
31
- private heartbeatInterval?: NodeJS.Timeout;
32
27
  private messageHandlers: Set<WSMessageHandler> = new Set();
33
28
  private errorHandlers: Set<WSErrorHandler> = new Set();
34
29
  private closeHandlers: Set<WSCloseHandler> = new Set();
35
- private isManuallyClosed = false;
30
+ private openHandlers: Set<WSOpenHandler> = new Set();
31
+ private isClosed = false;
36
32
 
37
33
  constructor(config: WSClientConfig) {
38
34
  this.url = config.wsURL;
39
35
  this.timeout = config.timeout ?? 30000;
40
- this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5;
41
- this.reconnectDelayMs = config.reconnectDelayMs ?? 1000;
42
- this.heartbeatIntervalMs = config.heartbeatIntervalMs ?? 30000;
43
- this.authMode = config.authMode ?? "header";
44
36
  this.authToken = config.authToken;
45
37
  this.WebSocketClass = config.WebSocket ?? WebSocket;
46
38
  }
47
39
 
40
+ /**
41
+ * Connect to WebSocket server
42
+ */
48
43
  connect(): Promise<void> {
49
44
  return new Promise((resolve, reject) => {
50
45
  try {
51
46
  const wsUrl = this.buildWSUrl();
52
47
  this.ws = new this.WebSocketClass(wsUrl);
53
-
54
- // Note: Custom headers via ws library in Node.js are not sent with WebSocket upgrade requests
55
- // so we rely on query parameters for authentication
48
+ this.isClosed = false;
56
49
 
57
50
  const timeout = setTimeout(() => {
58
51
  this.ws?.close();
59
- reject(new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT"));
52
+ reject(
53
+ new SDKError("WebSocket connection timeout", 408, "WS_TIMEOUT")
54
+ );
60
55
  }, this.timeout);
61
56
 
62
57
  this.ws.addEventListener("open", () => {
63
58
  clearTimeout(timeout);
64
- this.reconnectAttempts = 0;
65
- this.startHeartbeat();
59
+ console.log("[WSClient] Connected to", this.url);
60
+ this.openHandlers.forEach((handler) => handler());
66
61
  resolve();
67
62
  });
68
63
 
@@ -72,24 +67,16 @@ export class WSClient {
72
67
  });
73
68
 
74
69
  this.ws.addEventListener("error", (event: Event) => {
70
+ console.error("[WSClient] WebSocket error:", event);
75
71
  clearTimeout(timeout);
76
- const error = new SDKError(
77
- "WebSocket error",
78
- 500,
79
- "WS_ERROR",
80
- event
81
- );
72
+ const error = new SDKError("WebSocket error", 500, "WS_ERROR", event);
82
73
  this.errorHandlers.forEach((handler) => handler(error));
83
74
  });
84
75
 
85
76
  this.ws.addEventListener("close", () => {
86
77
  clearTimeout(timeout);
87
- this.stopHeartbeat();
88
- if (!this.isManuallyClosed) {
89
- this.attemptReconnect();
90
- } else {
91
- this.closeHandlers.forEach((handler) => handler());
92
- }
78
+ console.log("[WSClient] Connection closed");
79
+ this.closeHandlers.forEach((handler) => handler());
93
80
  });
94
81
  } catch (error) {
95
82
  reject(error);
@@ -97,86 +84,106 @@ export class WSClient {
97
84
  });
98
85
  }
99
86
 
87
+ /**
88
+ * Build WebSocket URL with auth token
89
+ */
100
90
  private buildWSUrl(): string {
101
91
  let url = this.url;
102
-
103
- // Always append auth token as query parameter for compatibility
104
- // Works in both Node.js and browser environments
92
+
105
93
  if (this.authToken) {
106
94
  const separator = url.includes("?") ? "&" : "?";
107
95
  const paramName = this.authToken.startsWith("ak_") ? "api_key" : "token";
108
96
  url += `${separator}${paramName}=${encodeURIComponent(this.authToken)}`;
109
97
  }
110
-
111
- return url;
112
- }
113
98
 
114
- private startHeartbeat() {
115
- this.heartbeatInterval = setInterval(() => {
116
- if (this.ws?.readyState === WebSocket.OPEN) {
117
- this.ws.send(JSON.stringify({ type: "ping" }));
118
- }
119
- }, this.heartbeatIntervalMs);
120
- }
121
-
122
- private stopHeartbeat() {
123
- if (this.heartbeatInterval) {
124
- clearInterval(this.heartbeatInterval);
125
- this.heartbeatInterval = undefined;
126
- }
127
- }
128
-
129
- private attemptReconnect() {
130
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
131
- this.reconnectAttempts++;
132
- const delayMs = this.reconnectDelayMs * this.reconnectAttempts;
133
- setTimeout(() => {
134
- this.connect().catch((error) => {
135
- this.errorHandlers.forEach((handler) => handler(error));
136
- });
137
- }, delayMs);
138
- } else {
139
- this.closeHandlers.forEach((handler) => handler());
140
- }
99
+ return url;
141
100
  }
142
101
 
143
- onMessage(handler: WSMessageHandler) {
102
+ /**
103
+ * Register message handler
104
+ */
105
+ onMessage(handler: WSMessageHandler): () => void {
144
106
  this.messageHandlers.add(handler);
145
107
  return () => this.messageHandlers.delete(handler);
146
108
  }
147
109
 
148
- onError(handler: WSErrorHandler) {
110
+ /**
111
+ * Unregister message handler
112
+ */
113
+ offMessage(handler: WSMessageHandler): void {
114
+ this.messageHandlers.delete(handler);
115
+ }
116
+
117
+ /**
118
+ * Register error handler
119
+ */
120
+ onError(handler: WSErrorHandler): () => void {
149
121
  this.errorHandlers.add(handler);
150
122
  return () => this.errorHandlers.delete(handler);
151
123
  }
152
124
 
153
- onClose(handler: WSCloseHandler) {
125
+ /**
126
+ * Unregister error handler
127
+ */
128
+ offError(handler: WSErrorHandler): void {
129
+ this.errorHandlers.delete(handler);
130
+ }
131
+
132
+ /**
133
+ * Register close handler
134
+ */
135
+ onClose(handler: WSCloseHandler): () => void {
154
136
  this.closeHandlers.add(handler);
155
137
  return () => this.closeHandlers.delete(handler);
156
138
  }
157
139
 
158
- send(data: string) {
140
+ /**
141
+ * Unregister close handler
142
+ */
143
+ offClose(handler: WSCloseHandler): void {
144
+ this.closeHandlers.delete(handler);
145
+ }
146
+
147
+ /**
148
+ * Register open handler
149
+ */
150
+ onOpen(handler: WSOpenHandler): () => void {
151
+ this.openHandlers.add(handler);
152
+ return () => this.openHandlers.delete(handler);
153
+ }
154
+
155
+ /**
156
+ * Send data through WebSocket
157
+ */
158
+ send(data: string): void {
159
159
  if (this.ws?.readyState !== WebSocket.OPEN) {
160
- throw new SDKError(
161
- "WebSocket is not connected",
162
- 500,
163
- "WS_NOT_CONNECTED"
164
- );
160
+ throw new SDKError("WebSocket is not connected", 500, "WS_NOT_CONNECTED");
165
161
  }
166
162
  this.ws.send(data);
167
163
  }
168
164
 
169
- close() {
170
- this.isManuallyClosed = true;
171
- this.stopHeartbeat();
165
+ /**
166
+ * Close WebSocket connection
167
+ */
168
+ close(): void {
169
+ if (this.isClosed) {
170
+ return;
171
+ }
172
+ this.isClosed = true;
172
173
  this.ws?.close();
173
174
  }
174
175
 
176
+ /**
177
+ * Check if WebSocket is connected
178
+ */
175
179
  isConnected(): boolean {
176
- return this.ws?.readyState === WebSocket.OPEN;
180
+ return !this.isClosed && this.ws?.readyState === WebSocket.OPEN;
177
181
  }
178
182
 
179
- setAuthToken(token?: string) {
183
+ /**
184
+ * Update auth token
185
+ */
186
+ setAuthToken(token?: string): void {
180
187
  this.authToken = token;
181
188
  }
182
189
  }
@@ -98,7 +98,9 @@ export class Repository<T extends Record<string, any>> {
98
98
  private buildInsertSql(entity: T): string {
99
99
  const columns = Object.keys(entity).filter((k) => entity[k] !== undefined);
100
100
  const placeholders = columns.map(() => "?").join(", ");
101
- return `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders})`;
101
+ return `INSERT INTO ${this.tableName} (${columns.join(
102
+ ", "
103
+ )}) VALUES (${placeholders})`;
102
104
  }
103
105
 
104
106
  private buildInsertArgs(entity: T): any[] {
@@ -111,7 +113,9 @@ export class Repository<T extends Record<string, any>> {
111
113
  const columns = Object.keys(entity)
112
114
  .filter((k) => entity[k] !== undefined && k !== this.primaryKey)
113
115
  .map((k) => `${k} = ?`);
114
- return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${this.primaryKey} = ?`;
116
+ return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${
117
+ this.primaryKey
118
+ } = ?`;
115
119
  }
116
120
 
117
121
  private buildUpdateArgs(entity: T): any[] {
package/src/index.ts CHANGED
@@ -3,8 +3,14 @@ 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
+ import { CacheClient } from "./cache/client";
7
+ import { StorageClient } from "./storage/client";
6
8
  import { WSClientConfig } from "./core/ws";
7
- import { StorageAdapter, MemoryStorage, LocalStorageAdapter } from "./auth/types";
9
+ import {
10
+ StorageAdapter,
11
+ MemoryStorage,
12
+ LocalStorageAdapter,
13
+ } from "./auth/types";
8
14
 
9
15
  export interface ClientConfig extends Omit<HttpClientConfig, "fetch"> {
10
16
  apiKey?: string;
@@ -19,6 +25,8 @@ export interface Client {
19
25
  db: DBClient;
20
26
  pubsub: PubSubClient;
21
27
  network: NetworkClient;
28
+ cache: CacheClient;
29
+ storage: StorageClient;
22
30
  }
23
31
 
24
32
  export function createClient(config: ClientConfig): Client {
@@ -38,8 +46,9 @@ export function createClient(config: ClientConfig): Client {
38
46
  });
39
47
 
40
48
  // Derive WebSocket URL from baseURL if not explicitly provided
41
- const wsURL = config.wsConfig?.wsURL ??
42
- config.baseURL.replace(/^http/, 'ws').replace(/\/$/, '');
49
+ const wsURL =
50
+ config.wsConfig?.wsURL ??
51
+ config.baseURL.replace(/^http/, "ws").replace(/\/$/, "");
43
52
 
44
53
  const db = new DBClient(httpClient);
45
54
  const pubsub = new PubSubClient(httpClient, {
@@ -47,16 +56,19 @@ export function createClient(config: ClientConfig): Client {
47
56
  wsURL,
48
57
  });
49
58
  const network = new NetworkClient(httpClient);
59
+ const cache = new CacheClient(httpClient);
60
+ const storage = new StorageClient(httpClient);
50
61
 
51
62
  return {
52
63
  auth,
53
64
  db,
54
65
  pubsub,
55
66
  network,
67
+ cache,
68
+ storage,
56
69
  };
57
70
  }
58
71
 
59
- // Re-exports
60
72
  export { HttpClient } from "./core/http";
61
73
  export { WSClient } from "./core/ws";
62
74
  export { AuthClient } from "./auth/client";
@@ -65,13 +77,11 @@ export { QueryBuilder } from "./db/qb";
65
77
  export { Repository } from "./db/repository";
66
78
  export { PubSubClient, Subscription } from "./pubsub/client";
67
79
  export { NetworkClient } from "./network/client";
80
+ export { CacheClient } from "./cache/client";
81
+ export { StorageClient } from "./storage/client";
68
82
  export { SDKError } from "./errors";
69
83
  export { MemoryStorage, LocalStorageAdapter } from "./auth/types";
70
- export type {
71
- StorageAdapter,
72
- AuthConfig,
73
- WhoAmI,
74
- } from "./auth/types";
84
+ export type { StorageAdapter, AuthConfig, WhoAmI } from "./auth/types";
75
85
  export type * from "./db/types";
76
86
  export type {
77
87
  Message,
@@ -79,4 +89,28 @@ export type {
79
89
  ErrorHandler,
80
90
  CloseHandler,
81
91
  } from "./pubsub/client";
82
- export type { PeerInfo, NetworkStatus } from "./network/client";
92
+ export type {
93
+ PeerInfo,
94
+ NetworkStatus,
95
+ ProxyRequest,
96
+ ProxyResponse,
97
+ } from "./network/client";
98
+ export type {
99
+ CacheGetRequest,
100
+ CacheGetResponse,
101
+ CachePutRequest,
102
+ CachePutResponse,
103
+ CacheDeleteRequest,
104
+ CacheDeleteResponse,
105
+ CacheMultiGetRequest,
106
+ CacheMultiGetResponse,
107
+ CacheScanRequest,
108
+ CacheScanResponse,
109
+ CacheHealthResponse,
110
+ } from "./cache/client";
111
+ export type {
112
+ StorageUploadResponse,
113
+ StoragePinRequest,
114
+ StoragePinResponse,
115
+ StorageStatus,
116
+ } from "./storage/client";
@@ -7,9 +7,25 @@ export interface PeerInfo {
7
7
  }
8
8
 
9
9
  export interface NetworkStatus {
10
- healthy: boolean;
11
- peers: number;
12
- uptime?: number;
10
+ node_id: string;
11
+ connected: boolean;
12
+ peer_count: number;
13
+ database_size: number;
14
+ uptime: number;
15
+ }
16
+
17
+ export interface ProxyRequest {
18
+ url: string;
19
+ method: string;
20
+ headers?: Record<string, string>;
21
+ body?: string;
22
+ }
23
+
24
+ export interface ProxyResponse {
25
+ status_code: number;
26
+ headers: Record<string, string>;
27
+ body: string;
28
+ error?: string;
13
29
  }
14
30
 
15
31
  export class NetworkClient {
@@ -35,7 +51,9 @@ export class NetworkClient {
35
51
  * Get network status.
36
52
  */
37
53
  async status(): Promise<NetworkStatus> {
38
- const response = await this.httpClient.get<NetworkStatus>("/v1/status");
54
+ const response = await this.httpClient.get<NetworkStatus>(
55
+ "/v1/network/status"
56
+ );
39
57
  return response;
40
58
  }
41
59
 
@@ -62,4 +80,40 @@ export class NetworkClient {
62
80
  async disconnect(peerId: string): Promise<void> {
63
81
  await this.httpClient.post("/v1/network/disconnect", { peer_id: peerId });
64
82
  }
83
+
84
+ /**
85
+ * Proxy an HTTP request through the Anyone network.
86
+ * Requires authentication (API key or JWT).
87
+ *
88
+ * @param request - The proxy request configuration
89
+ * @returns The proxied response
90
+ * @throws {SDKError} If the Anyone proxy is not available or the request fails
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const response = await client.network.proxyAnon({
95
+ * url: 'https://api.example.com/data',
96
+ * method: 'GET',
97
+ * headers: {
98
+ * 'Accept': 'application/json'
99
+ * }
100
+ * });
101
+ *
102
+ * console.log(response.status_code); // 200
103
+ * console.log(response.body); // Response data
104
+ * ```
105
+ */
106
+ async proxyAnon(request: ProxyRequest): Promise<ProxyResponse> {
107
+ const response = await this.httpClient.post<ProxyResponse>(
108
+ "/v1/proxy/anon",
109
+ request
110
+ );
111
+
112
+ // Check if the response contains an error
113
+ if (response.error) {
114
+ throw new Error(`Proxy request failed: ${response.error}`);
115
+ }
116
+
117
+ return response;
118
+ }
65
119
  }