Files
nest/packages/core/repl/repl-context.ts
2024-11-26 14:39:51 +01:00

187 lines
5.4 KiB
TypeScript

import {
INestApplicationContext,
InjectionToken,
Logger,
} from '@nestjs/common';
import { ApplicationConfig } from '../application-config';
import { ModuleRef, NestContainer } from '../injector';
import { InternalCoreModule } from '../injector/internal-core-module/internal-core-module';
import { Module } from '../injector/module';
import {
DebugReplFn,
GetReplFn,
HelpReplFn,
MethodsReplFn,
ResolveReplFn,
SelectReplFn,
} from './native-functions';
import { ReplFunction } from './repl-function';
import type { ReplFunctionClass } from './repl.interfaces';
type ModuleKey = string;
export type ModuleDebugEntry = {
controllers: Record<string, InjectionToken>;
providers: Record<string, InjectionToken>;
};
type ReplScope = Record<string, any>;
export class ReplContext {
public readonly logger = new Logger(ReplContext.name);
public debugRegistry: Record<ModuleKey, ModuleDebugEntry> = {};
public readonly globalScope: ReplScope = Object.create(null);
public readonly nativeFunctions = new Map<
string,
InstanceType<ReplFunctionClass>
>();
private readonly container: NestContainer;
constructor(
public readonly app: INestApplicationContext,
nativeFunctionsClassRefs?: ReplFunctionClass[],
) {
this.container = (app as any).container; // Using `any` because `app.container` is not public.
this.initializeContext();
this.initializeNativeFunctions(nativeFunctionsClassRefs || []);
}
public writeToStdout(text: string) {
process.stdout.write(text);
}
private initializeContext() {
const modules = this.container.getModules();
modules.forEach(moduleRef => {
let moduleName = moduleRef.metatype.name;
if (moduleName === InternalCoreModule.name) {
return;
}
if (this.globalScope[moduleName]) {
moduleName += ` (${moduleRef.token})`;
}
this.introspectCollection(moduleRef, moduleName, 'providers');
this.introspectCollection(moduleRef, moduleName, 'controllers');
// For in REPL auto-complete functionality
Object.defineProperty(this.globalScope, moduleName, {
value: moduleRef.metatype,
configurable: false,
enumerable: true,
});
});
}
private introspectCollection(
moduleRef: Module,
moduleKey: ModuleKey,
collection: keyof ModuleDebugEntry,
) {
const moduleDebugEntry = {};
moduleRef[collection].forEach(({ token }) => {
const stringifiedToken = this.stringifyToken(token);
if (
stringifiedToken === ApplicationConfig.name ||
stringifiedToken === moduleRef.metatype.name
) {
return;
}
if (!this.globalScope[stringifiedToken]) {
// For in REPL auto-complete functionality
Object.defineProperty(this.globalScope, stringifiedToken, {
value: token,
configurable: false,
enumerable: true,
});
}
if (stringifiedToken === ModuleRef.name) {
return;
}
moduleDebugEntry[stringifiedToken] = token;
});
this.debugRegistry[moduleKey] = {
...this.debugRegistry?.[moduleKey],
[collection]: moduleDebugEntry,
};
}
private stringifyToken(token: unknown): string {
return typeof token !== 'string'
? typeof token === 'function'
? token.name
: (token?.toString() as string)
: `"${token}"`;
}
private addNativeFunction(
NativeFunctionRef: ReplFunctionClass,
): InstanceType<ReplFunctionClass>[] {
const nativeFunction = new NativeFunctionRef(this);
const nativeFunctions = [nativeFunction];
this.nativeFunctions.set(nativeFunction.fnDefinition.name, nativeFunction);
nativeFunction.fnDefinition.aliases?.forEach(aliasName => {
const aliasNativeFunction: InstanceType<ReplFunctionClass> =
Object.create(nativeFunction);
aliasNativeFunction.fnDefinition = {
name: aliasName,
description: aliasNativeFunction.fnDefinition.description,
signature: aliasNativeFunction.fnDefinition.signature,
};
this.nativeFunctions.set(aliasName, aliasNativeFunction);
nativeFunctions.push(aliasNativeFunction);
});
return nativeFunctions;
}
private registerFunctionIntoGlobalScope(
nativeFunction: InstanceType<ReplFunctionClass>,
) {
// Bind the method to REPL's context:
this.globalScope[nativeFunction.fnDefinition.name] =
nativeFunction.action.bind(nativeFunction);
// Load the help trigger as a `help` getter on each native function:
const functionBoundRef: ReplFunction['action'] =
this.globalScope[nativeFunction.fnDefinition.name];
Object.defineProperty(functionBoundRef, 'help', {
enumerable: false,
configurable: false,
get: () =>
// Dynamically builds the help message as will unlikely to be called
// several times.
this.writeToStdout(nativeFunction.makeHelpMessage()),
});
}
private initializeNativeFunctions(
nativeFunctionsClassRefs: ReplFunctionClass[],
): void {
const builtInFunctionsClassRefs: ReplFunctionClass[] = [
HelpReplFn,
GetReplFn,
ResolveReplFn,
SelectReplFn,
DebugReplFn,
MethodsReplFn,
];
builtInFunctionsClassRefs
.concat(nativeFunctionsClassRefs)
.forEach(NativeFunction => {
const nativeFunctions = this.addNativeFunction(NativeFunction);
nativeFunctions.forEach(nativeFunction => {
this.registerFunctionIntoGlobalScope(nativeFunction);
});
});
}
}