Skip to content

feat(nestjs-json-rpc): default transport for rpc #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(nestjs-json-rpc-sdk): sdk for rpc
- http transport
- factory for native js
- angular module

Closes: #77
  • Loading branch information
klerick committed Mar 14, 2024
commit eaa48e6cb15a06a7e1bb0bb43e041add49cc6f60
32 changes: 26 additions & 6 deletions apps/json-api-front/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@ import { Component, inject, OnInit } from '@angular/core';
import { NxWelcomeComponent } from './nx-welcome.component';
import { JsonApiSdkService } from 'json-api-nestjs-sdk';
import { AtomicFactory } from 'json-api-nestjs-sdk/json-api-nestjs-sdk.module';
import {
JSON_RPC,
RPC_BATCH,
Rpc,
} from '@klerick/nestjs-json-rpc-sdk/json-rpc-sdk.module';

import { switchMap } from 'rxjs';

interface TestRpc {
test(a: number, b: number): Promise<number>;
test2(firstArg: string, secondArg: number): Promise<string>;
}

type RpcMap = {
TestRpc: TestRpc;
};

@Component({
standalone: true,
imports: [NxWelcomeComponent],
Expand All @@ -14,14 +29,19 @@ import { switchMap } from 'rxjs';
export class AppComponent implements OnInit {
private JsonApiSdkService = inject(JsonApiSdkService);
private atomicFactory = inject(AtomicFactory);
private rpc = inject<Rpc<RpcMap>>(JSON_RPC);
private rpcBatch = inject(RPC_BATCH);

ngOnInit(): void {
// this.JsonApiSdkService.getAll(class Users {}, {
// page: {
// size: 2,
// number: 1,
// },
// }).subscribe((r) => console.log(r));
const rpc1 = this.rpc.TestRpc.test(1, 2);
const rpc2 = this.rpc.TestRpc.test2('string', 2);
this.rpcBatch(rpc2, rpc1).subscribe(([r2, r1]) => console.log(r1, r2));
this.JsonApiSdkService.getAll(class Users {}, {
page: {
size: 2,
number: 1,
},
}).subscribe((r) => console.log(r));

class Addresses {
id = 1;
Expand Down
11 changes: 11 additions & 0 deletions apps/json-api-front/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { JsonApiAngular } from 'json-api-nestjs-sdk/json-api-nestjs-sdk.module';
import {
JsonRpcAngular,
TransportType,
} from '@klerick/nestjs-json-rpc-sdk/json-rpc-sdk.module';

export const appConfig: ApplicationConfig = {
providers: [
Expand All @@ -11,5 +15,12 @@ export const appConfig: ApplicationConfig = {
operationUrl: 'operation',
})
),
importProvidersFrom(
JsonRpcAngular.forRoot({
transport: TransportType.HTTP,
rpcPath: 'rpc',
rpcHost: 'http://localhost:4200',
})
),
],
};
25 changes: 25 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": ["../../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}
11 changes: 11 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# nestjs-json-rpc-sdk

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build nestjs-json-rpc-sdk` to build the library.

## Running unit tests

Run `nx test nestjs-json-rpc-sdk` to execute the unit tests via [Jest](https://jestjs.io).
11 changes: 11 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'nestjs-json-rpc-sdk',
preset: '../../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../coverage/libs/json-rpc/nestjs-json-rpc-sdk',
};
7 changes: 7 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@klerick/nestjs-json-rpc-sdk",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
}
}
23 changes: 23 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "nestjs-json-rpc-sdk",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/json-rpc/nestjs-json-rpc-sdk/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/json-rpc/nestjs-json-rpc-sdk",
"main": "libs/json-rpc/nestjs-json-rpc-sdk/src/index.ts",
"tsConfig": "libs/json-rpc/nestjs-json-rpc-sdk/tsconfig.lib.json",
"assets": ["libs/json-rpc/nestjs-json-rpc-sdk/*.md"]
}
},
"publish": {
"command": "node tools/scripts/publish.mjs nestjs-json-rpc-sdk {args.ver} {args.tag}",
"dependsOn": ["build"]
}
},
"tags": []
}
1 change: 1 addition & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/nestjs-json-rpc-sdk';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/json-rpc-angular';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const JSON_RPC_VERSION = '2.0';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Axios, AxiosResponse } from 'axios';
import { Observable } from 'rxjs';

import {
HttpAgentFactory,
LoopFunc,
PayloadRpc,
ReturnTransportCall,
RpcResult,
} from '../types';
import { map } from 'rxjs/operators';

export function axiosTransportFactory<T extends LoopFunc>(
axios: Axios
): HttpAgentFactory<T> {
return (url: string) => (body: PayloadRpc<T>) => {
const controller = new AbortController();
const signal = controller.signal;

return new Observable<AxiosResponse<RpcResult<T>>>((subscriber) => {
axios
.post<
ReturnTransportCall<T>,
AxiosResponse<RpcResult<T>, PayloadRpc<T>>,
PayloadRpc<T>
>(url, body, { signal })
.then((response) => subscriber.next(response))
.catch((error: unknown) => subscriber.error(error))
.finally(() => subscriber.complete());

return { unsubscribe: () => controller.abort() };
}).pipe(map((r) => r.data));
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { idRequest } from './id-request';

describe('id-request', () => {
it('should be increment', () => {
expect(idRequest()).toBe(1);
expect(idRequest()).toBe(2);
expect(idRequest()).toBe(3);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let i = 0;
export const idRequest = () => ++i;
4 changes: 4 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './axios-transport.factory';
export * from './id-request';
export * from './rpc.factory';
export * from './transport.factory';
39 changes: 39 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/rpc.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RpcConfig, RpcReturnList, RpcBatch, RpcBatchPromise } from '../types';
import { transportFactory } from './transport.factory';
import { RpcBatchFactory, rpcProxy, RpcBatchFactoryPromise } from '../utils';

type ResultRpcFactory<T extends object> = {
rpc: RpcReturnList<T, false>;
rpcBatch: RpcBatch;
};
type ResultRpcFactoryPromise<T extends object> = {
rpc: RpcReturnList<T, true>;
rpcForBatch: RpcReturnList<T, false>;
rpcBatch: RpcBatchPromise;
};

export function RpcFactory<T extends object>(
options: RpcConfig,
usePromise: false
): ResultRpcFactory<T>;
export function RpcFactory<T extends object>(
options: RpcConfig,
usePromise: true
): ResultRpcFactoryPromise<T>;
export function RpcFactory<T extends object>(
options: RpcConfig,
usePromise: true | false = false
): ResultRpcFactory<T> | ResultRpcFactoryPromise<T> {
const transport = transportFactory(options);
let rpc: RpcReturnList<T, true> | RpcReturnList<T, false>;
let rpcForBatch: RpcReturnList<T, false>;

if (usePromise) {
rpc = rpcProxy<RpcReturnList<T, true>>(transport, usePromise);
rpcForBatch = rpcProxy<RpcReturnList<T, false>>(transport, usePromise);
return { rpc, rpcForBatch, rpcBatch: RpcBatchFactoryPromise(transport) };
} else {
rpc = rpcProxy<RpcReturnList<T, false>>(transport, usePromise);
return { rpc, rpcBatch: RpcBatchFactory(transport) };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { fromFetch } from 'rxjs/fetch';
import {
RpcConfig,
Transport,
TransportType,
RpcHttpConfig,
LoopFunc,
PayloadRpc,
RpcResult,
} from '../types';

function httpTransport<T extends LoopFunc>(
config: RpcHttpConfig
): Transport<T> {
const url = new URL(config.rpcPath, config.rpcHost).toString();
if (config.httpAgentFactory) {
return config.httpAgentFactory(url);
}

return (body: PayloadRpc<T>) =>
fromFetch<RpcResult<T>>(url, {
method: 'post',
body: JSON.stringify(body),
selector: (r) => r.json(),
});
}

export function transportFactory<T extends LoopFunc>(
rpcConfig: RpcConfig
): Transport<T> {
switch (rpcConfig.transport) {
case TransportType.HTTP:
return httpTransport(rpcConfig);
case TransportType.WS:
throw new Error('Unknown transport');
default:
throw new Error('Unknown transport');
}
}
80 changes: 80 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/src/lib/json-rpc-angular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
Component,
inject,
InjectionToken,
ModuleWithProviders,
NgModule,
} from '@angular/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import {
LoopFunc,
RpcMainHttpConfig,
RpcHttpConfig,
RpcWsConfig,
Transport,
TransportType,
PayloadRpc,
RpcResult,
RpcReturnList,
RpcBatch,
} from './types';
import { transportFactory } from './factory';
import { RpcBatchFactory, rpcProxy } from './utils';

type Rpc<T extends object> = RpcReturnList<T, false>;

export { TransportType, Rpc };

export const JSON_RPC_SDK_CONFIG = new InjectionToken<JsonRpcAngularConfig>(
'Main config object for sdk'
);

export const JSON_RPC_SDK_TRANSPORT = new InjectionToken<Transport<LoopFunc>>(
'Transport for RPC',
{
factory: () => {
const config = inject(JSON_RPC_SDK_CONFIG);
const httpClient = inject(HttpClient);
if (config.transport === TransportType.HTTP) {
(config as unknown as RpcHttpConfig)['httpAgentFactory'] =
(url: string) => (body: PayloadRpc<LoopFunc>) => {
return httpClient.post<RpcResult<LoopFunc>>(url, body);
};
}
return transportFactory(config);
},
}
);

export const JSON_RPC = new InjectionToken<RpcReturnList<object, false>>(
'Rpc client',
{
factory: () =>
rpcProxy<RpcReturnList<any, true>>(inject(JSON_RPC_SDK_TRANSPORT), false),
}
);

export const RPC_BATCH = new InjectionToken<RpcBatch>('Rpc client for batch', {
factory: () => RpcBatchFactory(inject(JSON_RPC_SDK_TRANSPORT)),
});

export type JsonRpcAngularConfig = RpcMainHttpConfig | RpcWsConfig;

@NgModule({
imports: [HttpClientModule],
})
export class JsonRpcAngular {
static forRoot(
config: JsonRpcAngularConfig
): ModuleWithProviders<JsonRpcAngular> {
return {
ngModule: JsonRpcAngular,
providers: [
{
useValue: config,
provide: JSON_RPC_SDK_CONFIG,
},
],
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { nestjsJsonRpcSdk } from './nestjs-json-rpc-sdk';

describe('nestjsJsonRpcSdk', () => {
it('should work', () => {
expect(nestjsJsonRpcSdk()).toEqual('nestjs-json-rpc-sdk');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { RpcConfig } from './types';

export function nestjsJsonRpcSdk(rpcConfig: RpcConfig): string {
return 'nestjs-json-rpc-sdk';
}
Loading