@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/LICENSE +1 -1
- package/README.md +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +26 -3
- package/src/auth/index.ts +3 -0
- package/src/cache/index.ts +14 -0
- package/src/core/http.ts +8 -2
- package/src/core/index.ts +10 -0
- package/src/core/interfaces/IAuthStrategy.ts +28 -0
- package/src/core/interfaces/IHttpTransport.ts +73 -0
- package/src/core/interfaces/IRetryPolicy.ts +20 -0
- package/src/core/interfaces/IWebSocketClient.ts +60 -0
- package/src/core/interfaces/index.ts +4 -0
- package/src/core/transport/AuthHeaderStrategy.ts +108 -0
- package/src/core/transport/RequestLogger.ts +116 -0
- package/src/core/transport/RequestRetryPolicy.ts +53 -0
- package/src/core/transport/TLSConfiguration.ts +53 -0
- package/src/core/transport/index.ts +4 -0
- package/src/db/index.ts +13 -0
- package/src/functions/index.ts +2 -0
- package/src/index.ts +1 -0
- package/src/network/index.ts +7 -0
- package/src/pubsub/index.ts +12 -0
- package/src/storage/index.ts +7 -0
- package/src/utils/codec.ts +68 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/platform.ts +44 -0
- package/src/utils/retry.ts +58 -0
package/package.json
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@debros/network-ts-sdk",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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,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,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
|
+
}
|
package/src/db/index.ts
ADDED
|
@@ -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";
|
package/src/index.ts
CHANGED