Skip to content

Commit

Permalink
feat(nestjs-json-rpc-sdk): sdk for rpc
Browse files Browse the repository at this point in the history
- http transport
- factory for native js
- angular module

Closes: #77
  • Loading branch information
klerick committed Mar 14, 2024
1 parent f89f7cf commit eaa48e6
Show file tree
Hide file tree
Showing 47 changed files with 1,117 additions and 90 deletions.
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

0 comments on commit eaa48e6

Please sign in to comment.