Skip to content

Commit 31450b3

Browse files
committed
feat(nestjs-json-rpc,nestjs-json-rpc-sdk): add ws transport
Add WebSocket transport for backend Add Websocket transport for sdk,
1 parent 1532cf9 commit 31450b3

39 files changed

+842
-86
lines changed

apps/json-api-front/proxy.conf.json

+5
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@
22
"/api": {
33
"target": "http://localhost:3000",
44
"secure": false
5+
},
6+
"/rpc": {
7+
"target": "http://localhost:3000",
8+
"secure": false,
9+
"ws": true
510
}
611
}

apps/json-api-front/src/app/app.component.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ import {
88
Rpc,
99
} from '@klerick/nestjs-json-rpc-sdk/json-rpc-sdk.module';
1010

11+
import { RpcService as IRpcService } from '@nestjs-json-api/type-for-rpc';
1112
import { switchMap } from 'rxjs';
1213

13-
interface TestRpc {
14-
test(a: number, b: number): Promise<number>;
15-
test2(firstArg: string, secondArg: number): Promise<string>;
16-
}
17-
1814
type RpcMap = {
19-
TestRpc: TestRpc;
15+
RpcService: IRpcService;
2016
};
2117

2218
@Component({
@@ -33,9 +29,15 @@ export class AppComponent implements OnInit {
3329
private rpcBatch = inject(RPC_BATCH);
3430

3531
ngOnInit(): void {
36-
const rpc1 = this.rpc.TestRpc.test(1, 2);
37-
const rpc2 = this.rpc.TestRpc.test2('string', 2);
32+
const rpc1 = this.rpc.RpcService.someMethode(1);
33+
34+
const rpc2 = this.rpc.RpcService.methodeWithObjectParams({
35+
a: 1,
36+
b: 1,
37+
});
38+
3839
this.rpcBatch(rpc2, rpc1).subscribe(([r2, r1]) => console.log(r1, r2));
40+
3941
this.JsonApiSdkService.getAll(class Users {}, {
4042
page: {
4143
size: 2,
@@ -74,9 +76,9 @@ export class AppComponent implements OnInit {
7476

7577
const tmpUsers = new Users();
7678
tmpUsers.id = 1;
77-
// this.JsonApiSdkService.getRelationships(tmpUsers, 'addresses').subscribe(
78-
// (r) => console.log(r)
79-
// );
79+
this.JsonApiSdkService.getRelationships(tmpUsers, 'addresses').subscribe(
80+
(r) => console.log(r)
81+
);
8082

8183
const roles = new Roles();
8284
roles.id = 10000;

apps/json-api-front/src/app/app.config.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
JsonRpcAngular,
55
TransportType,
66
} from '@klerick/nestjs-json-rpc-sdk/json-rpc-sdk.module';
7+
import io from 'socket.io-client';
78

89
export const appConfig: ApplicationConfig = {
910
providers: [
@@ -17,9 +18,12 @@ export const appConfig: ApplicationConfig = {
1718
),
1819
importProvidersFrom(
1920
JsonRpcAngular.forRoot({
20-
transport: TransportType.HTTP,
21+
transport: TransportType.WS,
2122
rpcPath: 'rpc',
22-
rpcHost: 'http://localhost:4200',
23+
rpcHost: 'ws://localhost:4200',
24+
useWsNativeSocket: true,
25+
// useWsNativeSocket: false,
26+
// webSocketCtor: io('http://localhost:3000', { path: '/rpc' }),
2327
})
2428
),
2529
],

apps/json-api-server-e2e/src/json-api/json-api-sdk/atomic-sdk.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FilterOperand, JsonSdkPromise } from 'json-api-nestjs-sdk';
33
import { Addresses, CommentKind, Comments, Roles, Users } from 'database';
44
import { faker } from '@faker-js/faker';
55
import { getUser } from '../utils/data-utils';
6-
import { run, creatSdk } from '../utils/run-ppplication';
6+
import { run, creatSdk } from '../utils/run-application';
77

88
let app: INestApplication;
99

apps/json-api-server-e2e/src/json-api/json-api-sdk/check-common-decorator.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FilterOperand, JsonSdkPromise } from 'json-api-nestjs-sdk';
33
import { AxiosError } from 'axios';
44
import { Users } from 'database';
55

6-
import { run, creatSdk } from '../utils/run-ppplication';
6+
import { run, creatSdk } from '../utils/run-application';
77

88
let app: INestApplication;
99

apps/json-api-server-e2e/src/json-api/json-api-sdk/check-othe-call.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { BookList, Users } from 'database';
44
import { AxiosError } from 'axios';
55
import { faker } from '@faker-js/faker';
66
import { lastValueFrom } from 'rxjs';
7-
import { creatSdk, run, axiosAdapter } from '../utils/run-ppplication';
7+
import { creatSdk, run, axiosAdapter } from '../utils/run-application';
88

99
let app: INestApplication;
1010

apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker';
44

55
import { FilterOperand, JsonSdkPromise } from 'json-api-nestjs-sdk';
66
import { getUser } from '../utils/data-utils';
7-
import { creatSdk, run } from '../utils/run-ppplication';
7+
import { creatSdk, run } from '../utils/run-application';
88

99
let app: INestApplication;
1010

apps/json-api-server-e2e/src/json-api/json-api-sdk/patch-methode.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Addresses, CommentKind, Comments, Users } from 'database';
33
import { faker } from '@faker-js/faker';
44
import { JsonSdkPromise } from 'json-api-nestjs-sdk';
55

6-
import { creatSdk, run } from '../utils/run-ppplication';
6+
import { creatSdk, run } from '../utils/run-application';
77

88
let app: INestApplication;
99

apps/json-api-server-e2e/src/json-api/json-api-sdk/post-method.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Addresses, BookList, CommentKind, Comments, Users } from 'database';
22
import { faker } from '@faker-js/faker';
33
import { JsonSdkPromise } from 'json-api-nestjs-sdk';
44

5-
import { creatSdk, run } from '../utils/run-ppplication';
5+
import { creatSdk, run } from '../utils/run-application';
66
import { INestApplication } from '@nestjs/common';
77
let app: INestApplication;
88

apps/json-api-server-e2e/src/json-api/json-rpc/run-json-rpc.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
RpcError,
66
} from '@klerick/nestjs-json-rpc-sdk';
77

8-
import { creatRpcSdk, MapperRpc, run } from '../utils/run-ppplication';
8+
import { creatRpcSdk, MapperRpc, run } from '../utils/run-application';
99

1010
let app: INestApplication;
1111

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import {
3+
ResultRpcFactoryPromise,
4+
ErrorCodeType,
5+
RpcError,
6+
} from '@klerick/nestjs-json-rpc-sdk';
7+
8+
import { creatWsRpcSdk, MapperRpc, run } from '../utils/run-application';
9+
10+
let app: INestApplication;
11+
12+
beforeAll(async () => {
13+
app = await run();
14+
});
15+
16+
afterAll(async () => {
17+
await app.close();
18+
});
19+
20+
describe('Run ws json rpc:', () => {
21+
let rpc: ResultRpcFactoryPromise<MapperRpc>['rpc'];
22+
let rpcBatch: ResultRpcFactoryPromise<MapperRpc>['rpcBatch'];
23+
let rpcForBatch: ResultRpcFactoryPromise<MapperRpc>['rpcForBatch'];
24+
beforeEach(() => {
25+
({ rpc, rpcBatch, rpcForBatch } = creatWsRpcSdk());
26+
});
27+
28+
describe('Should be correct response', () => {
29+
it('Should be call one method', async () => {
30+
const input = 1;
31+
const result = await rpc.RpcService.someMethode(input);
32+
expect(result).toBe(input);
33+
});
34+
35+
it('Should be correct response batch', async () => {
36+
const input = 1;
37+
const input2 = {
38+
a: 1,
39+
b: 2,
40+
};
41+
const call1 = rpcForBatch.RpcService.someMethode(input);
42+
const call2 = rpcForBatch.RpcService.methodeWithObjectParams(input2);
43+
44+
const [result1, result2] = await rpcBatch(call1, call2);
45+
expect(result1).toBe(input);
46+
if ('error' in result2) {
47+
throw Error('Return error');
48+
}
49+
expect(result2.d).toEqual(`${input2.a}`);
50+
expect(result2.c).toEqual(`${input2.b}`);
51+
});
52+
});
53+
54+
describe('Check error', () => {
55+
it('Should throw an error ' + ErrorCodeType.MethodNotFound, async () => {
56+
const input = 1;
57+
expect.assertions(6);
58+
try {
59+
// @ts-ignore
60+
await rpc.IncorrectService.incorrectMethode(input);
61+
} catch (e) {
62+
expect(e).toBeInstanceOf(RpcError);
63+
expect((e as RpcError).code).toBe(-32601);
64+
expect((e as RpcError).message).toBe(ErrorCodeType.MethodNotFound);
65+
}
66+
try {
67+
// @ts-ignore
68+
await rpc.RpcService.incorrectMethode(input);
69+
} catch (e) {
70+
expect(e).toBeInstanceOf(RpcError);
71+
expect((e as RpcError).code).toBe(-32601);
72+
expect((e as RpcError).message).toBe(ErrorCodeType.MethodNotFound);
73+
}
74+
});
75+
76+
it('Should throw an error ' + ErrorCodeType.InvalidParams, async () => {
77+
const input = 'llll';
78+
expect.assertions(3);
79+
try {
80+
// @ts-ignore
81+
await rpc.RpcService.someMethode(input);
82+
} catch (e) {
83+
expect(e).toBeInstanceOf(RpcError);
84+
expect((e as RpcError).code).toBe(-32602);
85+
expect((e as RpcError).message).toBe(ErrorCodeType.InvalidParams);
86+
}
87+
});
88+
89+
it('Should throw an error ' + ErrorCodeType.ServerError, async () => {
90+
const input = 5;
91+
expect.assertions(4);
92+
try {
93+
await rpc.RpcService.someMethode(input);
94+
} catch (e) {
95+
expect(e).toBeInstanceOf(RpcError);
96+
expect((e as RpcError).code).toBe(-32099);
97+
expect((e as RpcError).message).toBe(ErrorCodeType.ServerError);
98+
expect((e as RpcError).data.title).toBe('Custom Error');
99+
}
100+
});
101+
});
102+
});

apps/json-api-server-e2e/src/json-api/utils/run-ppplication.ts renamed to apps/json-api-server-e2e/src/json-api/utils/run-application.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import {
77
RpcConfig,
88
} from '@klerick/nestjs-json-rpc-sdk';
99
import { RpcService } from '@nestjs-json-api/type-for-rpc';
10+
import { TransportType } from '@klerick/nestjs-json-rpc-sdk';
1011
import axios from 'axios';
1112
import { Logger } from 'nestjs-pino';
13+
import { WebSocket } from 'ws';
1214

1315
import { AppModule } from '../../../../json-api-server/src/app/app.module';
1416

1517
import { JsonConfig } from '../../../../../libs/json-api/json-api-nestjs-sdk/src/lib/types';
16-
import { TransportType } from '@klerick/nestjs-json-rpc-sdk';
18+
import { WsAdapter } from '@nestjs/platform-ws';
1719

1820
export const axiosAdapter = adapterForAxios(axios);
1921
let saveApp: INestApplication;
@@ -32,6 +34,7 @@ export const run = async () => {
3234
app.useLogger(app.get(Logger));
3335
// const app = await NestFactory.create(AppModule);
3436
app.setGlobalPrefix(globalPrefix);
37+
app.useWebSocketAdapter(new WsAdapter(app));
3538
await app.init();
3639
await app.listen(port);
3740

@@ -61,9 +64,21 @@ export const creatRpcSdk = (config: Partial<RpcConfig> = {}) =>
6164
{
6265
...config,
6366
rpcHost: `http://localhost:${port}`,
64-
rpcPath: `${globalPrefix}/rpc`,
67+
rpcPath: `/rpc`,
6568
transport: TransportType.HTTP,
6669
httpAgentFactory: axiosTransportFactory(axios),
6770
},
6871
true
6972
);
73+
74+
export const creatWsRpcSdk = (config: Partial<RpcConfig> = {}) =>
75+
RpcFactory<MapperRpc>(
76+
{
77+
transport: TransportType.WS,
78+
useWsNativeSocket: true,
79+
webSocketCtor: WebSocket,
80+
rpcHost: `http://localhost:${port}`,
81+
rpcPath: `/rpc`,
82+
},
83+
true
84+
);

apps/json-api-server/src/app/rpc/rpc.module.ts

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,54 @@
1-
import { Module } from '@nestjs/common';
1+
import { Injectable, Module, ParseIntPipe, UsePipes } from '@nestjs/common';
22
import { NestjsJsonRpcModule, TransportType } from '@klerick/nestjs-json-rpc';
33
import { RpcService } from './service/rpc.service';
4+
import {
5+
MessageBody,
6+
SubscribeMessage,
7+
WebSocketGateway,
8+
WsResponse,
9+
} from '@nestjs/websockets';
10+
import { from, Observable } from 'rxjs';
11+
import { map } from 'rxjs/operators';
12+
import {
13+
ArgumentMetadata,
14+
PipeTransform,
15+
} from '@nestjs/common/interfaces/features/pipe-transform.interface';
16+
17+
@WebSocketGateway({
18+
cors: {
19+
origin: '*',
20+
},
21+
path: '/rpc/',
22+
})
23+
class TesWebSocketService {
24+
constructor() {
25+
console.log(1213);
26+
}
27+
28+
@UsePipes(ParseIntPipe)
29+
@SubscribeMessage('events')
30+
findAll(@MessageBody() data: number): Observable<WsResponse<number>> {
31+
return from([1, 2, 3]).pipe(
32+
map((item) => ({ event: 'events', data: item }))
33+
);
34+
}
35+
}
436

537
@Module({
638
imports: [
739
NestjsJsonRpcModule.forRootAsync({
840
path: 'rpc',
941
transport: TransportType.HTTP,
1042
}),
43+
NestjsJsonRpcModule.forRootAsync({
44+
transport: TransportType.WS,
45+
wsConfig: {
46+
path: '/rpc',
47+
cors: {
48+
origin: '*',
49+
},
50+
},
51+
}),
1152
],
1253
providers: [RpcService],
1354
})

apps/json-api-server/src/main.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
import { Logger } from '@nestjs/common';
77
import { NestFactory } from '@nestjs/core';
8+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
9+
import { WsAdapter } from '@nestjs/platform-ws';
810

911
import { AppModule } from './app/app.module';
10-
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
1112

1213
async function bootstrap() {
1314
const app = await NestFactory.create(AppModule);
15+
app.useWebSocketAdapter(new WsAdapter(app));
1416
const globalPrefix = 'api';
1517
app.setGlobalPrefix(globalPrefix);
1618

Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const JSON_RPC_VERSION = '2.0';
2+
export const WS_EVENT_NAME = 'rpc';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { fromFetch } from 'rxjs/fetch';
2+
import { LoopFunc, PayloadRpc, RpcResult, Transport } from '../types';
3+
4+
export function fetchTransportFactory<T extends LoopFunc>(
5+
url: string
6+
): Transport<T> {
7+
return (body: PayloadRpc<T>) =>
8+
fromFetch<RpcResult<T>>(url, {
9+
method: 'post',
10+
body: JSON.stringify(body),
11+
selector: (r) => r.json(),
12+
});
13+
}

libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export * from './axios-transport.factory';
22
export * from './id-request';
33
export * from './rpc.factory';
44
export * from './transport.factory';
5+
export * from './fetch-transport.factory';
6+
export * from './io-transport.factory';

0 commit comments

Comments
 (0)