@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/README.md CHANGED
@@ -122,10 +122,23 @@ const results = await client.db.transaction([
122
122
 
123
123
  ### Pub/Sub Messaging
124
124
 
125
+ The SDK provides a robust pub/sub client with:
126
+
127
+ - **Multi-subscriber support**: Multiple connections can subscribe to the same topic
128
+ - **Namespace isolation**: Topics are scoped to your authenticated namespace
129
+ - **Server timestamps**: Messages preserve server-side timestamps
130
+ - **Binary-safe**: Supports both string and binary (`Uint8Array`) payloads
131
+ - **Strict envelope validation**: Type-safe message parsing with error handling
132
+
125
133
  #### Publish a Message
126
134
 
127
135
  ```typescript
136
+ // Publish a string message
128
137
  await client.pubsub.publish("notifications", "Hello, Network!");
138
+
139
+ // Publish binary data
140
+ const binaryData = new Uint8Array([1, 2, 3, 4]);
141
+ await client.pubsub.publish("binary-topic", binaryData);
129
142
  ```
130
143
 
131
144
  #### Subscribe to Topics
@@ -133,7 +146,9 @@ await client.pubsub.publish("notifications", "Hello, Network!");
133
146
  ```typescript
134
147
  const subscription = await client.pubsub.subscribe("notifications", {
135
148
  onMessage: (msg) => {
136
- console.log("Received:", msg.data);
149
+ console.log("Topic:", msg.topic);
150
+ console.log("Data:", msg.data);
151
+ console.log("Server timestamp:", new Date(msg.timestamp));
137
152
  },
138
153
  onError: (err) => {
139
154
  console.error("Subscription error:", err);
@@ -147,6 +162,52 @@ const subscription = await client.pubsub.subscribe("notifications", {
147
162
  subscription.close();
148
163
  ```
149
164
 
165
+ **Message Interface:**
166
+
167
+ ```typescript
168
+ interface Message {
169
+ data: string; // Decoded message payload (string)
170
+ topic: string; // Topic name
171
+ timestamp: number; // Server timestamp in milliseconds
172
+ }
173
+ ```
174
+
175
+ #### Debug Raw Envelopes
176
+
177
+ For debugging, you can inspect raw message envelopes before decoding:
178
+
179
+ ```typescript
180
+ const subscription = await client.pubsub.subscribe("notifications", {
181
+ onMessage: (msg) => {
182
+ console.log("Decoded message:", msg.data);
183
+ },
184
+ onRaw: (envelope) => {
185
+ console.log("Raw envelope:", envelope);
186
+ // { data: "base64...", timestamp: 1234567890, topic: "notifications" }
187
+ },
188
+ });
189
+ ```
190
+
191
+ #### Multi-Subscriber Support
192
+
193
+ Multiple subscriptions to the same topic are supported. Each receives its own copy of messages:
194
+
195
+ ```typescript
196
+ // First subscriber
197
+ const sub1 = await client.pubsub.subscribe("events", {
198
+ onMessage: (msg) => console.log("Sub1:", msg.data),
199
+ });
200
+
201
+ // Second subscriber (both receive messages)
202
+ const sub2 = await client.pubsub.subscribe("events", {
203
+ onMessage: (msg) => console.log("Sub2:", msg.data),
204
+ });
205
+
206
+ // Unsubscribe independently
207
+ sub1.close(); // sub2 still active
208
+ sub2.close(); // fully unsubscribed
209
+ ```
210
+
150
211
  #### List Topics
151
212
 
152
213
  ```typescript
@@ -211,6 +272,40 @@ peers.forEach((peer) => {
211
272
  });
212
273
  ```
213
274
 
275
+ #### Proxy Requests Through Anyone Network
276
+
277
+ Make anonymous HTTP requests through the Anyone network:
278
+
279
+ ```typescript
280
+ // Simple GET request
281
+ const response = await client.network.proxyAnon({
282
+ url: "https://api.example.com/data",
283
+ method: "GET",
284
+ headers: {
285
+ Accept: "application/json",
286
+ },
287
+ });
288
+
289
+ console.log(response.status_code); // 200
290
+ console.log(response.body); // Response data as string
291
+ console.log(response.headers); // Response headers
292
+
293
+ // POST request with body
294
+ const postResponse = await client.network.proxyAnon({
295
+ url: "https://api.example.com/submit",
296
+ method: "POST",
297
+ headers: {
298
+ "Content-Type": "application/json",
299
+ },
300
+ body: JSON.stringify({ key: "value" }),
301
+ });
302
+
303
+ // Parse JSON response
304
+ const data = JSON.parse(postResponse.body);
305
+ ```
306
+
307
+ **Note:** The proxy endpoint requires authentication (API key or JWT) and only works when the Anyone relay is running on the gateway server.
308
+
214
309
  ## Configuration
215
310
 
216
311
  ### ClientConfig
package/dist/index.d.ts CHANGED
@@ -20,16 +20,29 @@ declare class HttpClient {
20
20
  setJwt(jwt?: string): void;
21
21
  private getAuthHeaders;
22
22
  private getAuthToken;
23
+ getApiKey(): string | undefined;
23
24
  request<T = any>(method: "GET" | "POST" | "PUT" | "DELETE", path: string, options?: {
24
25
  body?: any;
25
26
  headers?: Record<string, string>;
26
27
  query?: Record<string, string | number | boolean>;
28
+ timeout?: number;
27
29
  }): Promise<T>;
28
30
  private requestWithRetry;
29
31
  get<T = any>(path: string, options?: Omit<Parameters<typeof this.request>[2], "body">): Promise<T>;
30
32
  post<T = any>(path: string, body?: any, options?: Omit<Parameters<typeof this.request>[2], "body">): Promise<T>;
31
33
  put<T = any>(path: string, body?: any, options?: Omit<Parameters<typeof this.request>[2], "body">): Promise<T>;
32
34
  delete<T = any>(path: string, options?: Omit<Parameters<typeof this.request>[2], "body">): Promise<T>;
35
+ /**
36
+ * Upload a file using multipart/form-data
37
+ * This is a special method for file uploads that bypasses JSON serialization
38
+ */
39
+ uploadFile<T = any>(path: string, formData: FormData, options?: {
40
+ timeout?: number;
41
+ }): Promise<T>;
42
+ /**
43
+ * Get a binary response (returns Response object for streaming)
44
+ */
45
+ getBinary(path: string): Promise<Response>;
33
46
  getToken(): string | undefined;
34
47
  }
35
48
 
@@ -76,8 +89,62 @@ declare class AuthClient {
76
89
  getToken(): string | undefined;
77
90
  whoami(): Promise<WhoAmI>;
78
91
  refresh(): Promise<string>;
92
+ /**
93
+ * Logout user and clear JWT, but preserve API key
94
+ * Use this for user logout in apps where API key is app-level credential
95
+ */
96
+ logoutUser(): Promise<void>;
97
+ /**
98
+ * Full logout - clears both JWT and API key
99
+ * Use this to completely reset authentication state
100
+ */
79
101
  logout(): Promise<void>;
80
102
  clear(): Promise<void>;
103
+ /**
104
+ * Request a challenge nonce for wallet authentication
105
+ */
106
+ challenge(params: {
107
+ wallet: string;
108
+ purpose?: string;
109
+ namespace?: string;
110
+ }): Promise<{
111
+ nonce: string;
112
+ wallet: string;
113
+ namespace: string;
114
+ expires_at: string;
115
+ }>;
116
+ /**
117
+ * Verify wallet signature and get JWT token
118
+ */
119
+ verify(params: {
120
+ wallet: string;
121
+ nonce: string;
122
+ signature: string;
123
+ namespace?: string;
124
+ chain_type?: "ETH" | "SOL";
125
+ }): Promise<{
126
+ access_token: string;
127
+ refresh_token?: string;
128
+ subject: string;
129
+ namespace: string;
130
+ api_key?: string;
131
+ expires_in?: number;
132
+ token_type?: string;
133
+ }>;
134
+ /**
135
+ * Get API key for wallet (creates namespace ownership)
136
+ */
137
+ getApiKey(params: {
138
+ wallet: string;
139
+ nonce: string;
140
+ signature: string;
141
+ namespace?: string;
142
+ chain_type?: "ETH" | "SOL";
143
+ }): Promise<{
144
+ api_key: string;
145
+ namespace: string;
146
+ wallet: string;
147
+ }>;
81
148
  }
82
149
 
83
150
  declare class QueryBuilder {
@@ -214,70 +281,110 @@ declare class DBClient {
214
281
  interface WSClientConfig {
215
282
  wsURL: string;
216
283
  timeout?: number;
217
- maxReconnectAttempts?: number;
218
- reconnectDelayMs?: number;
219
- heartbeatIntervalMs?: number;
220
- authMode?: "header" | "query";
221
284
  authToken?: string;
222
285
  WebSocket?: typeof WebSocket;
223
286
  }
224
287
  type WSMessageHandler = (data: string) => void;
225
288
  type WSErrorHandler = (error: Error) => void;
226
289
  type WSCloseHandler = () => void;
290
+ type WSOpenHandler = () => void;
291
+ /**
292
+ * Simple WebSocket client with minimal abstractions
293
+ * No complex reconnection, no heartbeats - keep it simple
294
+ */
227
295
  declare class WSClient {
228
296
  private url;
229
297
  private timeout;
230
- private maxReconnectAttempts;
231
- private reconnectDelayMs;
232
- private heartbeatIntervalMs;
233
- private authMode;
234
298
  private authToken?;
235
299
  private WebSocketClass;
236
300
  private ws?;
237
- private reconnectAttempts;
238
- private heartbeatInterval?;
239
301
  private messageHandlers;
240
302
  private errorHandlers;
241
303
  private closeHandlers;
242
- private isManuallyClosed;
304
+ private openHandlers;
305
+ private isClosed;
243
306
  constructor(config: WSClientConfig);
307
+ /**
308
+ * Connect to WebSocket server
309
+ */
244
310
  connect(): Promise<void>;
311
+ /**
312
+ * Build WebSocket URL with auth token
313
+ */
245
314
  private buildWSUrl;
246
- private startHeartbeat;
247
- private stopHeartbeat;
248
- private attemptReconnect;
249
- onMessage(handler: WSMessageHandler): () => boolean;
250
- onError(handler: WSErrorHandler): () => boolean;
251
- onClose(handler: WSCloseHandler): () => boolean;
315
+ /**
316
+ * Register message handler
317
+ */
318
+ onMessage(handler: WSMessageHandler): () => void;
319
+ /**
320
+ * Unregister message handler
321
+ */
322
+ offMessage(handler: WSMessageHandler): void;
323
+ /**
324
+ * Register error handler
325
+ */
326
+ onError(handler: WSErrorHandler): () => void;
327
+ /**
328
+ * Unregister error handler
329
+ */
330
+ offError(handler: WSErrorHandler): void;
331
+ /**
332
+ * Register close handler
333
+ */
334
+ onClose(handler: WSCloseHandler): () => void;
335
+ /**
336
+ * Unregister close handler
337
+ */
338
+ offClose(handler: WSCloseHandler): void;
339
+ /**
340
+ * Register open handler
341
+ */
342
+ onOpen(handler: WSOpenHandler): () => void;
343
+ /**
344
+ * Send data through WebSocket
345
+ */
252
346
  send(data: string): void;
347
+ /**
348
+ * Close WebSocket connection
349
+ */
253
350
  close(): void;
351
+ /**
352
+ * Check if WebSocket is connected
353
+ */
254
354
  isConnected(): boolean;
355
+ /**
356
+ * Update auth token
357
+ */
255
358
  setAuthToken(token?: string): void;
256
359
  }
257
360
 
258
361
  interface Message {
259
362
  data: string;
260
363
  topic: string;
261
- timestamp?: number;
364
+ timestamp: number;
262
365
  }
263
366
  type MessageHandler = (message: Message) => void;
264
367
  type ErrorHandler = (error: Error) => void;
265
368
  type CloseHandler = () => void;
369
+ /**
370
+ * Simple PubSub client - one WebSocket connection per topic
371
+ * No connection pooling, no reference counting - keep it simple
372
+ */
266
373
  declare class PubSubClient {
267
374
  private httpClient;
268
375
  private wsConfig;
269
376
  constructor(httpClient: HttpClient, wsConfig?: Partial<WSClientConfig>);
270
377
  /**
271
- * Publish a message to a topic.
378
+ * Publish a message to a topic via HTTP
272
379
  */
273
380
  publish(topic: string, data: string | Uint8Array): Promise<void>;
274
381
  /**
275
- * List active topics in the current namespace.
382
+ * List active topics in the current namespace
276
383
  */
277
384
  topics(): Promise<string[]>;
278
385
  /**
279
- * Subscribe to a topic via WebSocket.
280
- * Returns a subscription object with event handlers.
386
+ * Subscribe to a topic via WebSocket
387
+ * Creates one WebSocket connection per topic
281
388
  */
282
389
  subscribe(topic: string, handlers?: {
283
390
  onMessage?: MessageHandler;
@@ -285,17 +392,39 @@ declare class PubSubClient {
285
392
  onClose?: CloseHandler;
286
393
  }): Promise<Subscription>;
287
394
  }
395
+ /**
396
+ * Subscription represents an active WebSocket subscription to a topic
397
+ */
288
398
  declare class Subscription {
289
399
  private wsClient;
290
400
  private topic;
291
401
  private messageHandlers;
292
402
  private errorHandlers;
293
403
  private closeHandlers;
404
+ private isClosed;
405
+ private wsMessageHandler;
406
+ private wsErrorHandler;
407
+ private wsCloseHandler;
294
408
  constructor(wsClient: WSClient, topic: string);
295
- onMessage(handler: MessageHandler): () => boolean;
296
- onError(handler: ErrorHandler): () => boolean;
297
- onClose(handler: CloseHandler): () => boolean;
409
+ /**
410
+ * Register message handler
411
+ */
412
+ onMessage(handler: MessageHandler): () => void;
413
+ /**
414
+ * Register error handler
415
+ */
416
+ onError(handler: ErrorHandler): () => void;
417
+ /**
418
+ * Register close handler
419
+ */
420
+ onClose(handler: CloseHandler): () => void;
421
+ /**
422
+ * Close subscription and underlying WebSocket
423
+ */
298
424
  close(): void;
425
+ /**
426
+ * Check if subscription is active
427
+ */
299
428
  isConnected(): boolean;
300
429
  }
301
430
 
@@ -305,9 +434,23 @@ interface PeerInfo {
305
434
  lastSeen?: string;
306
435
  }
307
436
  interface NetworkStatus {
308
- healthy: boolean;
309
- peers: number;
310
- uptime?: number;
437
+ node_id: string;
438
+ connected: boolean;
439
+ peer_count: number;
440
+ database_size: number;
441
+ uptime: number;
442
+ }
443
+ interface ProxyRequest {
444
+ url: string;
445
+ method: string;
446
+ headers?: Record<string, string>;
447
+ body?: string;
448
+ }
449
+ interface ProxyResponse {
450
+ status_code: number;
451
+ headers: Record<string, string>;
452
+ body: string;
453
+ error?: string;
311
454
  }
312
455
  declare class NetworkClient {
313
456
  private httpClient;
@@ -332,6 +475,224 @@ declare class NetworkClient {
332
475
  * Disconnect from a peer.
333
476
  */
334
477
  disconnect(peerId: string): Promise<void>;
478
+ /**
479
+ * Proxy an HTTP request through the Anyone network.
480
+ * Requires authentication (API key or JWT).
481
+ *
482
+ * @param request - The proxy request configuration
483
+ * @returns The proxied response
484
+ * @throws {SDKError} If the Anyone proxy is not available or the request fails
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * const response = await client.network.proxyAnon({
489
+ * url: 'https://api.example.com/data',
490
+ * method: 'GET',
491
+ * headers: {
492
+ * 'Accept': 'application/json'
493
+ * }
494
+ * });
495
+ *
496
+ * console.log(response.status_code); // 200
497
+ * console.log(response.body); // Response data
498
+ * ```
499
+ */
500
+ proxyAnon(request: ProxyRequest): Promise<ProxyResponse>;
501
+ }
502
+
503
+ interface CacheGetRequest {
504
+ dmap: string;
505
+ key: string;
506
+ }
507
+ interface CacheGetResponse {
508
+ key: string;
509
+ value: any;
510
+ dmap: string;
511
+ }
512
+ interface CachePutRequest {
513
+ dmap: string;
514
+ key: string;
515
+ value: any;
516
+ ttl?: string;
517
+ }
518
+ interface CachePutResponse {
519
+ status: string;
520
+ key: string;
521
+ dmap: string;
522
+ }
523
+ interface CacheDeleteRequest {
524
+ dmap: string;
525
+ key: string;
526
+ }
527
+ interface CacheDeleteResponse {
528
+ status: string;
529
+ key: string;
530
+ dmap: string;
531
+ }
532
+ interface CacheMultiGetRequest {
533
+ dmap: string;
534
+ keys: string[];
535
+ }
536
+ interface CacheMultiGetResponse {
537
+ results: Array<{
538
+ key: string;
539
+ value: any;
540
+ }>;
541
+ dmap: string;
542
+ }
543
+ interface CacheScanRequest {
544
+ dmap: string;
545
+ match?: string;
546
+ }
547
+ interface CacheScanResponse {
548
+ keys: string[];
549
+ count: number;
550
+ dmap: string;
551
+ }
552
+ interface CacheHealthResponse {
553
+ status: string;
554
+ service: string;
555
+ }
556
+ declare class CacheClient {
557
+ private httpClient;
558
+ constructor(httpClient: HttpClient);
559
+ /**
560
+ * Check cache service health
561
+ */
562
+ health(): Promise<CacheHealthResponse>;
563
+ /**
564
+ * Get a value from cache
565
+ * Returns null if the key is not found (cache miss/expired), which is normal behavior
566
+ */
567
+ get(dmap: string, key: string): Promise<CacheGetResponse | null>;
568
+ /**
569
+ * Put a value into cache
570
+ */
571
+ put(dmap: string, key: string, value: any, ttl?: string): Promise<CachePutResponse>;
572
+ /**
573
+ * Delete a value from cache
574
+ */
575
+ delete(dmap: string, key: string): Promise<CacheDeleteResponse>;
576
+ /**
577
+ * Get multiple values from cache in a single request
578
+ * Returns a map of key -> value (or null if not found)
579
+ * Gracefully handles 404 errors (endpoint not implemented) by returning empty results
580
+ */
581
+ multiGet(dmap: string, keys: string[]): Promise<Map<string, any | null>>;
582
+ /**
583
+ * Scan keys in a distributed map, optionally matching a regex pattern
584
+ */
585
+ scan(dmap: string, match?: string): Promise<CacheScanResponse>;
586
+ }
587
+
588
+ interface StorageUploadResponse {
589
+ cid: string;
590
+ name: string;
591
+ size: number;
592
+ }
593
+ interface StoragePinRequest {
594
+ cid: string;
595
+ name?: string;
596
+ }
597
+ interface StoragePinResponse {
598
+ cid: string;
599
+ name: string;
600
+ }
601
+ interface StorageStatus {
602
+ cid: string;
603
+ name: string;
604
+ status: string;
605
+ replication_min: number;
606
+ replication_max: number;
607
+ replication_factor: number;
608
+ peers: string[];
609
+ error?: string;
610
+ }
611
+ declare class StorageClient {
612
+ private httpClient;
613
+ constructor(httpClient: HttpClient);
614
+ /**
615
+ * Upload content to IPFS and optionally pin it.
616
+ * Supports both File objects (browser) and Buffer/ReadableStream (Node.js).
617
+ *
618
+ * @param file - File to upload (File, Blob, or Buffer)
619
+ * @param name - Optional filename
620
+ * @param options - Optional upload options
621
+ * @param options.pin - Whether to pin the content (default: true). Pinning happens asynchronously on the backend.
622
+ * @returns Upload result with CID
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * // Browser
627
+ * const fileInput = document.querySelector('input[type="file"]');
628
+ * const file = fileInput.files[0];
629
+ * const result = await client.storage.upload(file, file.name);
630
+ * console.log(result.cid);
631
+ *
632
+ * // Node.js
633
+ * const fs = require('fs');
634
+ * const fileBuffer = fs.readFileSync('image.jpg');
635
+ * const result = await client.storage.upload(fileBuffer, 'image.jpg', { pin: true });
636
+ * ```
637
+ */
638
+ upload(file: File | Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>, name?: string, options?: {
639
+ pin?: boolean;
640
+ }): Promise<StorageUploadResponse>;
641
+ /**
642
+ * Pin an existing CID
643
+ *
644
+ * @param cid - Content ID to pin
645
+ * @param name - Optional name for the pin
646
+ * @returns Pin result
647
+ */
648
+ pin(cid: string, name?: string): Promise<StoragePinResponse>;
649
+ /**
650
+ * Get the pin status for a CID
651
+ *
652
+ * @param cid - Content ID to check
653
+ * @returns Pin status information
654
+ */
655
+ status(cid: string): Promise<StorageStatus>;
656
+ /**
657
+ * Retrieve content from IPFS by CID
658
+ *
659
+ * @param cid - Content ID to retrieve
660
+ * @returns ReadableStream of the content
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * const stream = await client.storage.get(cid);
665
+ * const reader = stream.getReader();
666
+ * while (true) {
667
+ * const { done, value } = await reader.read();
668
+ * if (done) break;
669
+ * // Process chunk
670
+ * }
671
+ * ```
672
+ */
673
+ get(cid: string): Promise<ReadableStream<Uint8Array>>;
674
+ /**
675
+ * Retrieve content from IPFS by CID and return the full Response object
676
+ * Useful when you need access to response headers (e.g., content-length)
677
+ *
678
+ * @param cid - Content ID to retrieve
679
+ * @returns Response object with body stream and headers
680
+ *
681
+ * @example
682
+ * ```ts
683
+ * const response = await client.storage.getBinary(cid);
684
+ * const contentLength = response.headers.get('content-length');
685
+ * const reader = response.body.getReader();
686
+ * // ... read stream
687
+ * ```
688
+ */
689
+ getBinary(cid: string): Promise<Response>;
690
+ /**
691
+ * Unpin a CID
692
+ *
693
+ * @param cid - Content ID to unpin
694
+ */
695
+ unpin(cid: string): Promise<void>;
335
696
  }
336
697
 
337
698
  declare class SDKError extends Error {
@@ -361,7 +722,9 @@ interface Client {
361
722
  db: DBClient;
362
723
  pubsub: PubSubClient;
363
724
  network: NetworkClient;
725
+ cache: CacheClient;
726
+ storage: StorageClient;
364
727
  }
365
728
  declare function createClient(config: ClientConfig): Client;
366
729
 
367
- export { AuthClient, type AuthConfig, 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, PubSubClient, QueryBuilder, type QueryResponse, Repository, SDKError, type SelectOptions, type StorageAdapter, Subscription, type TransactionOp, type TransactionRequest, WSClient, type WhoAmI, createClient, extractPrimaryKey, extractTableName };
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 };