@nest-extended/core
The foundation every other package builds on — the generic CRUD controller, the service events runtime, the dynamic configuration module, the interceptors and the shared types.
$npm install @nest-extended/core nestjs-cls
Key exports
| Export | Type | Purpose |
|---|---|---|
NestController<T> | class | Base controller — find / get / create / patch / delete |
NestExtendedModule | module | Dynamic module configured via .forRoot(config) |
NullResponseInterceptor | interceptor | Throws 404 when a GET resolves to null |
UseHooksInterceptor | interceptor | Runs @UseBefore / @UseAfter — registered by forRoot() |
NestServiceBase<D, E> | class | Base for every adapter's NestService: find/get/create/patch/remove, emit(), whenSettled() |
NestServiceEvents<S, D> | class | Base for a {name}.events.ts hooks class |
NestServiceHooks<S, D> | interface | The optional hook signatures |
@ServiceEvents(Service) | decorator | Attaches an events class to its service |
ServiceEventsRegistry | provider | Wires @ServiceEvents() classes at boot |
EventContext<S> | interface | Context passed to every hook |
getEventBus / setEventBus | function | Optional @nestjs/event-emitter bridge |
getCurrentUser<T>() | function | Read the authenticated user from CLS |
NEST_EXTENDED_CONFIG | token | DI token for the resolved config |
NestExtendedConfig | type | Root config (soft delete, query parser, filters, events) |
ServiceOptions<T> | interface | Service contract used by NestController |
PaginatedResponse<D> | type | { total, $limit, $skip, data } |
Generic controller
NestController wires the CRUD routes to any service implementing ServiceOptions:
import { Controller } from '@nestjs/common';
import { NestController } from '@nest-extended/core';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController extends NestController<Cat> {
constructor(service: CatsService) {
super(service);
}
}Service events
New in 1.5.0. Every NestService exposes two versions of each operation.
_find / _get / _create / _patch / _remove do the work; find / get / create /
patch / remove do exactly the same and additionally dispatch lifecycle events. Call the
underscore ones when one service calls another, the plain ones from controllers.
import type { CompanyEvents } from './company.events'; // type-only: avoids a runtime cycle
@Injectable()
export class CompanyService extends NestService<Company, CompanyDocument, CompanyEvents> {
constructor(@InjectModel(Company.name) model: Model<CompanyDocument>) {
super(model, { events: true }); // the default; { events: false } opts out
}
async approve(id: string) {
const doc = await this._patch(id, { status: 'approved' });
this.emit('approve', doc); // custom event, type-checked against CompanyEvents
return doc;
}
}import { EventContext, NestServiceEvents, ServiceEvents } from '@nest-extended/core';
import { CompanyService } from './company.service';
@Injectable()
@ServiceEvents(CompanyService)
export class CompanyEvents extends NestServiceEvents<CompanyService, CompanyDocument> {
// Inline and awaited — what you return replaces the input.
beforeCreate(data: any, ctx: EventContext<CompanyService>) {
return { ...data, slug: slugify(data.name) };
}
// Detached — the response has already been sent, so the return value is ignored
// and a throw is logged rather than surfaced to the client.
async onCreate(company: CompanyDocument, ctx: EventContext<CompanyService>) {
await this.profileModel.create({ company: company._id, createdBy: ctx.user?._id });
}
}Both classes go in the module's providers, and the app must import
NestExtendedModule.forRoot() — that is what discovers and wires them. Look for the boot
log line confirming each wiring:
[NestExtendedEvents] CompanyEvents -> CompanyServiceSee Service Events for every hook, the context object, broadcasting and the limitations.
Module configuration
NestExtendedModule.forRoot({
queryParser: { depth: 20, arrayLimit: 100 },
filters: [MongooseValidationExceptionFilter],
events: true, // default — set false to disable service events and controller hooks app-wide
});