Skip to content

Commit

Permalink
refactor(nestjs-connectrpc): decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
TorinAsakura committed Oct 30, 2024
1 parent 317226c commit 80fab75
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 50 deletions.
83 changes: 49 additions & 34 deletions packages/nestjs-connectrpc/src/connectrpc.decorators.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
import type { ServiceType } from '@bufbuild/protobuf'
import type { ServiceType } from '@bufbuild/protobuf'

import type { ConstructorWithPrototype } from './connectrpc.interfaces.js'
import type { FunctionPropertyDescriptor } from './connectrpc.interfaces.js'
import type { MethodKey } from './connectrpc.interfaces.js'
import type { MethodKeys } from './connectrpc.interfaces.js'
import type { ConstructorWithPrototype } from './connectrpc.interfaces.js'

import { MessagePattern } from '@nestjs/microservices'
import type { FunctionPropertyDescriptor } from './connectrpc.interfaces.js'

import { CONNECTRPC_TRANSPORT } from './connectrpc.constants.js'
import { METHOD_DECORATOR_KEY } from './connectrpc.constants.js'
import { STREAM_METHOD_DECORATOR_KEY } from './connectrpc.constants.js'
import { MethodType } from './connectrpc.interfaces.js'
import { CustomMetadataStore } from './custom-metadata.storage.js'
import { createConnectRpcMethodMetadata } from './utils/router.utils.js'
import type { MethodKey } from './connectrpc.interfaces.js'

import type { MethodKeys } from './connectrpc.interfaces.js'

import { MessagePattern } from '@nestjs/microservices'

import { CONNECTRPC_TRANSPORT } from './connectrpc.constants.js'
import { METHOD_DECORATOR_KEY } from './connectrpc.constants.js'
import { STREAM_METHOD_DECORATOR_KEY } from './connectrpc.constants.js'
import { MethodType } from './connectrpc.interfaces.js'
import { CustomMetadataStore } from './custom-metadata.storage.js'
import { createConnectRpcMethodMetadata } from './utils/router.utils.js'

/**
* Type guard to check if a given descriptor is a function property descriptor.
* @param {PropertyDescriptor | undefined} descriptor - The descriptor to check.
* @returns {descriptor is FunctionPropertyDescriptor} - True if descriptor is for a function.
*/
function isFunctionPropertyDescriptor(
descriptor: PropertyDescriptor | undefined
): descriptor is FunctionPropertyDescriptor {
return descriptor !== undefined && typeof descriptor.value === 'function'
}

