rpc-nats-alvamind v1.0.28
๐ฆ rpc-nats-alvamind: Your Go-To RPC Library with NATS
โก๏ธ Supercharge your microservices with seamless, reliable communication!
๐ค What's the Deal?
rpc-nats-alvamind is a lightweight, flexible RPC (Remote Procedure Call) library built on top of the blazing-fast NATS messaging system. It's designed for building robust, scalable microservices with ease. Think of it as the glue that holds your distributed systems together! ๐งฉ
โจ Key Features
- ๐จ NATS Power: Built on top of NATS, leveraging its speed and reliability for high-performance messaging.
- ๐ Codec Flexibility: Supports
jsonandstringcodecs. Easily plug in your own! - ๐ฏ Type-Safe Proxies: Generate client proxies with strong typing, reducing runtime errors.
- Choose between
ClassTypeProxyoras unknown as ClassNamecasting for generated proxies.
- Choose between
- ๐ก๏ธ Error Handling: Handles errors gracefully with retry logic and dead-letter queue (DLQ) support.
- โฑ๏ธ Customizable Timeouts: Set timeouts for requests to prevent hanging calls.
- ๐ Prototype Chain Support: Works flawlessly with class inheritance, making your life easier.
- ๐ชต Logger Integration: Includes built-in logging with
logger-alvamindfor easy debugging. - ๐ ๏ธ Easy Setup: Simple to install and use, getting you up and running in minutes.
- โ Fully Tested: Comes with a comprehensive test suite for reliability.
๐ Benefits
- โก๏ธ Speed & Efficiency: NATS's performance gives you lightning-fast RPC calls.
- โ๏ธ Scalability: Designed for microservices, making it easy to scale your apps.
- ๐งฑ Modular Design: The code is clean and well-organized, making it easy to maintain and extend.
- โ Reliability: Retry logic and DLQ ensure your requests are processed, even when things get bumpy.
- ๐งโ๐ป Developer-Friendly: Easy-to-use API with TypeScript support.
- ๐ฅณ Code That's Fun: Built with modern JS, you'll enjoy developing with this!
๐ ๏ธ Installation
bun add rpc-nats-alvamind๐ Usage
1. Setting Up a Server
import { RPCServer } from 'rpc-nats-alvamind';
// Define your service classes
class MyService {
async hello(name: string): Promise<string> {
return `Hello, ${name}!`;
}
async complexObject(data: any): Promise<any> {
return data
}
}
class AnotherService {
async greet(message: string): Promise<string> {
return `Server says: ${message}`;
}
}
async function main() {
const server = new RPCServer({ url: 'nats://localhost:4222', debug: true }); // Pass in your NATS options
await server.start();
// Register your service instances
await server.handleRequest(new MyService());
await server.handleRequest(new AnotherService());
// or with retry
await server.handleRequest(new MyService(),{ retry: { attempts: 3, delay: 100 } });
await server.handleRequest(new AnotherService(), { dlq: 'my.dlq', retry: { attempts: 3, delay: 100 } });
console.log('RPC Server is running!');
}
main();2. Creating a Client Proxy
import { RPCClient, ClassTypeProxy } from 'rpc-nats-alvamind';
// Define your service interfaces/classes, matching server side
class MyService {
hello(name: string): Promise<string> {
throw new Error('Method not implemented.');
}
complexObject(data: any): Promise<any> {
throw new Error('Method not implemented.');
}
}
class AnotherService {
greet(message: string): Promise<string> {
throw new Error('Method not implemented.');
}
}
async function main() {
const client = new RPCClient({ url: 'nats://localhost:4222', debug: true, timeout: 5000 }); // Pass in your NATS options
await client.start();
// Generate proxies
const myServiceClient: ClassTypeProxy<MyService> = client.createProxy(MyService);
const anotherServiceClient: ClassTypeProxy<AnotherService> = client.createProxy(AnotherService);
// Use the generated proxies to make RPC calls
const response1 = await myServiceClient.hello('World');
console.log('Response 1:', response1); // Output: Hello, World!
const response2 = await anotherServiceClient.greet('Howdy!');
console.log('Response 2:', response2); // Output: Server says: Howdy!
const data = { name: "Complex", data: { nested: [{ value: 1, isTrue: true, date: new Date(), bigint: BigInt(9007199254740991) }] } }
const response3 = await myServiceClient.complexObject(data)
console.log('Response 3:', response3);
await client.close();
}
main();3. Codec Usage
import { RPCServer, RPCClient, ClassTypeProxy, getCodec } from 'rpc-nats-alvamind';
// Server Side
class MyService {
async hello(name: string): Promise<string> {
return `Hello, ${name}!`;
}
}
async function mainServer() {
const server = new RPCServer({ url: 'nats://localhost:4222', codec: "string" }); // using string codec
await server.start();
await server.handleRequest(new MyService());
}
mainServer();
// Client Side
class MyService {
hello(name: string): Promise<string> {
throw new Error('Method not implemented.');
}
}
async function mainClient() {
const client = new RPCClient({ url: 'nats://localhost:4222', codec: "string"}); // using string codec
await client.start();
const myServiceClient: ClassTypeProxy<MyService> = client.createProxy(MyService);
const response1 = await myServiceClient.hello('World');
console.log('Response 1:', response1); // Output: Hello, World!
await client.close();
}
mainClient();4. Custom Codec Usage
import { RPCServer, RPCClient, ClassTypeProxy, NatsCodec } from 'rpc-nats-alvamind';
// Server Side
//Custom Codec
class MyCustomCodec implements NatsCodec<any>{
encode(data: any): Uint8Array {
return new TextEncoder().encode(JSON.stringify(data));
}
decode(data: Uint8Array): any {
const value = new TextDecoder().decode(data)
return JSON.parse(value);
}
}
class MyService {
async hello(name: string): Promise<string> {
return `Hello, ${name}!`;
}
}
async function mainServer() {
const codec = new MyCustomCodec();
const server = new RPCServer({ url: 'nats://localhost:4222', codec: codec });
await server.start();
await server.handleRequest(new MyService());
}
mainServer();
// Client Side
class MyService {
hello(name: string): Promise<string> {
throw new Error('Method not implemented.');
}
}
async function mainClient() {
const codec = new MyCustomCodec()
const client = new RPCClient({ url: 'nats://localhost:4222', codec: codec});
await client.start();
const myServiceClient: ClassTypeProxy<MyService> = client.createProxy(MyService);
const response1 = await myServiceClient.hello('World');
console.log('Response 1:', response1);
await client.close();
}
mainClient();5. Error Handling
import { RPCServer, RPCClient, ClassTypeProxy } from 'rpc-nats-alvamind';
// Define your service classes
class MyService {
async error(): Promise<string> {
throw new Error("this is test error");
}
}
async function main() {
const server = new RPCServer({ url: 'nats://localhost:4222', debug: true });
await server.start();
// Register your service instances
await server.handleRequest(new MyService());
console.log('RPC Server is running!');
const client = new RPCClient({ url: 'nats://localhost:4222', debug: true, timeout: 5000 });
await client.start();
const myServiceClient: ClassTypeProxy<MyService> = client.createProxy(MyService);
try{
await myServiceClient.error();
}catch(err){
console.log("error", err) // catch the error
}
await server.close();
await client.close();
}
main();6. Retry with DLQ (Dead Letter Queue)
import { RPCServer, RPCClient, ClassTypeProxy } from 'rpc-nats-alvamind';
interface DLQMessage {
className: string;
methodName: string;
data: any;
error: string;
errorType: string
}
// Define your service classes
class MyService {
async flakyCall(): Promise<string> {
throw new Error("flaky error");
}
}
async function main() {
const dlqSubject = 'my.dlq'; // dlq subject
let dlqMessage: DLQMessage | undefined;
const server = new RPCServer({ url: 'nats://localhost:4222', debug: true, dlq: dlqSubject, retry: { attempts: 2, delay: 100 } }); // retry logic
await server.start();
const dlqPromise = new Promise<DLQMessage>((resolve) => {
const nc = server['natsClient'].getNatsConnection();
nc?.subscribe(dlqSubject, {
callback: (err, msg) => {
if (!err) {
const decoded = server['natsClient'].getCodec().decode(msg.data);
resolve(decoded);
}
}
});
});
// Register your service instances
await server.handleRequest(new MyService());
console.log('RPC Server is running!');
const client = new RPCClient({ url: 'nats://localhost:4222', debug: true, timeout: 5000 });
await client.start();
const myServiceClient: ClassTypeProxy<MyService> = client.createProxy(MyService);
try{
await myServiceClient.flakyCall();
}catch(err){
console.log("error", err) // catch the error
}
dlqMessage = await dlqPromise;
console.log("DLQ Message", dlqMessage); // DLQ message
await server.close();
await client.close();
}
main();7. Generating Client Proxies with rpc-nats CLI
The rpc-nats-alvamind package includes a CLI tool to automatically generate the rpc-services.ts file containing client proxies. This tool is particularly useful for large projects.
To generate RPC services, use the following command:
rpc-nats generate --includes="src/**/*.controller.ts" --output="src/common/rpc/rpc-services.ts"Options:
--includes: Glob patterns for including files (required).--excludes: Glob patterns for excluding files.--output: Output file path.--watch: Watch for file changes and regenerate.--logLevel: Log level (debug, info, warn, error).--proxyType: Type of proxy generation,proxy(default), generate proxies usingClassTypeProxyorcastto generate proxies with casting withas unknown as ClassName
Examples:
# Generate rpc-services.ts using ClassTypeProxy
rpc-nats generate --includes="src/**/*.controller.ts" --output="src/common/rpc/rpc-services.ts"
# Generate rpc-services.ts with casting
rpc-nats generate --includes="src/**/*.controller.ts" --output="src/common/rpc/rpc-services.ts" --proxyType=cast
# Multiple includes and excludes with watch mode
rpc-nats generate \
--includes="src/**/*.controller.ts" "src/**/*.service.ts" \
--excludes="src/**/*.spec.ts" "src/**/*.test.ts" \
--output="src/common/rpc/rpc-services.ts" \
--watch๐งช Testing
To run the test suite:
bun test test/*.test.ts๐ API Reference
RPCServer
constructor(options: RPCServerOptions): Creates a new RPC server instance.start(): Promise<void>: Connects to the NATS server.close(): Promise<void>: Closes the NATS connection.handleRequest<T extends ClassType>(instance: T, retryConfig?: { retry?: {attempts: number, delay: number }, dlq?: string}): Promise<void>: Registers an instance to handle requests.isConnected(): boolean: Checks whether nats client is connectedgetRegisteredMethods(): Map<string, Set<string>>: get all registered methods.isMethodRegistered(className: string, methodName: string): boolean: check if method is registered.
RPCClient
constructor(options: RPCClientOptions): Creates a new RPC client instance.start(): Promise<void>: Connects to the NATS server.close(): Promise<void>: Closes the NATS connection.createProxy<T extends ClassType>(classConstructor: new (...args: any[]) => T): ClassTypeProxy<T>: Creates a proxy for a class.isConnected(): boolean: Checks if the client is connected.getAvailableMethods(className: string): string[]: Gets available methods for class.isMethodAvailable(className: string, methodName: string): boolean: check if method is available.setTimeout(timeout: number): void: Sets request timeout.getTimeout(): number: Gets the current timeout.clearMethodCache(className?: string): void: Clears method cache.getStats(): { isConnected: boolean; cachedClasses: number; totalCachedMethods: number; timeout: number; }: Gets client stats.
NatsOptions
url?: string: NATS server URL (nats://localhost:4222by default).codec?: "json" | "string": Choose codec, default isjson.debug?: boolean: Enable debug logs.
RPCServerOptions
retry?: { attempts: number; delay: number; }: Configure retries.dlq?: string: Dead Letter Queue Subject.
ClassTypeProxy<T>
- A type for dynamic client proxy, that will expose method of class
T.
getCodec
getCodec<T = unknown>(codec: SupportedCodec | NatsCodec<T> = "json"): NatsCodec<T>: A function to get codec instance either json or string or instance of custom codec.
โ๏ธ Advanced Usage
๐ ๏ธ Custom Codecs
You can create your own codec by implementing the NatsCodec interface:
import { NatsCodec } from 'rpc-nats-alvamind';
class MyCustomCodec<T> implements NatsCodec<T> {
encode(data: T): Uint8Array {
// Your custom encoding logic here
return new TextEncoder().encode(JSON.stringify(data));
}
decode(data: Uint8Array): T {
// Your custom decoding logic here
const value = new TextDecoder().decode(data)
return JSON.parse(value);
}
}Then you can pass the codec instance to RPCServer and RPCClient options like:
const codec = new MyCustomCodec<any>();
const server = new RPCServer({ url: 'nats://localhost:4222', codec: codec });
const client = new RPCClient({ url: 'nats://localhost:4222', codec: codec });๐ชต Logging
This library use logger-alvamind to provide logging functionality, to see debug logs pass {debug: true} to constructor options of RPCServer and RPCClient or set process.env.DEBUG or set NODE_ENV=test.
๐ค Contributing
Contributions are welcome! Feel free to submit pull requests or open issues on the GitHub repository.
Contribution Guidelines
- Fork the repository.
- Create a new branch.
- Make your changes and ensure that tests pass
- Submit a pull request.
๐ Donations
If you find this library helpful, consider making a donation to support further development:
- PayPal: your-paypal-link
- GitHub Sponsors: your-github-sponsors-link
โ ๏ธ Disclaimer
This library is provided "as is" without any warranties. Use at your own risk!
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
Thank you and Happy Coding! ๐๐
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago