@debros/network-ts-sdk 0.1.4
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 +21 -0
- package/README.md +329 -0
- package/dist/index.d.ts +367 -0
- package/dist/index.js +826 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/src/auth/client.ts +97 -0
- package/src/auth/types.ts +62 -0
- package/src/core/http.ts +158 -0
- package/src/core/ws.ts +182 -0
- package/src/db/client.ts +126 -0
- package/src/db/qb.ts +111 -0
- package/src/db/repository.ts +124 -0
- package/src/db/types.ts +67 -0
- package/src/errors.ts +38 -0
- package/src/index.ts +82 -0
- package/src/network/client.ts +65 -0
- package/src/pubsub/client.ts +142 -0
package/src/db/client.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { HttpClient } from "../core/http";
|
|
2
|
+
import { QueryBuilder } from "./qb";
|
|
3
|
+
import { Repository } from "./repository";
|
|
4
|
+
import {
|
|
5
|
+
QueryResponse,
|
|
6
|
+
TransactionOp,
|
|
7
|
+
TransactionRequest,
|
|
8
|
+
Entity,
|
|
9
|
+
FindOptions,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
export class DBClient {
|
|
13
|
+
private httpClient: HttpClient;
|
|
14
|
+
|
|
15
|
+
constructor(httpClient: HttpClient) {
|
|
16
|
+
this.httpClient = httpClient;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Execute a write/DDL SQL statement.
|
|
21
|
+
*/
|
|
22
|
+
async exec(
|
|
23
|
+
sql: string,
|
|
24
|
+
args: any[] = []
|
|
25
|
+
): Promise<{ rows_affected: number; last_insert_id?: number }> {
|
|
26
|
+
return this.httpClient.post("/v1/rqlite/exec", { sql, args });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execute a SELECT query.
|
|
31
|
+
*/
|
|
32
|
+
async query<T = any>(sql: string, args: any[] = []): Promise<T[]> {
|
|
33
|
+
const response = await this.httpClient.post<QueryResponse>(
|
|
34
|
+
"/v1/rqlite/query",
|
|
35
|
+
{ sql, args }
|
|
36
|
+
);
|
|
37
|
+
return response.items || [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Find rows with map-based criteria.
|
|
42
|
+
*/
|
|
43
|
+
async find<T = any>(
|
|
44
|
+
table: string,
|
|
45
|
+
criteria: Record<string, any> = {},
|
|
46
|
+
options: FindOptions = {}
|
|
47
|
+
): Promise<T[]> {
|
|
48
|
+
const response = await this.httpClient.post<QueryResponse>(
|
|
49
|
+
"/v1/rqlite/find",
|
|
50
|
+
{
|
|
51
|
+
table,
|
|
52
|
+
criteria,
|
|
53
|
+
options,
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
return response.items || [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Find a single row with map-based criteria.
|
|
61
|
+
*/
|
|
62
|
+
async findOne<T = any>(
|
|
63
|
+
table: string,
|
|
64
|
+
criteria: Record<string, any>
|
|
65
|
+
): Promise<T | null> {
|
|
66
|
+
return this.httpClient.post<T | null>("/v1/rqlite/find-one", {
|
|
67
|
+
table,
|
|
68
|
+
criteria,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a fluent QueryBuilder for complex SELECT queries.
|
|
74
|
+
*/
|
|
75
|
+
createQueryBuilder(table: string): QueryBuilder {
|
|
76
|
+
return new QueryBuilder(this.httpClient, table);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a Repository for entity-based operations.
|
|
81
|
+
*/
|
|
82
|
+
repository<T extends Record<string, any>>(
|
|
83
|
+
tableName: string,
|
|
84
|
+
primaryKey = "id"
|
|
85
|
+
): Repository<T> {
|
|
86
|
+
return new Repository(this.httpClient, tableName, primaryKey);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Execute multiple operations atomically.
|
|
91
|
+
*/
|
|
92
|
+
async transaction(
|
|
93
|
+
ops: TransactionOp[],
|
|
94
|
+
returnResults = true
|
|
95
|
+
): Promise<any[]> {
|
|
96
|
+
const response = await this.httpClient.post<{ results?: any[] }>(
|
|
97
|
+
"/v1/rqlite/transaction",
|
|
98
|
+
{
|
|
99
|
+
ops,
|
|
100
|
+
return_results: returnResults,
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
return response.results || [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a table from DDL SQL.
|
|
108
|
+
*/
|
|
109
|
+
async createTable(schema: string): Promise<void> {
|
|
110
|
+
await this.httpClient.post("/v1/rqlite/create-table", { schema });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Drop a table.
|
|
115
|
+
*/
|
|
116
|
+
async dropTable(table: string): Promise<void> {
|
|
117
|
+
await this.httpClient.post("/v1/rqlite/drop-table", { table });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get current database schema.
|
|
122
|
+
*/
|
|
123
|
+
async getSchema(): Promise<any> {
|
|
124
|
+
return this.httpClient.get("/v1/rqlite/schema");
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/db/qb.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { HttpClient } from "../core/http";
|
|
2
|
+
import { SelectOptions, QueryResponse } from "./types";
|
|
3
|
+
|
|
4
|
+
export class QueryBuilder {
|
|
5
|
+
private httpClient: HttpClient;
|
|
6
|
+
private table: string;
|
|
7
|
+
private options: SelectOptions = {};
|
|
8
|
+
|
|
9
|
+
constructor(httpClient: HttpClient, table: string) {
|
|
10
|
+
this.httpClient = httpClient;
|
|
11
|
+
this.table = table;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
select(...columns: string[]): this {
|
|
15
|
+
this.options.select = columns;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
innerJoin(table: string, on: string): this {
|
|
20
|
+
if (!this.options.joins) this.options.joins = [];
|
|
21
|
+
this.options.joins.push({ kind: "INNER", table, on });
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
leftJoin(table: string, on: string): this {
|
|
26
|
+
if (!this.options.joins) this.options.joins = [];
|
|
27
|
+
this.options.joins.push({ kind: "LEFT", table, on });
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
rightJoin(table: string, on: string): this {
|
|
32
|
+
if (!this.options.joins) this.options.joins = [];
|
|
33
|
+
this.options.joins.push({ kind: "RIGHT", table, on });
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
where(expr: string, args?: any[]): this {
|
|
38
|
+
if (!this.options.where) this.options.where = [];
|
|
39
|
+
this.options.where.push({ conj: "AND", expr, args });
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
andWhere(expr: string, args?: any[]): this {
|
|
44
|
+
return this.where(expr, args);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
orWhere(expr: string, args?: any[]): this {
|
|
48
|
+
if (!this.options.where) this.options.where = [];
|
|
49
|
+
this.options.where.push({ conj: "OR", expr, args });
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
groupBy(...columns: string[]): this {
|
|
54
|
+
this.options.group_by = columns;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
orderBy(...columns: string[]): this {
|
|
59
|
+
this.options.order_by = columns;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
limit(n: number): this {
|
|
64
|
+
this.options.limit = n;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
offset(n: number): this {
|
|
69
|
+
this.options.offset = n;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getMany<T = any>(ctx?: any): Promise<T[]> {
|
|
74
|
+
const response = await this.httpClient.post<QueryResponse>(
|
|
75
|
+
"/v1/rqlite/select",
|
|
76
|
+
{
|
|
77
|
+
table: this.table,
|
|
78
|
+
...this.options,
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
return response.items || [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getOne<T = any>(ctx?: any): Promise<T | null> {
|
|
85
|
+
const response = await this.httpClient.post<QueryResponse>(
|
|
86
|
+
"/v1/rqlite/select",
|
|
87
|
+
{
|
|
88
|
+
table: this.table,
|
|
89
|
+
...this.options,
|
|
90
|
+
one: true,
|
|
91
|
+
limit: 1,
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
const items = response.items || [];
|
|
95
|
+
return items.length > 0 ? items[0] : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async count(): Promise<number> {
|
|
99
|
+
const response = await this.httpClient.post<QueryResponse>(
|
|
100
|
+
"/v1/rqlite/select",
|
|
101
|
+
{
|
|
102
|
+
table: this.table,
|
|
103
|
+
select: ["COUNT(*) AS count"],
|
|
104
|
+
where: this.options.where,
|
|
105
|
+
one: true,
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
const items = response.items || [];
|
|
109
|
+
return items.length > 0 ? items[0].count : 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { HttpClient } from "../core/http";
|
|
2
|
+
import { QueryBuilder } from "./qb";
|
|
3
|
+
import { QueryResponse, FindOptions } from "./types";
|
|
4
|
+
import { SDKError } from "../errors";
|
|
5
|
+
|
|
6
|
+
export class Repository<T extends Record<string, any>> {
|
|
7
|
+
private httpClient: HttpClient;
|
|
8
|
+
private tableName: string;
|
|
9
|
+
private primaryKey: string;
|
|
10
|
+
|
|
11
|
+
constructor(httpClient: HttpClient, tableName: string, primaryKey = "id") {
|
|
12
|
+
this.httpClient = httpClient;
|
|
13
|
+
this.tableName = tableName;
|
|
14
|
+
this.primaryKey = primaryKey;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createQueryBuilder(): QueryBuilder {
|
|
18
|
+
return new QueryBuilder(this.httpClient, this.tableName);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async find(
|
|
22
|
+
criteria: Record<string, any> = {},
|
|
23
|
+
options: FindOptions = {}
|
|
24
|
+
): Promise<T[]> {
|
|
25
|
+
const response = await this.httpClient.post<QueryResponse>(
|
|
26
|
+
"/v1/rqlite/find",
|
|
27
|
+
{
|
|
28
|
+
table: this.tableName,
|
|
29
|
+
criteria,
|
|
30
|
+
options,
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
return response.items || [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async findOne(criteria: Record<string, any>): Promise<T | null> {
|
|
37
|
+
try {
|
|
38
|
+
const response = await this.httpClient.post<T | null>(
|
|
39
|
+
"/v1/rqlite/find-one",
|
|
40
|
+
{
|
|
41
|
+
table: this.tableName,
|
|
42
|
+
criteria,
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
return response;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Return null if not found instead of throwing
|
|
48
|
+
if (error instanceof SDKError && error.httpStatus === 404) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async save(entity: T): Promise<T> {
|
|
56
|
+
const pkValue = entity[this.primaryKey];
|
|
57
|
+
|
|
58
|
+
if (!pkValue) {
|
|
59
|
+
// INSERT
|
|
60
|
+
const response = await this.httpClient.post<{
|
|
61
|
+
rows_affected: number;
|
|
62
|
+
last_insert_id: number;
|
|
63
|
+
}>("/v1/rqlite/exec", {
|
|
64
|
+
sql: this.buildInsertSql(entity),
|
|
65
|
+
args: this.buildInsertArgs(entity),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (response.last_insert_id) {
|
|
69
|
+
(entity as any)[this.primaryKey] = response.last_insert_id;
|
|
70
|
+
}
|
|
71
|
+
return entity;
|
|
72
|
+
} else {
|
|
73
|
+
// UPDATE
|
|
74
|
+
await this.httpClient.post("/v1/rqlite/exec", {
|
|
75
|
+
sql: this.buildUpdateSql(entity),
|
|
76
|
+
args: this.buildUpdateArgs(entity),
|
|
77
|
+
});
|
|
78
|
+
return entity;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async remove(entity: T | Record<string, any>): Promise<void> {
|
|
83
|
+
const pkValue = entity[this.primaryKey];
|
|
84
|
+
if (!pkValue) {
|
|
85
|
+
throw new SDKError(
|
|
86
|
+
`Primary key "${this.primaryKey}" is required for remove`,
|
|
87
|
+
400,
|
|
88
|
+
"MISSING_PK"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await this.httpClient.post("/v1/rqlite/exec", {
|
|
93
|
+
sql: `DELETE FROM ${this.tableName} WHERE ${this.primaryKey} = ?`,
|
|
94
|
+
args: [pkValue],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private buildInsertSql(entity: T): string {
|
|
99
|
+
const columns = Object.keys(entity).filter((k) => entity[k] !== undefined);
|
|
100
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
101
|
+
return `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private buildInsertArgs(entity: T): any[] {
|
|
105
|
+
return Object.entries(entity)
|
|
106
|
+
.filter(([, v]) => v !== undefined)
|
|
107
|
+
.map(([, v]) => v);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private buildUpdateSql(entity: T): string {
|
|
111
|
+
const columns = Object.keys(entity)
|
|
112
|
+
.filter((k) => entity[k] !== undefined && k !== this.primaryKey)
|
|
113
|
+
.map((k) => `${k} = ?`);
|
|
114
|
+
return `UPDATE ${this.tableName} SET ${columns.join(", ")} WHERE ${this.primaryKey} = ?`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private buildUpdateArgs(entity: T): any[] {
|
|
118
|
+
const args = Object.entries(entity)
|
|
119
|
+
.filter(([k, v]) => v !== undefined && k !== this.primaryKey)
|
|
120
|
+
.map(([, v]) => v);
|
|
121
|
+
args.push(entity[this.primaryKey]);
|
|
122
|
+
return args;
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/db/types.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export interface Entity {
|
|
2
|
+
TableName(): string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface QueryResponse {
|
|
6
|
+
columns?: string[];
|
|
7
|
+
rows?: any[][];
|
|
8
|
+
count?: number;
|
|
9
|
+
items?: any[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TransactionOp {
|
|
13
|
+
kind: "exec" | "query";
|
|
14
|
+
sql: string;
|
|
15
|
+
args?: any[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TransactionRequest {
|
|
19
|
+
statements?: string[];
|
|
20
|
+
ops?: TransactionOp[];
|
|
21
|
+
return_results?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SelectOptions {
|
|
25
|
+
select?: string[];
|
|
26
|
+
joins?: Array<{
|
|
27
|
+
kind: "INNER" | "LEFT" | "RIGHT" | "FULL";
|
|
28
|
+
table: string;
|
|
29
|
+
on: string;
|
|
30
|
+
}>;
|
|
31
|
+
where?: Array<{
|
|
32
|
+
conj?: "AND" | "OR";
|
|
33
|
+
expr: string;
|
|
34
|
+
args?: any[];
|
|
35
|
+
}>;
|
|
36
|
+
group_by?: string[];
|
|
37
|
+
order_by?: string[];
|
|
38
|
+
limit?: number;
|
|
39
|
+
offset?: number;
|
|
40
|
+
one?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type FindOptions = Omit<SelectOptions, "select" | "joins" | "one">;
|
|
44
|
+
|
|
45
|
+
export interface ColumnDefinition {
|
|
46
|
+
name: string;
|
|
47
|
+
isPrimaryKey?: boolean;
|
|
48
|
+
isAutoIncrement?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function extractTableName(entity: Entity | string): string {
|
|
52
|
+
if (typeof entity === "string") return entity;
|
|
53
|
+
return entity.TableName();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function extractPrimaryKey(entity: any): string | undefined {
|
|
57
|
+
if (typeof entity === "string") return undefined;
|
|
58
|
+
|
|
59
|
+
// Check for explicit pk tag
|
|
60
|
+
const metadata = (entity as any)._dbMetadata;
|
|
61
|
+
if (metadata?.primaryKey) return metadata.primaryKey;
|
|
62
|
+
|
|
63
|
+
// Check for ID field
|
|
64
|
+
if (entity.id !== undefined) return "id";
|
|
65
|
+
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class SDKError extends Error {
|
|
2
|
+
public readonly httpStatus: number;
|
|
3
|
+
public readonly code: string;
|
|
4
|
+
public readonly details: Record<string, any>;
|
|
5
|
+
|
|
6
|
+
constructor(
|
|
7
|
+
message: string,
|
|
8
|
+
httpStatus: number = 500,
|
|
9
|
+
code: string = "SDK_ERROR",
|
|
10
|
+
details: Record<string, any> = {}
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "SDKError";
|
|
14
|
+
this.httpStatus = httpStatus;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.details = details;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static fromResponse(
|
|
20
|
+
status: number,
|
|
21
|
+
body: any,
|
|
22
|
+
message?: string
|
|
23
|
+
): SDKError {
|
|
24
|
+
const errorMsg = message || body?.error || `HTTP ${status}`;
|
|
25
|
+
const code = body?.code || `HTTP_${status}`;
|
|
26
|
+
return new SDKError(errorMsg, status, code, body);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
toJSON() {
|
|
30
|
+
return {
|
|
31
|
+
name: this.name,
|
|
32
|
+
message: this.message,
|
|
33
|
+
httpStatus: this.httpStatus,
|
|
34
|
+
code: this.code,
|
|
35
|
+
details: this.details,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { HttpClient, HttpClientConfig } from "./core/http";
|
|
2
|
+
import { AuthClient } from "./auth/client";
|
|
3
|
+
import { DBClient } from "./db/client";
|
|
4
|
+
import { PubSubClient } from "./pubsub/client";
|
|
5
|
+
import { NetworkClient } from "./network/client";
|
|
6
|
+
import { WSClientConfig } from "./core/ws";
|
|
7
|
+
import { StorageAdapter, MemoryStorage, LocalStorageAdapter } from "./auth/types";
|
|
8
|
+
|
|
9
|
+
export interface ClientConfig extends Omit<HttpClientConfig, "fetch"> {
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
jwt?: string;
|
|
12
|
+
storage?: StorageAdapter;
|
|
13
|
+
wsConfig?: Partial<WSClientConfig>;
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Client {
|
|
18
|
+
auth: AuthClient;
|
|
19
|
+
db: DBClient;
|
|
20
|
+
pubsub: PubSubClient;
|
|
21
|
+
network: NetworkClient;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createClient(config: ClientConfig): Client {
|
|
25
|
+
const httpClient = new HttpClient({
|
|
26
|
+
baseURL: config.baseURL,
|
|
27
|
+
timeout: config.timeout,
|
|
28
|
+
maxRetries: config.maxRetries,
|
|
29
|
+
retryDelayMs: config.retryDelayMs,
|
|
30
|
+
fetch: config.fetch,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const auth = new AuthClient({
|
|
34
|
+
httpClient,
|
|
35
|
+
storage: config.storage,
|
|
36
|
+
apiKey: config.apiKey,
|
|
37
|
+
jwt: config.jwt,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Derive WebSocket URL from baseURL if not explicitly provided
|
|
41
|
+
const wsURL = config.wsConfig?.wsURL ??
|
|
42
|
+
config.baseURL.replace(/^http/, 'ws').replace(/\/$/, '');
|
|
43
|
+
|
|
44
|
+
const db = new DBClient(httpClient);
|
|
45
|
+
const pubsub = new PubSubClient(httpClient, {
|
|
46
|
+
...config.wsConfig,
|
|
47
|
+
wsURL,
|
|
48
|
+
});
|
|
49
|
+
const network = new NetworkClient(httpClient);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
auth,
|
|
53
|
+
db,
|
|
54
|
+
pubsub,
|
|
55
|
+
network,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Re-exports
|
|
60
|
+
export { HttpClient } from "./core/http";
|
|
61
|
+
export { WSClient } from "./core/ws";
|
|
62
|
+
export { AuthClient } from "./auth/client";
|
|
63
|
+
export { DBClient } from "./db/client";
|
|
64
|
+
export { QueryBuilder } from "./db/qb";
|
|
65
|
+
export { Repository } from "./db/repository";
|
|
66
|
+
export { PubSubClient, Subscription } from "./pubsub/client";
|
|
67
|
+
export { NetworkClient } from "./network/client";
|
|
68
|
+
export { SDKError } from "./errors";
|
|
69
|
+
export { MemoryStorage, LocalStorageAdapter } from "./auth/types";
|
|
70
|
+
export type {
|
|
71
|
+
StorageAdapter,
|
|
72
|
+
AuthConfig,
|
|
73
|
+
WhoAmI,
|
|
74
|
+
} from "./auth/types";
|
|
75
|
+
export type * from "./db/types";
|
|
76
|
+
export type {
|
|
77
|
+
Message,
|
|
78
|
+
MessageHandler,
|
|
79
|
+
ErrorHandler,
|
|
80
|
+
CloseHandler,
|
|
81
|
+
} from "./pubsub/client";
|
|
82
|
+
export type { PeerInfo, NetworkStatus } from "./network/client";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { HttpClient } from "../core/http";
|
|
2
|
+
|
|
3
|
+
export interface PeerInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
addresses: string[];
|
|
6
|
+
lastSeen?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface NetworkStatus {
|
|
10
|
+
healthy: boolean;
|
|
11
|
+
peers: number;
|
|
12
|
+
uptime?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class NetworkClient {
|
|
16
|
+
private httpClient: HttpClient;
|
|
17
|
+
|
|
18
|
+
constructor(httpClient: HttpClient) {
|
|
19
|
+
this.httpClient = httpClient;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check gateway health.
|
|
24
|
+
*/
|
|
25
|
+
async health(): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
await this.httpClient.get("/v1/health");
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get network status.
|
|
36
|
+
*/
|
|
37
|
+
async status(): Promise<NetworkStatus> {
|
|
38
|
+
const response = await this.httpClient.get<NetworkStatus>("/v1/status");
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get connected peers.
|
|
44
|
+
*/
|
|
45
|
+
async peers(): Promise<PeerInfo[]> {
|
|
46
|
+
const response = await this.httpClient.get<{ peers: PeerInfo[] }>(
|
|
47
|
+
"/v1/network/peers"
|
|
48
|
+
);
|
|
49
|
+
return response.peers || [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Connect to a peer.
|
|
54
|
+
*/
|
|
55
|
+
async connect(peerAddr: string): Promise<void> {
|
|
56
|
+
await this.httpClient.post("/v1/network/connect", { peer_addr: peerAddr });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Disconnect from a peer.
|
|
61
|
+
*/
|
|
62
|
+
async disconnect(peerId: string): Promise<void> {
|
|
63
|
+
await this.httpClient.post("/v1/network/disconnect", { peer_id: peerId });
|
|
64
|
+
}
|
|
65
|
+
}
|