NestJS dialect
@pbuilder/sdk/nestjs edits the .ts files of a NestJS project. Its handle has every op of
@pbuilder/sdk/typescript plus four ops that import a class and register it in the file’s
@Module decorator. All ops chain on one handle and the file is written once; see
Modify for how a handle works.
import * as nestjs from "@pbuilder/sdk/nestjs";
export const run = async () => { await nestjs .find("src/app.module.ts") .addModuleImport("UsersModule", "./users/users.module") .addProvider("UsersService", "./users/users.service");};// before@Module({ providers: [AppService] })export class AppModule {}
// afterimport { UsersModule } from "./users/users.module";import { UsersService } from "./users/users.service";
@Module({ providers: [AppService, UsersService], imports: [UsersModule] })export class AppModule {}Inherited TypeScript ops
Section titled “Inherited TypeScript ops”The handle carries every TypeScript dialect op: addImport,
addDefaultImport, addNamespaceImport, addTypeImport, addSideEffectImport,
addReExport, addExportAll, addFunction, addVariable, addClass, addArrayElement,
addClassProperty, removeImport, removeExport and removeExportAll. An op the TypeScript dialect gains later is available
here too.
@Module ops
Section titled “@Module ops”Each op takes (name, from) and does two things:
- It adds
import { name } from "from"with the same rules as the TypeScriptaddImport. An existing import ofnamefromfromis reused, and anamealready bound some other way in the file rejects. - It appends
nameto one array of the file’s@Modulemetadata. If@Module()has no argument, or the metadata has no such array, the op creates it. If the array already has an entry namedname, the op leaves it as it is.
| Op | Appends to |
|---|---|
addProvider(name, from) |
providers |
addController(name, from) |
controllers |
addModuleImport(name, from) |
imports, the other Nest modules this module imports |
addExport(name, from) |
exports |
addModuleImport has that name because addImport is already the inherited TypeScript op,
which adds an import statement.
Format preservation
Section titled “Format preservation”A new entry follows the layout of the list it joins. A one-line list stays on one line, a
multi-line list gets a new line at the indentation of its last entry, and a trailing comma
stays trailing. A comma inside a comment after the last entry ([A /* x, y */]) is not taken
for a trailing comma. Everything outside the inserted text is byte-identical.
Rejections
Section titled “Rejections”An op rejects, and the file is left unchanged, when:
- the file has no class decorated with
@Module, or more than one; - the
@Moduleargument is not an object literal (for example@Module(METADATA)); - the target property is not an array literal (for example
providers: PROVIDERS); nameis not a valid identifier, or is already bound in the file some other way.
Like the TypeScript dialect, find(path) accepts any .ts path. A file without @Module
fails only when one of these ops runs.
Entries that are not a class name
Section titled “Entries that are not a class name”The @Module ops take a class name. For an entry such as ConfigModule.forRoot() or
{ provide: TOKEN, useValue: 1 }, use the inherited
addArrayElement with a decorator root, and
add the import yourself:
await nestjs .find("src/app.module.ts") .addImport("ConfigModule", "@nestjs/config") .addArrayElement({ decorator: "Module", path: ["imports"] }, { expression: "ConfigModule.forRoot()" });// before: @Module({ imports: [] })// after: @Module({ imports: [ConfigModule.forRoot()] })Custom edits with .modify()
Section titled “Custom edits with .modify()”Use .modify(fn) for files the ops reject and for edits no op covers. The callback receives the
live ts-morph SourceFile, and the module’s astLibrary export is ts-morph itself. This
example makes the same edit as the addArrayElement call above, by hand:
import * as nestjs from "@pbuilder/sdk/nestjs";
await nestjs .find("src/app.module.ts") .addImport("ConfigModule", "@nestjs/config") .modify((ast) => { const imports = ast .getClassOrThrow("AppModule") .getDecoratorOrThrow("Module") .getArguments()[0]! .asKindOrThrow(nestjs.astLibrary.SyntaxKind.ObjectLiteralExpression) .getPropertyOrThrow("imports") .asKindOrThrow(nestjs.astLibrary.SyntaxKind.PropertyAssignment) .getInitializerIfKindOrThrow(nestjs.astLibrary.SyntaxKind.ArrayLiteralExpression); imports.addElement("ConfigModule.forRoot()"); });Next steps
Section titled “Next steps”- Modify: the TypeScript and React dialects and how a handle works.
- AST libraries: which library and version back each dialect.