@loopback/context
Version:
Facilities to manage artifacts and their dependencies in your Node.js applications. The module exposes TypeScript/JavaScript APIs and decorators to register artifacts, declare dependencies, and resolve artifacts by keys. It also serves as an IoC container
127 lines (117 loc) • 3.76 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
// Node module: @loopback/context
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {Context} from './context';
import {invokeMethodWithInterceptors} from './interceptor';
import {InvocationArgs, InvocationSource} from './invocation';
import {ResolutionSession} from './resolution-session';
import {ValueOrPromise} from './value-promise';
/**
* Create the Promise type for `T`. If `T` extends `Promise`, the type is `T`,
* otherwise the type is `ValueOrPromise<T>`.
*/
export type AsValueOrPromise<T> =
T extends Promise<unknown> ? T : ValueOrPromise<T>;
/**
* The intercepted variant of a function to return `ValueOrPromise<T>`.
* If `T` is not a function, the type is `T`.
*/
export type AsInterceptedFunction<T> = T extends (
...args: InvocationArgs
) => infer R
? (...args: Parameters<T>) => AsValueOrPromise<R>
: T;
/**
* The proxy type for `T`. The return type for any method of `T` with original
* return type `R` becomes `ValueOrPromise<R>` if `R` does not extend `Promise`.
* Property types stay untouched.
*
* @example
* ```ts
* class MyController {
* name: string;
*
* greet(name: string): string {
* return `Hello, ${name}`;
* }
*
* async hello(name: string) {
* return `Hello, ${name}`;
* }
* }
* ```
*
* `AsyncProxy<MyController>` will be:
* ```ts
* {
* name: string; // the same as MyController
* greet(name: string): ValueOrPromise<string>; // the return type becomes `ValueOrPromise<string>`
* hello(name: string): Promise<string>; // the same as MyController
* }
* ```
*/
export type AsyncProxy<T> = {[P in keyof T]: AsInterceptedFunction<T[P]>};
/**
* Invocation source for injected proxies. It wraps a snapshot of the
* `ResolutionSession` that tracks the binding/injection stack.
*/
export class ProxySource implements InvocationSource<ResolutionSession> {
type = 'proxy';
constructor(readonly value: ResolutionSession) {}
toString() {
return this.value.getBindingPath();
}
}
/**
* A proxy handler that applies interceptors
*
* See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy
*/
export class InterceptionHandler<T extends object> implements ProxyHandler<T> {
constructor(
private context = new Context(),
private session?: ResolutionSession,
private source?: InvocationSource,
) {}
get(target: T, propertyName: PropertyKey, receiver: unknown) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const targetObj = target as any;
if (typeof propertyName !== 'string') return targetObj[propertyName];
const propertyOrMethod = targetObj[propertyName];
if (typeof propertyOrMethod === 'function') {
return (...args: InvocationArgs) => {
return invokeMethodWithInterceptors(
this.context,
target,
propertyName,
args,
{
source:
this.source ?? (this.session && new ProxySource(this.session)),
},
);
};
} else {
return propertyOrMethod;
}
}
}
/**
* Create a proxy that applies interceptors for method invocations
* @param target - Target class or object
* @param context - Context object
* @param session - Resolution session
* @param source - Invocation source
*/
export function createProxyWithInterceptors<T extends object>(
target: T,
context?: Context,
session?: ResolutionSession,
source?: InvocationSource,
): AsyncProxy<T> {
return new Proxy(
target,
new InterceptionHandler(context, ResolutionSession.fork(session), source),
) as AsyncProxy<T>;
}