@debros/network-ts-sdk 0.4.3 → 0.6.0

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/package.json CHANGED
@@ -1,15 +1,36 @@
1
1
  {
2
2
  "name": "@debros/network-ts-sdk",
3
- "version": "0.4.3",
4
- "description": "TypeScript SDK for DeBros Network Gateway",
3
+ "version": "0.6.0",
4
+ "description": "TypeScript SDK for DeBros Network Gateway - Database, PubSub, Cache, Storage, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "license": "MIT",
9
9
  "author": "DeBrosOfficial",
10
+ "keywords": [
11
+ "debros",
12
+ "network",
13
+ "sdk",
14
+ "typescript",
15
+ "database",
16
+ "rqlite",
17
+ "pubsub",
18
+ "websocket",
19
+ "cache",
20
+ "olric",
21
+ "ipfs",
22
+ "storage",
23
+ "wasm",
24
+ "serverless",
25
+ "distributed",
26
+ "gateway"
27
+ ],
10
28
  "repository": {
11
29
  "type": "git",
12
- "url": "https://github.com/DeBrosOfficial/network-ts-sdk/tree/v0.0.1"
30
+ "url": "https://github.com/DeBrosOfficial/network-ts-sdk"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/DeBrosOfficial/network-ts-sdk/issues"
13
34
  },
14
35
  "exports": {
15
36
  ".": {
@@ -38,9 +59,11 @@
38
59
  "@types/node": "^20.0.0",
39
60
  "@typescript-eslint/eslint-plugin": "^6.0.0",
40
61
  "@typescript-eslint/parser": "^6.0.0",
62
+ "@vitest/coverage-v8": "^1.0.0",
41
63
  "dotenv": "^17.2.3",
42
64
  "eslint": "^8.0.0",
43
65
  "tsup": "^8.0.0",
66
+ "typedoc": "^0.25.0",
44
67
  "typescript": "^5.3.0",
45
68
  "vitest": "^1.0.0"
46
69
  },
@@ -0,0 +1,3 @@
1
+ export { AuthClient } from "./client";
2
+ export type { AuthConfig, WhoAmI, StorageAdapter } from "./types";
3
+ export { MemoryStorage, LocalStorageAdapter } from "./types";
@@ -0,0 +1,14 @@
1
+ export { CacheClient } from "./client";
2
+ export type {
3
+ CacheGetRequest,
4
+ CacheGetResponse,
5
+ CachePutRequest,
6
+ CachePutResponse,
7
+ CacheDeleteRequest,
8
+ CacheDeleteResponse,
9
+ CacheMultiGetRequest,
10
+ CacheMultiGetResponse,
11
+ CacheScanRequest,
12
+ CacheScanResponse,
13
+ CacheHealthResponse,
14
+ } from "./client";
package/src/core/http.ts CHANGED
@@ -25,6 +25,10 @@ export interface HttpClientConfig {
25
25
  maxRetries?: number;
26
26
  retryDelayMs?: number;
27
27
  fetch?: typeof fetch;
28
+ /**
29
+ * Enable debug logging (includes full SQL queries and args). Default: false
30
+ */
31
+ debug?: boolean;
28
32
  /**
29
33
  * Callback invoked on network errors (after all retries exhausted).
30
34
  * Use this to trigger gateway failover at the application layer.
@@ -63,6 +67,7 @@ export class HttpClient {
63
67
  private fetch: typeof fetch;
64
68
  private apiKey?: string;
65
69
  private jwt?: string;
70
+ private debug: boolean;
66
71
  private onNetworkError?: NetworkErrorCallback;
67
72
 
68
73
  constructor(config: HttpClientConfig) {
@@ -72,6 +77,7 @@ export class HttpClient {
72
77
  this.retryDelayMs = config.retryDelayMs ?? 1000;
73
78
  // Use provided fetch or create one with proper TLS configuration for staging certificates
74
79
  this.fetch = config.fetch ?? createFetchWithTLSConfig();
80
+ this.debug = config.debug ?? false;
75
81
  this.onNetworkError = config.onNetworkError;
76
82
  }
77
83
 
@@ -256,7 +262,7 @@ export class HttpClient {
256
262
  const logMessage = `[HttpClient] ${method} ${path} completed in ${duration.toFixed(
257
263
  2
258
264
  )}ms`;
259
- if (queryDetails) {
265
+ if (queryDetails && this.debug) {
260
266
  console.log(logMessage);
261
267
  console.log(`[HttpClient] ${queryDetails}`);
262
268
  } else {
@@ -286,7 +292,7 @@ export class HttpClient {
286
292
  2
287
293
  )}ms:`;
288
294
  console.error(errorMessage, error);
289
- if (queryDetails) {
295
+ if (queryDetails && this.debug) {
290
296
  console.error(`[HttpClient] ${queryDetails}`);
291
297
  }
292
298
  }
@@ -0,0 +1,10 @@
1
+ export { HttpClient, type HttpClientConfig, type NetworkErrorCallback, type NetworkErrorContext } from "./http";
2
+ export { WSClient, type WSClientConfig } from "./ws";
3
+ export type { IHttpTransport, RequestOptions } from "./interfaces/IHttpTransport";
4
+ export type { IWebSocketClient } from "./interfaces/IWebSocketClient";
5
+ export type { IAuthStrategy, RequestContext } from "./interfaces/IAuthStrategy";
6
+ export type { IRetryPolicy } from "./interfaces/IRetryPolicy";
7
+ export { PathBasedAuthStrategy } from "./transport/AuthHeaderStrategy";
8
+ export { ExponentialBackoffRetryPolicy } from "./transport/RequestRetryPolicy";
9
+ export { RequestLogger } from "./transport/RequestLogger";
10
+ export { TLSConfiguration } from "./transport/TLSConfiguration";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Request context for authentication
3
+ */
4
+ export interface RequestContext {
5
+ path: string;
6
+ method: string;
7
+ }
8
+
9
+ /**
10
+ * Authentication strategy interface
11
+ * Provides abstraction for different authentication header strategies
12
+ */
13
+ export interface IAuthStrategy {
14
+ /**
15
+ * Get authentication headers for a request
16
+ */
17
+ getHeaders(context: RequestContext): Record<string, string>;
18
+
19
+ /**
20
+ * Set API key
21
+ */
22
+ setApiKey(apiKey?: string): void;
23
+
24
+ /**
25
+ * Set JWT token
26
+ */
27
+ setJwt(jwt?: string): void;
28
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * HTTP Request options
3
+ */
4
+ export interface RequestOptions {
5
+ headers?: Record<string, string>;
6
+ query?: Record<string, string | number | boolean>;
7
+ timeout?: number;
8
+ }
9
+
10
+ /**
11
+ * HTTP Transport abstraction interface
12
+ * Provides a testable abstraction layer for HTTP operations
13
+ */
14
+ export interface IHttpTransport {
15
+ /**
16
+ * Perform GET request
17
+ */
18
+ get<T = any>(path: string, options?: RequestOptions): Promise<T>;
19
+
20
+ /**
21
+ * Perform POST request
22
+ */
23
+ post<T = any>(path: string, body?: any, options?: RequestOptions): Promise<T>;
24
+
25
+ /**
26
+ * Perform PUT request
27
+ */
28
+ put<T = any>(path: string, body?: any, options?: RequestOptions): Promise<T>;
29
+
30
+ /**
31
+ * Perform DELETE request
32
+ */
33
+ delete<T = any>(path: string, options?: RequestOptions): Promise<T>;
34
+
35
+ /**
36
+ * Upload file using multipart/form-data
37
+ */
38
+ uploadFile<T = any>(
39
+ path: string,
40
+ formData: FormData,
41
+ options?: { timeout?: number }
42
+ ): Promise<T>;
43
+
44
+ /**
45
+ * Get binary response (returns Response object for streaming)
46
+ */
47
+ getBinary(path: string): Promise<Response>;
48
+
49
+ /**
50
+ * Get base URL
51
+ */
52
+ getBaseURL(): string;
53
+
54
+ /**
55
+ * Get API key
56
+ */
57
+ getApiKey(): string | undefined;
58
+
59
+ /**
60
+ * Get current token (JWT or API key)
61
+ */
62
+ getToken(): string | undefined;
63
+
64
+ /**
65
+ * Set API key for authentication
66
+ */
67
+ setApiKey(apiKey?: string): void;
68
+
69
+ /**
70
+ * Set JWT token for authentication
71
+ */
72
+ setJwt(jwt?: string): void;
73
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Retry policy interface
3
+ * Provides abstraction for retry logic and backoff strategies
4
+ */
5
+ export interface IRetryPolicy {
6
+ /**
7
+ * Determine if request should be retried
8
+ */
9
+ shouldRetry(error: any, attempt: number): boolean;
10
+
11
+ /**
12
+ * Get delay before next retry attempt (in milliseconds)
13
+ */
14
+ getDelay(attempt: number): number;
15
+
16
+ /**
17
+ * Get maximum number of retry attempts
18
+ */
19
+ getMaxRetries(): number;
20
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * WebSocket Client abstraction interface
3
+ * Provides a testable abstraction layer for WebSocket operations
4
+ */
5
+ export interface IWebSocketClient {
6
+ /**
7
+ * Connect to WebSocket server
8
+ */
9
+ connect(): Promise<void>;
10
+
11
+ /**
12
+ * Close WebSocket connection
13
+ */
14
+ close(): void;
15
+
16
+ /**
17
+ * Send data through WebSocket
18
+ */
19
+ send(data: string): void;
20
+
21
+ /**
22
+ * Register message handler
23
+ */
24
+ onMessage(handler: (data: string) => void): void;
25
+
26
+ /**
27
+ * Unregister message handler
28
+ */
29
+ offMessage(handler: (data: string) => void): void;
30
+
31
+ /**
32
+ * Register error handler
33
+ */
34
+ onError(handler: (error: Error) => void): void;
35
+
36
+ /**
37
+ * Unregister error handler
38
+ */
39
+ offError(handler: (error: Error) => void): void;
40
+
41
+ /**
42
+ * Register close handler
43
+ */
44
+ onClose(handler: () => void): void;
45
+
46
+ /**
47
+ * Unregister close handler
48
+ */
49
+ offClose(handler: () => void): void;
50
+
51
+ /**
52
+ * Check if WebSocket is connected
53
+ */
54
+ isConnected(): boolean;
55
+
56
+ /**
57
+ * Get WebSocket URL
58
+ */
59
+ get url(): string;
60
+ }
@@ -0,0 +1,4 @@
1
+ export type { IHttpTransport, RequestOptions } from "./IHttpTransport";
2
+ export type { IWebSocketClient } from "./IWebSocketClient";
3
+ export type { IAuthStrategy, RequestContext } from "./IAuthStrategy";
4
+ export type { IRetryPolicy } from "./IRetryPolicy";
@@ -0,0 +1,108 @@
1
+ import type { IAuthStrategy, RequestContext } from "../interfaces/IAuthStrategy";
2
+
3
+ /**
4
+ * Authentication type for different operations
5
+ */
6
+ type AuthType = "api-key-only" | "api-key-preferred" | "jwt-preferred" | "both";
7
+
8
+ /**
9
+ * Path-based authentication strategy
10
+ * Determines which auth credentials to use based on the request path
11
+ */
12
+ export class PathBasedAuthStrategy implements IAuthStrategy {
13
+ private apiKey?: string;
14
+ private jwt?: string;
15
+
16
+ /**
17
+ * Mapping of path patterns to auth types
18
+ */
19
+ private readonly authRules: Array<{ pattern: string; type: AuthType }> = [
20
+ // Database, PubSub, Proxy, Cache: prefer API key
21
+ { pattern: "/v1/rqlite/", type: "api-key-only" },
22
+ { pattern: "/v1/pubsub/", type: "api-key-only" },
23
+ { pattern: "/v1/proxy/", type: "api-key-only" },
24
+ { pattern: "/v1/cache/", type: "api-key-only" },
25
+ // Auth operations: prefer API key
26
+ { pattern: "/v1/auth/", type: "api-key-preferred" },
27
+ ];
28
+
29
+ constructor(apiKey?: string, jwt?: string) {
30
+ this.apiKey = apiKey;
31
+ this.jwt = jwt;
32
+ }
33
+
34
+ /**
35
+ * Get authentication headers for a request
36
+ */
37
+ getHeaders(context: RequestContext): Record<string, string> {
38
+ const headers: Record<string, string> = {};
39
+ const authType = this.detectAuthType(context.path);
40
+
41
+ switch (authType) {
42
+ case "api-key-only":
43
+ if (this.apiKey) {
44
+ headers["X-API-Key"] = this.apiKey;
45
+ } else if (this.jwt) {
46
+ // Fallback to JWT if no API key
47
+ headers["Authorization"] = `Bearer ${this.jwt}`;
48
+ }
49
+ break;
50
+
51
+ case "api-key-preferred":
52
+ if (this.apiKey) {
53
+ headers["X-API-Key"] = this.apiKey;
54
+ }
55
+ if (this.jwt) {
56
+ headers["Authorization"] = `Bearer ${this.jwt}`;
57
+ }
58
+ break;
59
+
60
+ case "jwt-preferred":
61
+ if (this.jwt) {
62
+ headers["Authorization"] = `Bearer ${this.jwt}`;
63
+ }
64
+ if (this.apiKey) {
65
+ headers["X-API-Key"] = this.apiKey;
66
+ }
67
+ break;
68
+
69
+ case "both":
70
+ if (this.jwt) {
71
+ headers["Authorization"] = `Bearer ${this.jwt}`;
72
+ }
73
+ if (this.apiKey) {
74
+ headers["X-API-Key"] = this.apiKey;
75
+ }
76
+ break;
77
+ }
78
+
79
+ return headers;
80
+ }
81
+
82
+ /**
83
+ * Set API key
84
+ */
85
+ setApiKey(apiKey?: string): void {
86
+ this.apiKey = apiKey;
87
+ }
88
+
89
+ /**
90
+ * Set JWT token
91
+ */
92
+ setJwt(jwt?: string): void {
93
+ this.jwt = jwt;
94
+ }
95
+
96
+ /**
97
+ * Detect auth type based on path
98
+ */
99
+ private detectAuthType(path: string): AuthType {
100
+ for (const rule of this.authRules) {
101
+ if (path.includes(rule.pattern)) {
102
+ return rule.type;
103
+ }
104
+ }
105
+ // Default: send both if available
106
+ return "both";
107
+ }
108
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Request logger for debugging HTTP operations
3
+ */
4
+ export class RequestLogger {
5
+ private readonly debug: boolean;
6
+
7
+ constructor(debug: boolean = false) {
8
+ this.debug = debug;
9
+ }
10
+
11
+ /**
12
+ * Log successful request
13
+ */
14
+ logSuccess(
15
+ method: string,
16
+ path: string,
17
+ duration: number,
18
+ queryDetails?: string
19
+ ): void {
20
+ if (typeof console === "undefined") return;
21
+
22
+ const logMessage = `[HttpClient] ${method} ${path} completed in ${duration.toFixed(2)}ms`;
23
+
24
+ if (queryDetails && this.debug) {
25
+ console.log(logMessage);
26
+ console.log(`[HttpClient] ${queryDetails}`);
27
+ } else {
28
+ console.log(logMessage);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Log failed request
34
+ */
35
+ logError(
36
+ method: string,
37
+ path: string,
38
+ duration: number,
39
+ error: any,
40
+ queryDetails?: string
41
+ ): void {
42
+ if (typeof console === "undefined") return;
43
+
44
+ // Special handling for 404 on find-one (expected behavior)
45
+ const is404FindOne =
46
+ path === "/v1/rqlite/find-one" &&
47
+ error?.httpStatus === 404;
48
+
49
+ if (is404FindOne) {
50
+ console.warn(
51
+ `[HttpClient] ${method} ${path} returned 404 after ${duration.toFixed(2)}ms (expected for optional lookups)`
52
+ );
53
+ return;
54
+ }
55
+
56
+ const errorMessage = `[HttpClient] ${method} ${path} failed after ${duration.toFixed(2)}ms:`;
57
+ console.error(errorMessage, error);
58
+
59
+ if (queryDetails && this.debug) {
60
+ console.error(`[HttpClient] ${queryDetails}`);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Extract query details from request for logging
66
+ */
67
+ extractQueryDetails(path: string, body?: any): string | null {
68
+ if (!this.debug) return null;
69
+
70
+ const isRqliteOperation = path.includes("/v1/rqlite/");
71
+ if (!isRqliteOperation || !body) return null;
72
+
73
+ try {
74
+ const parsedBody = typeof body === "string" ? JSON.parse(body) : body;
75
+
76
+ // Direct SQL query
77
+ if (parsedBody.sql) {
78
+ let details = `SQL: ${parsedBody.sql}`;
79
+ if (parsedBody.args && parsedBody.args.length > 0) {
80
+ details += ` | Args: [${parsedBody.args
81
+ .map((a: any) => (typeof a === "string" ? `"${a}"` : a))
82
+ .join(", ")}]`;
83
+ }
84
+ return details;
85
+ }
86
+
87
+ // Table-based query
88
+ if (parsedBody.table) {
89
+ let details = `Table: ${parsedBody.table}`;
90
+ if (parsedBody.criteria && Object.keys(parsedBody.criteria).length > 0) {
91
+ details += ` | Criteria: ${JSON.stringify(parsedBody.criteria)}`;
92
+ }
93
+ if (parsedBody.options) {
94
+ details += ` | Options: ${JSON.stringify(parsedBody.options)}`;
95
+ }
96
+ if (parsedBody.select) {
97
+ details += ` | Select: ${JSON.stringify(parsedBody.select)}`;
98
+ }
99
+ if (parsedBody.where) {
100
+ details += ` | Where: ${JSON.stringify(parsedBody.where)}`;
101
+ }
102
+ if (parsedBody.limit) {
103
+ details += ` | Limit: ${parsedBody.limit}`;
104
+ }
105
+ if (parsedBody.offset) {
106
+ details += ` | Offset: ${parsedBody.offset}`;
107
+ }
108
+ return details;
109
+ }
110
+ } catch {
111
+ // Failed to parse, ignore
112
+ }
113
+
114
+ return null;
115
+ }
116
+ }
@@ -0,0 +1,53 @@
1
+ import type { IRetryPolicy } from "../interfaces/IRetryPolicy";
2
+ import { SDKError } from "../../errors";
3
+
4
+ /**
5
+ * Exponential backoff retry policy
6
+ * Retries failed requests with increasing delays
7
+ */
8
+ export class ExponentialBackoffRetryPolicy implements IRetryPolicy {
9
+ private readonly maxRetries: number;
10
+ private readonly baseDelayMs: number;
11
+
12
+ /**
13
+ * HTTP status codes that should trigger a retry
14
+ */
15
+ private readonly retryableStatusCodes = [408, 429, 500, 502, 503, 504];
16
+
17
+ constructor(maxRetries: number = 3, baseDelayMs: number = 1000) {
18
+ this.maxRetries = maxRetries;
19
+ this.baseDelayMs = baseDelayMs;
20
+ }
21
+
22
+ /**
23
+ * Determine if request should be retried
24
+ */
25
+ shouldRetry(error: any, attempt: number): boolean {
26
+ // Don't retry if max attempts reached
27
+ if (attempt >= this.maxRetries) {
28
+ return false;
29
+ }
30
+
31
+ // Retry on retryable HTTP errors
32
+ if (error instanceof SDKError) {
33
+ return this.retryableStatusCodes.includes(error.httpStatus);
34
+ }
35
+
36
+ // Don't retry other errors
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Get delay before next retry (exponential backoff)
42
+ */
43
+ getDelay(attempt: number): number {
44
+ return this.baseDelayMs * (attempt + 1);
45
+ }
46
+
47
+ /**
48
+ * Get maximum number of retry attempts
49
+ */
50
+ getMaxRetries(): number {
51
+ return this.maxRetries;
52
+ }
53
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TLS Configuration for development/staging environments
3
+ *
4
+ * WARNING: Only use this in development/testing environments!
5
+ * DO NOT disable certificate validation in production.
6
+ */
7
+ export class TLSConfiguration {
8
+ /**
9
+ * Create fetch function with proper TLS configuration
10
+ */
11
+ static createFetchWithTLSConfig(): typeof fetch {
12
+ // Only allow insecure TLS in development
13
+ if (this.shouldAllowInsecure()) {
14
+ this.configureInsecureTLS();
15
+ }
16
+
17
+ return globalThis.fetch;
18
+ }
19
+
20
+ /**
21
+ * Check if insecure TLS should be allowed
22
+ */
23
+ private static shouldAllowInsecure(): boolean {
24
+ // Check if we're in Node.js environment
25
+ if (typeof process === "undefined" || !process.versions?.node) {
26
+ return false;
27
+ }
28
+
29
+ // Only allow in non-production with explicit flag
30
+ const isProduction = process.env.NODE_ENV === "production";
31
+ const allowInsecure = process.env.DEBROS_ALLOW_INSECURE_TLS === "true";
32
+
33
+ return !isProduction && allowInsecure;
34
+ }
35
+
36
+ /**
37
+ * Configure Node.js to allow insecure TLS
38
+ * WARNING: Only call in development!
39
+ */
40
+ private static configureInsecureTLS(): void {
41
+ if (typeof process !== "undefined" && process.env) {
42
+ // Allow self-signed/staging certificates for development
43
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
44
+
45
+ if (typeof console !== "undefined") {
46
+ console.warn(
47
+ "[TLSConfiguration] WARNING: TLS certificate validation disabled for development. " +
48
+ "DO NOT use in production!"
49
+ );
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,4 @@
1
+ export { PathBasedAuthStrategy } from "./AuthHeaderStrategy";
2
+ export { ExponentialBackoffRetryPolicy } from "./RequestRetryPolicy";
3
+ export { RequestLogger } from "./RequestLogger";
4
+ export { TLSConfiguration } from "./TLSConfiguration";
@@ -0,0 +1,13 @@
1
+ export { DBClient } from "./client";
2
+ export { QueryBuilder } from "./qb";
3
+ export { Repository } from "./repository";
4
+ export type {
5
+ Entity,
6
+ QueryResponse,
7
+ TransactionOp,
8
+ TransactionRequest,
9
+ SelectOptions,
10
+ FindOptions,
11
+ ColumnDefinition,
12
+ } from "./types";
13
+ export { extractTableName, extractPrimaryKey } from "./types";
@@ -0,0 +1,2 @@
1
+ export { FunctionsClient, type FunctionsClientConfig } from "./client";
2
+ export type { FunctionResponse, SuccessResponse } from "./types";
package/src/index.ts CHANGED
@@ -43,6 +43,7 @@ export function createClient(config: ClientConfig): Client {
43
43
  timeout: config.timeout,
44
44
  maxRetries: config.maxRetries,
45
45
  retryDelayMs: config.retryDelayMs,
46
+ debug: config.debug,
46
47
  fetch: config.fetch,
47
48
  onNetworkError: config.onNetworkError,
48
49
  });
@@ -0,0 +1,7 @@
1
+ export { NetworkClient } from "./client";
2
+ export type {
3
+ PeerInfo,
4
+ NetworkStatus,
5
+ ProxyRequest,
6
+ ProxyResponse,
7
+ } from "./client";