@nestbolt/settings
Events
Listen to settings.created, settings.updated, and settings.deleted lifecycle events.
@nestbolt/settings emits lifecycle events when a setting is written, updated, or deleted. Events are emitted via @nestjs/event-emitter when the package is installed; otherwise they're silently no-op.
Setup
Install @nestjs/event-emitter and register it in your application:
npm install @nestjs/event-emitterimport { Module } from "@nestjs/common";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { SettingsModule } from "@nestbolt/settings";
@Module({
imports: [
EventEmitterModule.forRoot(),
SettingsModule.forRoot(),
],
})
export class AppModule {}If the package isn't installed or EventEmitterModule isn't registered, SettingsService works normally and silently skips event emission.
Event Reference
The package exports a SETTINGS_EVENTS constant for use with @OnEvent():
import { SETTINGS_EVENTS } from "@nestbolt/settings";
SETTINGS_EVENTS.CREATED; // "settings.created"
SETTINGS_EVENTS.UPDATED; // "settings.updated"
SETTINGS_EVENTS.DELETED; // "settings.deleted"settings.created
Emitted by service.set(key, value, options?) when no row existed for key before the write.
import type { SettingCreatedEvent } from "@nestbolt/settings";
interface SettingCreatedEvent {
key: string;
value: unknown;
type: "string" | "number" | "boolean" | "json";
group: string | null;
}import { OnEvent } from "@nestjs/event-emitter";
import { SETTINGS_EVENTS, SettingCreatedEvent } from "@nestbolt/settings";
@Injectable()
export class SettingsAuditor {
@OnEvent(SETTINGS_EVENTS.CREATED)
onCreated(event: SettingCreatedEvent) {
console.log(`Setting created: ${event.key} = ${JSON.stringify(event.value)}`);
}
}settings.updated
Emitted by service.set(key, value, options?) when a row already existed for key. The payload includes both oldValue and newValue:
import type { SettingUpdatedEvent } from "@nestbolt/settings";
interface SettingUpdatedEvent {
key: string;
oldValue: unknown;
newValue: unknown;
type: "string" | "number" | "boolean" | "json";
}@OnEvent(SETTINGS_EVENTS.UPDATED)
onUpdated(event: SettingUpdatedEvent) {
this.audit.record({
action: "setting-changed",
key: event.key,
before: event.oldValue,
after: event.newValue,
});
}The oldValue is the deserialized value as it existed before the write, reconstructed from the stored type column.
settings.deleted
Emitted by service.forget(key) -- but only when the key actually existed. Forgetting a non-existent key is a no-op and emits nothing.
import type { SettingDeletedEvent } from "@nestbolt/settings";
interface SettingDeletedEvent {
key: string;
lastValue: unknown;
}@OnEvent(SETTINGS_EVENTS.DELETED)
onDeleted(event: SettingDeletedEvent) {
this.cacheBus.broadcast("settings:invalidate", event.key);
}Use Cases
Cross-Process Cache Invalidation
The in-memory cache is process-local. When one Node process calls service.set("app.name", "X"), only its own cache is updated -- siblings won't see the change until their entry expires. For tighter consistency, pipe the events to your message broker:
@Injectable()
export class CrossProcessInvalidator {
constructor(private readonly broker: MessageBroker) {}
@OnEvent(SETTINGS_EVENTS.UPDATED)
@OnEvent(SETTINGS_EVENTS.DELETED)
onChange(event: { key: string }) {
this.broker.publish("settings:invalidate", { key: event.key });
}
}
@Injectable()
export class CacheBusListener {
constructor(private readonly settings: SettingsService) {}
@OnEvent("settings:invalidate-received")
invalidate({ key }: { key: string }) {
this.settings.flushCache(); // or invalidate just `key` via your own logic
}
}Audit Logging
Record every change to sensitive settings:
@OnEvent(SETTINGS_EVENTS.UPDATED)
async onUpdated(event: SettingUpdatedEvent) {
await this.auditLog.record({
actor: this.requestContext.userId,
action: "settings.update",
key: event.key,
before: event.oldValue,
after: event.newValue,
});
}Reactive Re-Configuration
Re-bootstrap a service when its setting changes:
@OnEvent(SETTINGS_EVENTS.UPDATED, { async: true })
async onUpdated(event: SettingUpdatedEvent) {
if (event.key === "mail.host" || event.key === "mail.port") {
await this.mailer.reconnect();
}
}Emission Timing
Events are emitted after the database transaction commits but before the call returns. If a listener throws, the exception propagates back to the caller of set() / forget(). Wrap listener bodies in try/catch (or use the async listener pattern) when failures should be isolated from the write path.
The event emitter is resolved as an @Optional() provider with the token "EventEmitter2". When @nestjs/event-emitter isn't installed, the dependency is undefined and emission is skipped silently -- no error, no warning.