Skip to content

Commit d77718e

Browse files
Merge pull request #100 from uniocjs/dev-groupguanfang
feat(nestjs): add interceptor support (#25)
2 parents 3486a42 + 5de5792 commit d77718e

File tree

6 files changed

+79
-5
lines changed

6 files changed

+79
-5
lines changed

.changeset/shy-walls-fly.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/adapter-nestjs": patch
3+
---
4+
5+
fix: prevent potential memory leaks if the observable does not complete [72c5ddd1ff26f0ccf741b7976633e1624c6e0d93]

.changeset/tough-mice-bathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@unioc/adapter-nestjs": minor
3+
---
4+
5+
feat(nestjs): add interceptor support (#25) [54b436bba69ef98cf0a9f60afe9cba08f6faf16a]

packages/adapter/adapter-nestjs/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@unioc/decorator": "workspace:*",
5959
"@unioc/meta": "workspace:*",
6060
"@unioc/shared": "workspace:*",
61-
"@unioc/web": "workspace:*"
61+
"@unioc/web": "workspace:*",
62+
"rxjs": "^7.8.2"
6263
}
6364
}

packages/adapter/adapter-nestjs/src/restful/restful-handler.ts

+47-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type { INestJSMethodParamMetadata, NestJSMethodWrapper } from './method-w
55
import type { NestJSRestfulScanner } from './restful-scanner'
66
import { UnauthorizedException } from '@nestjs/common'
77
import { isClass } from '@unioc/shared'
8+
import { Observable } from 'rxjs'
89
import { ExecutionContextBuilder } from '../execution-context-builder'
10+
import { toResult } from '../utils'
911
import { EndingHandler } from './ending-handler'
1012
import { NestJSMethodOperator } from './method-operator'
1113

@@ -156,6 +158,34 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
156158
throw new UnauthorizedException()
157159
}
158160

161+
protected async executeInterceptors(methodWrapper: NestJSMethodWrapper, params: unknown[], observable: Observable<unknown>): Promise<Observable<unknown>> {
162+
const controllerTarget = methodWrapper
163+
.getControllerOperator()
164+
.getControllerWrapper()
165+
.getClassWrapper()
166+
.getTarget()
167+
168+
const resolvedInterceptors = await methodWrapper.getControllerOperator().getControllerWrapper().getRestfulScanner().mergeAndResolveToInstance(
169+
methodWrapper.getMethodInterceptors(),
170+
methodWrapper.getControllerOperator().getControllerInterceptors(),
171+
)
172+
173+
for (const interceptor of resolvedInterceptors) {
174+
observable = await interceptor.intercept(
175+
new ExecutionContextBuilder(
176+
params,
177+
'http',
178+
controllerTarget,
179+
controllerTarget.prototype[methodWrapper.getPropertyKey()],
180+
),
181+
{
182+
handle: () => observable,
183+
},
184+
)
185+
}
186+
return observable
187+
}
188+
159189
async initialize(ctx: IRestfulConnect.InitializeContext): Promise<void> {
160190
const map = await this.getRestfulScanner()
161191
.getMiddlewareResolver()
@@ -195,10 +225,23 @@ export class NestJSRestfulHandler implements IRestfulConnect.Handler {
195225
methodArguments = await this.buildParams(methodWrapper, ctx)
196226
// 2. Execute guards
197227
await this.executeGuards(methodWrapper, methodArguments)
198-
// 3. Execute the controller method
199-
const result = await methodWrapper.execute(methodArguments, extraOptions)
200-
// 4. TODO: Execute the interceptors
201-
// 5. Send the ending response
228+
// 3. Execute the controller method in an observable
229+
const responseObservable = new Observable((subscribe) => {
230+
methodWrapper.execute(methodArguments, extraOptions)
231+
.then((result) => {
232+
if (result.type === 'result')
233+
subscribe.next(result.value)
234+
else
235+
subscribe.error(result.error)
236+
subscribe.complete()
237+
})
238+
.catch(error => subscribe.error(error))
239+
})
240+
// 4. Execute the before interceptors
241+
const interceptedObservable = await this.executeInterceptors(methodWrapper, methodArguments, responseObservable)
242+
// 5. Get the result from the observable
243+
const result = await toResult(interceptedObservable)
244+
// 6. Send the ending response if not sent
202245
await this._endingHandler.sendConnectEndingIfNotSent(ctx, result)
203246
}
204247
catch (error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { IResult } from '@unioc/shared'
2+
import type { Observable } from 'rxjs'
3+
import { firstValueFrom } from 'rxjs'
4+
5+
/**
6+
* ### 🎦 Convert an observable to a promise
7+
*
8+
* @param observable - The observable to convert
9+
* @returns A promise that resolves to the result of the observable
10+
*/
11+
export function toResult<T>(observable: Observable<T>): Promise<IResult> {
12+
return new Promise((resolve) => {
13+
firstValueFrom(observable)
14+
.then(value => resolve({ type: 'result', value }))
15+
.catch(error => resolve({ type: 'error', error }))
16+
})
17+
}

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)