/**
* ConnectRpcService decorator to register RPC services and their handlers.
* @param {ServiceType} serviceName - The service type from protobuf.
* @returns {ClassDecorator} - Class decorator function.
*/
export const ConnectRpcService = (serviceName: ServiceType): ClassDecorator =>
(target: ConstructorWithPrototype): void => {
const processMethodKey = (methodImpl: MethodKey): void => {
const functionName = methodImpl.key
const { methodType } = methodImpl
// Получаем все зарегистрированные методы и объединяем их
const unaryMethodKeys: MethodKeys = Reflect.getMetadata(METHOD_DECORATOR_KEY, target) || []
const streamMethodKeys: MethodKeys =
Reflect.getMetadata(STREAM_METHOD_DECORATOR_KEY, target) || []
const allMethodKeys = [...unaryMethodKeys, ...streamMethodKeys] as MethodKey[]

Check failure on line 43 in packages/nestjs-connectrpc/src/connectrpc.decorators.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/array-type): Array type using 'MethodKey[]' is forbidden. Use 'Array<MethodKey>' instead.

Array type using 'MethodKey[]' is forbidden. Use 'Array<MethodKey>' instead.
Raw output
  40 |     const streamMethodKeys: MethodKeys =
  41 |       Reflect.getMetadata(STREAM_METHOD_DECORATOR_KEY, target) || []
> 42 |     const allMethodKeys = [...unaryMethodKeys, ...streamMethodKeys] as MethodKey[]
     |                                                                        ^
  43 |
  44 |     allMethodKeys.forEach((methodImpl) => {
  45 |       const { key: functionName, methodType } = methodImpl
allMethodKeys.forEach((methodImpl) => {
const { key: functionName, methodType } = methodImpl
const descriptor = Object.getOwnPropertyDescriptor(target.prototype, functionName)

if (isFunctionPropertyDescriptor(descriptor)) {
if (descriptor && isFunctionPropertyDescriptor(descriptor)) {
const metadata = createConnectRpcMethodMetadata(
descriptor.value,
functionName,
Expand All @@ -37,49 +54,47 @@ export const ConnectRpcService = (serviceName: ServiceType): ClassDecorator =>
methodType
)

const customMetadataStore = CustomMetadataStore.getInstance()
customMetadataStore.set(serviceName.typeName, serviceName)

CustomMetadataStore.getInstance().set(serviceName.typeName, serviceName)
MessagePattern(metadata, CONNECTRPC_TRANSPORT)(target.prototype, functionName, descriptor)
}
}

const unaryMethodKeys: MethodKeys = Reflect.getMetadata(METHOD_DECORATOR_KEY, target) || []
const streamMethodKeys: MethodKeys =
Reflect.getMetadata(STREAM_METHOD_DECORATOR_KEY, target) || []

unaryMethodKeys.forEach((methodImpl) => {
processMethodKey(methodImpl)
})
streamMethodKeys.forEach((methodImpl) => {
processMethodKey(methodImpl)
})
}

/**
* Decorator for unary RPC methods.
* Registers the method as a unary RPC with no streaming.
* @returns {MethodDecorator} - Method decorator function.
*/
export const ConnectRpcMethod = (): MethodDecorator => (target: object, key: string | symbol) => {
const metadata: MethodKey = {
key: key.toString(),
methodType: MethodType.NO_STREAMING,
}

const existingMethods: Set<MethodKey> =
Reflect.getMetadata(METHOD_DECORATOR_KEY, target.constructor) || new Set()
const existingMethods =
(Reflect.getMetadata(METHOD_DECORATOR_KEY, target.constructor) as Set<MethodKey>) || new Set()

if (!existingMethods.has(metadata)) {
existingMethods.add(metadata)
Reflect.defineMetadata(METHOD_DECORATOR_KEY, existingMethods, target.constructor)
}
}

/**
* Decorator for streaming RPC methods.
* Registers the method as a streaming RPC with RX_STREAMING type.
* @returns {MethodDecorator} - Method decorator function.
*/
export const ConnectRpcStreamMethod = (): MethodDecorator =>
(target: object, key: string | symbol) => {
const metadata: MethodKey = {
key: key.toString(),
methodType: MethodType.RX_STREAMING,
}

const existingMethods: Set<MethodKey> =
Reflect.getMetadata(STREAM_METHOD_DECORATOR_KEY, target.constructor) || new Set()
const existingMethods =
(Reflect.getMetadata(STREAM_METHOD_DECORATOR_KEY, target.constructor) as Set<MethodKey>) ||
new Set()

if (!existingMethods.has(metadata)) {
existingMethods.add(metadata)
Expand Down
11 changes: 7 additions & 4 deletions packages/nestjs-connectrpc/src/utils/async.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { ResultOrDeferred } from '../connectrpc.interfaces.js'
import { Observable, from, Subject } from 'rxjs'
import { lastValueFrom } from 'rxjs'
import type { ResultOrDeferred } from '../connectrpc.interfaces.js'

import { Observable } from 'rxjs'
import { from } from 'rxjs'
import { Subject } from 'rxjs'
import { lastValueFrom } from 'rxjs'

/**
* Type guard to check if a given input is an AsyncGenerator.
Expand Down Expand Up @@ -100,7 +103,7 @@ export const transformToObservable = <T>(resultOrDeferred: ResultOrDeferred<T>):
}
if (hasSubscribe(resultOrDeferred)) {
return new Observable<T>((subscriber) => {
(resultOrDeferred as any).subscribe({
;(resultOrDeferred as any).subscribe({
next: (value: any) => subscriber.next(value as T),

Check failure on line 107 in packages/nestjs-connectrpc/src/utils/async.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/no-unsafe-call): Unsafe call of an `any` typed value.

Unsafe call of an `any` typed value.
Raw output
  104 |   if (hasSubscribe(resultOrDeferred)) {
  105 |     return new Observable<T>((subscriber) => {
> 106 |       ;(resultOrDeferred as any).subscribe({
      |        ^
  107 |         next: (value: any) => subscriber.next(value as T),
  108 |         error: (error: any) => subscriber.error(error),
  109 |         complete: () => subscriber.complete(),
error: (error: any) => subscriber.error(error),

Check failure on line 108 in packages/nestjs-connectrpc/src/utils/async.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/no-confusing-void-expression): Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.

Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.
Raw output
  105 |     return new Observable<T>((subscriber) => {
  106 |       ;(resultOrDeferred as any).subscribe({
> 107 |         next: (value: any) => subscriber.next(value as T),
      |                               ^
  108 |         error: (error: any) => subscriber.error(error),
  109 |         complete: () => subscriber.complete(),
  110 |       })
complete: () => subscriber.complete(),

Check failure on line 109 in packages/nestjs-connectrpc/src/utils/async.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/no-confusing-void-expression): Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.

Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.
Raw output
  106 |       ;(resultOrDeferred as any).subscribe({
  107 |         next: (value: any) => subscriber.next(value as T),
> 108 |         error: (error: any) => subscriber.error(error),
      |                                ^
  109 |         complete: () => subscriber.complete(),
  110 |       })
  111 |     })
Expand Down
30 changes: 18 additions & 12 deletions packages/nestjs-connectrpc/src/utils/router.utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { ServiceType } from '@bufbuild/protobuf'
import type { ConnectRouter } from '@connectrpc/connect'
import type { ServiceImpl } from '@connectrpc/connect'
import type { MessageHandler } from '@nestjs/microservices'
import type { Observable } from 'rxjs'
import type { ServiceType } from '@bufbuild/protobuf'
import type { ConnectRouter } from '@connectrpc/connect'
import type { ServiceImpl } from '@connectrpc/connect'
import type { MessageHandler } from '@nestjs/microservices'
import type { Observable } from 'rxjs'

import type { ConnectRpcPattern } from '../connectrpc.interfaces.js'
import type { ConnectRpcPattern } from '../connectrpc.interfaces.js'
import type { CustomMetadataStore } from '../custom-metadata.storage.js'

import { lastValueFrom } from 'rxjs'
import { MethodType } from '../connectrpc.interfaces.js'
import { toAsyncGenerator } from './async.utils.js'
import { transformToObservable } from './async.utils.js'
import { lastValueFrom } from 'rxjs'

import { MethodType } from '../connectrpc.interfaces.js'
import { toAsyncGenerator } from './async.utils.js'
import { transformToObservable } from './async.utils.js'

/**
* Creates a JSON string pattern for a given service, method, and streaming type.
Expand Down Expand Up @@ -73,9 +74,14 @@ export const createServiceHandlersMap = (
break

case MethodType.RX_STREAMING:
serviceHandlersMap[service][rpc] = async function* (request: unknown, context: unknown) {
serviceHandlersMap[service][rpc] = async function* (
request: unknown,

Check failure on line 78 in packages/nestjs-connectrpc/src/utils/router.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/explicit-function-return-type): Missing return type on function.

Missing return type on function.
Raw output
  75 |
  76 |           case MethodType.RX_STREAMING:
> 77 |             serviceHandlersMap[service][rpc] = async function* (
     |                                                ^
  78 |               request: unknown,
  79 |               context: unknown
  80 |             ) {

Check warning on line 78 in packages/nestjs-connectrpc/src/utils/router.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(func-names): Unexpected unnamed async generator function.

Unexpected unnamed async generator function.
Raw output
  75 |
  76 |           case MethodType.RX_STREAMING:
> 77 |             serviceHandlersMap[service][rpc] = async function* (
     |                                                ^
  78 |               request: unknown,
  79 |               context: unknown
  80 |             ) {
context: unknown
) {
const streamOrValue = await handlerMetadata(request, context)
yield* toAsyncGenerator(streamOrValue as Observable<unknown> | AsyncGenerator<unknown>)
yield* toAsyncGenerator(
streamOrValue as Observable<unknown> | AsyncGenerator<unknown>
)

Check failure on line 84 in packages/nestjs-connectrpc/src/utils/router.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/sort-type-constituents): Union type constituents must be sorted.

Union type constituents must be sorted.
Raw output
  81 |               const streamOrValue = await handlerMetadata(request, context)
  82 |               yield* toAsyncGenerator(
> 83 |                 streamOrValue as Observable<unknown> | AsyncGenerator<unknown>
     |                                  ^
  84 |               )
  85 |             }
  86 |             break

Check failure on line 84 in packages/nestjs-connectrpc/src/utils/router.utils.ts

View workflow job for this annotation

GitHub Actions / Lint

(@typescript-eslint/no-unnecessary-type-arguments): This is the default value for this type parameter, so it can be omitted.

This is the default value for this type parameter, so it can be omitted.
Raw output
  81 |               const streamOrValue = await handlerMetadata(request, context)
  82 |               yield* toAsyncGenerator(
> 83 |                 streamOrValue as Observable<unknown> | AsyncGenerator<unknown>
     |                                                                       ^
  84 |               )
  85 |             }
  86 |             break
}
break

Expand Down

0 comments on commit 80fab75

Please sign in to comment.