From ad43c8c8529ec3d8a452b23cfaee77369b8c17a9 Mon Sep 17 00:00:00 2001 From: yasser Date: Wed, 18 Mar 2026 15:39:48 +0000 Subject: [PATCH] implemented nestjs module --- src/nest/constants.ts | 78 +++++++++ src/nest/index.ts | 18 ++ src/nest/interfaces.ts | 288 ++++++++++++++++++++++++++++++++ src/nest/module.ts | 370 ++++++++++++++++++++++++++++++++++++++++- src/nest/providers.ts | 253 ++++++++++++++++++++++++++++ 5 files changed, 1000 insertions(+), 7 deletions(-) create mode 100644 src/nest/constants.ts create mode 100644 src/nest/interfaces.ts create mode 100644 src/nest/providers.ts diff --git a/src/nest/constants.ts b/src/nest/constants.ts new file mode 100644 index 0000000..e93efa3 --- /dev/null +++ b/src/nest/constants.ts @@ -0,0 +1,78 @@ +/** + * ============================================================================ + * AUDIT KIT MODULE - DEPENDENCY INJECTION TOKENS + * ============================================================================ + * + * Constants used for dependency injection in AuditKit module. + * + * Token Naming Convention: + * - UPPER_SNAKE_CASE for all tokens + * - Descriptive names indicating what the token represents + * + * @packageDocumentation + */ + +/** + * Injection token for AuditKit module configuration options. + * + * @example Injecting options in a service + * ```typescript + * constructor( + * @Inject(AUDIT_KIT_OPTIONS) + * private readonly options: AuditKitModuleOptions + * ) {} + * ``` + */ +export const AUDIT_KIT_OPTIONS = Symbol("AUDIT_KIT_OPTIONS"); + +/** + * Injection token for the audit log repository. + * + * @example Injecting repository + * ```typescript + * constructor( + * @Inject(AUDIT_REPOSITORY) + * private readonly repository: IAuditLogRepository + * ) {} + * ``` + */ +export const AUDIT_REPOSITORY = Symbol("AUDIT_REPOSITORY"); + +/** + * Injection token for the ID generator. + * + * @example Injecting ID generator + * ```typescript + * constructor( + * @Inject(ID_GENERATOR) + * private readonly idGenerator: IIdGenerator + * ) {} + * ``` + */ +export const ID_GENERATOR = Symbol("ID_GENERATOR"); + +/** + * Injection token for the timestamp provider. + * + * @example Injecting timestamp provider + * ```typescript + * constructor( + * @Inject(TIMESTAMP_PROVIDER) + * private readonly timestampProvider: ITimestampProvider + * ) {} + * ``` + */ +export const TIMESTAMP_PROVIDER = Symbol("TIMESTAMP_PROVIDER"); + +/** + * Injection token for the change detector. + * + * @example Injecting change detector + * ```typescript + * constructor( + * @Inject(CHANGE_DETECTOR) + * private readonly changeDetector: IChangeDetector + * ) {} + * ``` + */ +export const CHANGE_DETECTOR = Symbol("CHANGE_DETECTOR"); diff --git a/src/nest/index.ts b/src/nest/index.ts index b999044..1f76136 100644 --- a/src/nest/index.ts +++ b/src/nest/index.ts @@ -1 +1,19 @@ +/** + * ============================================================================ + * NEST LAYER - PUBLIC EXPORTS + * ============================================================================ + * + * Exports for NestJS integration. + * + * Components: + * - AuditKitModule: Main dynamic module + * - Interfaces: Configuration types + * - Constants: DI tokens + * + * @packageDocumentation + */ + +// Module and configuration export * from "./module"; +export * from "./interfaces"; +export * from "./constants"; diff --git a/src/nest/interfaces.ts b/src/nest/interfaces.ts new file mode 100644 index 0000000..a37dcc5 --- /dev/null +++ b/src/nest/interfaces.ts @@ -0,0 +1,288 @@ +/** + * ============================================================================ + * AUDIT KIT MODULE - CONFIGURATION INTERFACES + * ============================================================================ + * + * Type definitions for configuring AuditKitModule. + * + * Module Registration Patterns: + * 1. register() - Synchronous configuration with static values + * 2. registerAsync() - Async configuration with useFactory/useClass/useExisting + * + * @packageDocumentation + */ + +import type { ModuleMetadata, Type } from "@nestjs/common"; +import type { Model } from "mongoose"; + +import type { AuditLogDocument } from "../infra/repositories/mongodb/audit-log.schema"; + +// ============================================================================ +// REPOSITORY CONFIGURATION +// ============================================================================ + +/** + * MongoDB repository configuration. + */ +export interface MongoDbRepositoryConfig { + /** + * Repository type identifier. + */ + type: "mongodb"; + + /** + * MongoDB connection URI. + * Required if not providing a model instance. + * + * @example 'mongodb://localhost:27017/auditdb' + */ + uri?: string; + + /** + * MongoDB database name. + */ + database?: string; + + /** + * Pre-configured Mongoose model for audit logs. + * If provided, uri and database are ignored. + */ + model?: Model; +} + +/** + * In-memory repository configuration. + * Useful for testing and simple deployments. + */ +export interface InMemoryRepositoryConfig { + /** + * Repository type identifier. + */ + type: "in-memory"; + + /** + * Optional initial data to seed the repository. + */ + initialData?: never; // Placeholder for future implementation +} + +/** + * Repository configuration union type. + */ +export type RepositoryConfig = MongoDbRepositoryConfig | InMemoryRepositoryConfig; + +// ============================================================================ +// UTILITY PROVIDER CONFIGURATION +// ============================================================================ + +/** + * ID generator configuration. + */ +export interface IdGeneratorConfig { + /** + * Generator type. + * Currently only 'nanoid' is supported. + */ + type?: "nanoid"; + + /** + * Default length for generated IDs. + * @default 21 + */ + defaultLength?: number; + + /** + * Custom alphabet for ID generation. + * @default 'A-Za-z0-9_-' + */ + defaultAlphabet?: string; +} + +/** + * Timestamp provider configuration. + */ +export interface TimestampProviderConfig { + /** + * Provider type. + * Currently only 'system' is supported. + */ + type?: "system"; + + /** + * Default timezone for timestamp operations. + * @default 'utc' + */ + defaultTimezone?: "utc" | "local"; + + /** + * Default precision for timestamps. + * @default 'millisecond' + */ + defaultPrecision?: "second" | "millisecond" | "microsecond"; +} + +/** + * Change detector configuration. + */ +export interface ChangeDetectorConfig { + /** + * Detector type. + * Currently only 'deep-diff' is supported. + */ + type?: "deep-diff"; +} + +// ============================================================================ +// MAIN MODULE OPTIONS +// ============================================================================ + +/** + * Configuration options for AuditKitModule. + * + * @example Basic configuration with MongoDB + * ```typescript + * AuditKitModule.register({ + * repository: { + * type: 'mongodb', + * uri: 'mongodb://localhost:27017/auditdb', + * database: 'auditdb' + * } + * }) + * ``` + * + * @example Configuration with in-memory repository + * ```typescript + * AuditKitModule.register({ + * repository: { + * type: 'in-memory' + * } + * }) + * ``` + * + * @example Full configuration with custom providers + * ```typescript + * AuditKitModule.register({ + * repository: { + * type: 'mongodb', + * uri: process.env.MONGO_URI + * }, + * idGenerator: { + * type: 'nanoid', + * defaultLength: 16 + * }, + * timestampProvider: { + * type: 'system', + * defaultTimezone: 'utc' + * }, + * changeDetector: { + * type: 'deep-diff' + * } + * }) + * ``` + */ +export interface AuditKitModuleOptions { + /** + * Repository configuration. + * Determines where audit logs are persisted. + */ + repository: RepositoryConfig; + + /** + * ID generator configuration. + * Optional - defaults to nanoid with standard settings. + */ + idGenerator?: IdGeneratorConfig; + + /** + * Timestamp provider configuration. + * Optional - defaults to system clock with UTC. + */ + timestampProvider?: TimestampProviderConfig; + + /** + * Change detector configuration. + * Optional - defaults to deep-diff detector. + */ + changeDetector?: ChangeDetectorConfig; +} + +// ============================================================================ +// ASYNC MODULE OPTIONS +// ============================================================================ + +/** + * Factory function for creating AuditKit options asynchronously. + */ +export interface AuditKitModuleOptionsFactory { + /** + * Creates module options. + * Can be async (returns Promise) or sync. + */ + createAuditKitOptions(): Promise | AuditKitModuleOptions; +} + +/** + * Async configuration options for AuditKitModule. + * + * Supports three patterns: + * 1. useFactory - Provide a factory function + * 2. useClass - Provide a class that implements AuditKitModuleOptionsFactory + * 3. useExisting - Reuse an existing provider + * + * @example With useFactory + * ```typescript + * AuditKitModule.registerAsync({ + * imports: [ConfigModule], + * inject: [ConfigService], + * useFactory: (config: ConfigService) => ({ + * repository: { + * type: 'mongodb', + * uri: config.get('MONGO_URI') + * } + * }) + * }) + * ``` + * + * @example With useClass + * ```typescript + * AuditKitModule.registerAsync({ + * useClass: AuditKitConfigService + * }) + * ``` + * + * @example With useExisting + * ```typescript + * AuditKitModule.registerAsync({ + * useExisting: AuditKitConfigService + * }) + * ``` + */ +export interface AuditKitModuleAsyncOptions extends Pick { + /** + * Factory function to create module options. + * Can inject dependencies via the inject array. + */ + useFactory?: ( + // eslint-disable-next-line no-unused-vars + ...args: any[] + ) => Promise | AuditKitModuleOptions; + + /** + * Dependencies to inject into the factory function. + */ + + inject?: any[]; + + /** + * Class that implements AuditKitModuleOptionsFactory. + * Will be instantiated by NestJS. + */ + useClass?: Type; + + /** + * Existing provider token that implements AuditKitModuleOptionsFactory. + * Reuses an already registered provider. + */ + + useExisting?: Type; +} diff --git a/src/nest/module.ts b/src/nest/module.ts index f72fb46..755a579 100644 --- a/src/nest/module.ts +++ b/src/nest/module.ts @@ -1,17 +1,373 @@ +/** + * ============================================================================ + * AUDIT KIT MODULE - MAIN MODULE + * ============================================================================ + * + * NestJS dynamic module for AuditKit. + * + * Registration Patterns: + * 1. register() - Synchronous registration with static configuration + * 2. registerAsync() - Async registration with factory/class/existing provider + * + * Module Exports: + * - AuditService: Core service for creating and querying audit logs + * - All utility providers (ID generator, timestamp, change detector) + * - Repository implementation (MongoDB or In-Memory) + * + * @packageDocumentation + */ + import { Module } from "@nestjs/common"; import type { DynamicModule } from "@nestjs/common"; +import type { ConnectOptions } from "mongoose"; +import { connect } from "mongoose"; + +import { AuditService } from "../core/audit.service"; +import type { IAuditLogRepository } from "../core/ports/audit-repository.port"; +import type { IChangeDetector } from "../core/ports/change-detector.port"; +import type { IIdGenerator } from "../core/ports/id-generator.port"; +import type { ITimestampProvider } from "../core/ports/timestamp-provider.port"; +import { DeepDiffChangeDetector } from "../infra/providers/change-detector/deep-diff-change-detector"; +import { NanoidIdGenerator } from "../infra/providers/id-generator/nanoid-id-generator"; +import { SystemTimestampProvider } from "../infra/providers/timestamp/system-timestamp-provider"; +import { InMemoryAuditRepository } from "../infra/repositories/in-memory/in-memory-audit.repository"; +import { AuditLogSchema } from "../infra/repositories/mongodb/audit-log.schema"; +import { MongoAuditRepository } from "../infra/repositories/mongodb/mongo-audit.repository"; + +import { + AUDIT_KIT_OPTIONS, + AUDIT_REPOSITORY, + CHANGE_DETECTOR, + ID_GENERATOR, + TIMESTAMP_PROVIDER, +} from "./constants"; +import type { AuditKitModuleAsyncOptions, AuditKitModuleOptions } from "./interfaces"; +import { createAuditKitAsyncProviders, createAuditKitProviders } from "./providers"; -export type DeveloperKitModuleOptions = Record; +// ============================================================================ +// AUDIT KIT MODULE +// ============================================================================ +/** + * AuditKit NestJS module. + * + * Provides comprehensive audit logging capabilities with: + * - Multi-repository support (MongoDB, In-Memory) + * - Pluggable utility providers + * - Type-safe configuration + * - Synchronous and asynchronous registration + * + * @example Basic synchronous registration + * ```typescript + * @Module({ + * imports: [ + * AuditKitModule.register({ + * repository: { + * type: 'mongodb', + * uri: 'mongodb://localhost:27017/auditdb' + * } + * }) + * ] + * }) + * export class AppModule {} + * ``` + * + * @example Async registration with ConfigService + * ```typescript + * @Module({ + * imports: [ + * AuditKitModule.registerAsync({ + * imports: [ConfigModule], + * inject: [ConfigService], + * useFactory: (config: ConfigService) => ({ + * repository: { + * type: 'mongodb', + * uri: config.get('MONGO_URI') + * } + * }) + * }) + * ] + * }) + * export class AppModule {} + * ``` + * + * @example Using AuditService in your code + * ```typescript + * @Injectable() + * export class UserService { + * constructor(private readonly auditService: AuditService) {} + * + * async updateUser(id: string, updates: UpdateUserDto, actor: Actor) { + * const user = await this.userRepository.findById(id); + * const updated = await this.userRepository.update(id, updates); + * + * // Log the change + * await this.auditService.log({ + * action: 'UPDATE', + * actor, + * resource: { type: 'User', id: user.id, label: user.email }, + * before: user, + * after: updated + * }); + * + * return updated; + * } + * } + * ``` + */ @Module({}) -export class DeveloperKitModule { - static register(_options: DeveloperKitModuleOptions = {}): DynamicModule { - void _options; +export class AuditKitModule { + /** + * Registers AuditKit module with static configuration. + * + * Use this when all configuration values are known at compile/startup time. + * + * @param options - Module configuration options + * @returns Dynamic module + * + * @example With MongoDB + * ```typescript + * AuditKitModule.register({ + * repository: { + * type: 'mongodb', + * uri: 'mongodb://localhost:27017/auditdb', + * database: 'auditdb' + * } + * }) + * ``` + * + * @example With In-Memory + * ```typescript + * AuditKitModule.register({ + * repository: { + * type: 'in-memory' + * } + * }) + * ``` + * + * @example With custom providers + * ```typescript + * AuditKitModule.register({ + * repository: { + * type: 'mongodb', + * uri: process.env.MONGO_URI + * }, + * idGenerator: { + * type: 'nanoid', + * defaultLength: 16 + * }, + * timestampProvider: { + * type: 'system', + * defaultTimezone: 'utc' + * } + * }) + * ``` + */ + static register(options: AuditKitModuleOptions): DynamicModule { + const providers = createAuditKitProviders(options); + + return { + module: AuditKitModule, + providers, + exports: [AuditService, AUDIT_REPOSITORY, ID_GENERATOR, TIMESTAMP_PROVIDER, CHANGE_DETECTOR], + }; + } + + /** + * Registers AuditKit module with async configuration. + * + * Use this when configuration values come from: + * - ConfigService + * - Remote configuration service + * - Database + * - Any other async source + * + * Supports three patterns: + * 1. useFactory - Provide a factory function + * 2. useClass - Provide a class implementing AuditKitModuleOptionsFactory + * 3. useExisting - Reuse an existing provider + * + * @param options - Async configuration options + * @returns Dynamic module + * + * @example With useFactory and ConfigService + * ```typescript + * AuditKitModule.registerAsync({ + * imports: [ConfigModule], + * inject: [ConfigService], + * useFactory: (config: ConfigService) => ({ + * repository: { + * type: 'mongodb', + * uri: config.get('MONGO_URI'), + * database: config.get('MONGO_DB') + * }, + * idGenerator: { + * type: 'nanoid', + * defaultLength: config.get('ID_LENGTH', 21) + * } + * }) + * }) + * ``` + * + * @example With useClass + * ```typescript + * @Injectable() + * class AuditKitConfigService implements AuditKitModuleOptionsFactory { + * constructor(private config: ConfigService) {} + * + * createAuditKitOptions(): AuditKitModuleOptions { + * return { + * repository: { + * type: 'mongodb', + * uri: this.config.get('MONGO_URI') + * } + * }; + * } + * } + * + * AuditKitModule.registerAsync({ + * useClass: AuditKitConfigService + * }) + * ``` + * + * @example With useExisting + * ```typescript + * AuditKitModule.registerAsync({ + * imports: [SharedConfigModule], + * useExisting: AuditKitConfigService + * }) + * ``` + */ + static registerAsync(options: AuditKitModuleAsyncOptions): DynamicModule { + const asyncProviders = createAuditKitAsyncProviders(options); return { - module: DeveloperKitModule, - providers: [], - exports: [], + module: AuditKitModule, + imports: options.imports ?? [], + providers: [ + ...asyncProviders, + // ID Generator + { + provide: ID_GENERATOR, + useFactory: (moduleOptions: AuditKitModuleOptions): IIdGenerator => { + const config = moduleOptions.idGenerator ?? { type: "nanoid" }; + + switch (config.type) { + case "nanoid": + default: { + const providerOptions: { + defaultLength?: number; + defaultAlphabet?: string; + } = {}; + + if (config.defaultLength !== undefined) { + providerOptions.defaultLength = config.defaultLength; + } + if (config.defaultAlphabet !== undefined) { + providerOptions.defaultAlphabet = config.defaultAlphabet; + } + + return new NanoidIdGenerator(providerOptions); + } + } + }, + inject: [AUDIT_KIT_OPTIONS], + }, + // Timestamp Provider + { + provide: TIMESTAMP_PROVIDER, + useFactory: (moduleOptions: AuditKitModuleOptions): ITimestampProvider => { + const config = moduleOptions.timestampProvider ?? { type: "system" }; + + switch (config.type) { + case "system": + default: { + const providerOptions: { + defaultTimezone?: "utc" | "local"; + defaultPrecision?: "second" | "millisecond" | "microsecond"; + } = {}; + + if (config.defaultTimezone !== undefined) { + providerOptions.defaultTimezone = config.defaultTimezone; + } + if (config.defaultPrecision !== undefined) { + providerOptions.defaultPrecision = config.defaultPrecision; + } + + return new SystemTimestampProvider(providerOptions); + } + } + }, + inject: [AUDIT_KIT_OPTIONS], + }, + // Change Detector + { + provide: CHANGE_DETECTOR, + useFactory: (moduleOptions: AuditKitModuleOptions): IChangeDetector => { + const config = moduleOptions.changeDetector ?? { type: "deep-diff" }; + + switch (config.type) { + case "deep-diff": + default: + return new DeepDiffChangeDetector(); + } + }, + inject: [AUDIT_KIT_OPTIONS], + }, + // Repository + { + provide: AUDIT_REPOSITORY, + useFactory: async ( + moduleOptions: AuditKitModuleOptions, + ): Promise => { + const config = moduleOptions.repository; + + switch (config.type) { + case "mongodb": { + // If a model is provided, use it directly + if (config.model) { + return new MongoAuditRepository(config.model); + } + + // Otherwise, create a connection and model + if (!config.uri) { + throw new Error( + "MongoDB repository requires either 'uri' or 'model' to be configured", + ); + } + + const connectionOptions: Partial = {}; + if (config.database !== undefined) { + connectionOptions.dbName = config.database; + } + + const connection = await connect(config.uri, connectionOptions as ConnectOptions); + const model = connection.model("AuditLog", AuditLogSchema); + return new MongoAuditRepository(model); + } + + case "in-memory": + default: + return new InMemoryAuditRepository(); + } + }, + inject: [AUDIT_KIT_OPTIONS], + }, + // Audit Service + { + provide: AuditService, + useFactory: ( + repository: IAuditLogRepository, + idGenerator: IIdGenerator, + timestampProvider: ITimestampProvider, + changeDetector: IChangeDetector, + ) => { + return new AuditService(repository, idGenerator, timestampProvider, changeDetector); + }, + inject: [AUDIT_REPOSITORY, ID_GENERATOR, TIMESTAMP_PROVIDER, CHANGE_DETECTOR], + }, + ], + exports: [AuditService, AUDIT_REPOSITORY, ID_GENERATOR, TIMESTAMP_PROVIDER, CHANGE_DETECTOR], }; } } diff --git a/src/nest/providers.ts b/src/nest/providers.ts new file mode 100644 index 0000000..188c0ad --- /dev/null +++ b/src/nest/providers.ts @@ -0,0 +1,253 @@ +/** + * ============================================================================ + * AUDIT KIT MODULE - PROVIDER FACTORY + * ============================================================================ + * + * Factory functions for creating NestJS providers based on module configuration. + * + * Architecture: + * - Wires concrete implementations to port interfaces + * - Handles configuration-based provider selection + * - Manages dependency injection setup + * + * @packageDocumentation + */ + +import type { Provider } from "@nestjs/common"; +import type { ConnectOptions } from "mongoose"; +import { connect } from "mongoose"; + +import { AuditService } from "../core/audit.service"; +import type { IAuditLogRepository } from "../core/ports/audit-repository.port"; +import type { IChangeDetector } from "../core/ports/change-detector.port"; +import type { IIdGenerator } from "../core/ports/id-generator.port"; +import type { ITimestampProvider } from "../core/ports/timestamp-provider.port"; +import { DeepDiffChangeDetector } from "../infra/providers/change-detector/deep-diff-change-detector"; +import { NanoidIdGenerator } from "../infra/providers/id-generator/nanoid-id-generator"; +import { SystemTimestampProvider } from "../infra/providers/timestamp/system-timestamp-provider"; +import { InMemoryAuditRepository } from "../infra/repositories/in-memory/in-memory-audit.repository"; +import { AuditLogSchema } from "../infra/repositories/mongodb/audit-log.schema"; +import { MongoAuditRepository } from "../infra/repositories/mongodb/mongo-audit.repository"; + +import { + AUDIT_KIT_OPTIONS, + AUDIT_REPOSITORY, + CHANGE_DETECTOR, + ID_GENERATOR, + TIMESTAMP_PROVIDER, +} from "./constants"; +import type { AuditKitModuleOptions } from "./interfaces"; + +// ============================================================================ +// PROVIDER FACTORY +// ============================================================================ + +/** + * Creates all NestJS providers for AuditKit module. + * + * Providers created: + * 1. AUDIT_KIT_OPTIONS - Module configuration + * 2. ID_GENERATOR - ID generation implementation + * 3. TIMESTAMP_PROVIDER - Timestamp provider implementation + * 4. CHANGE_DETECTOR - Change detection implementation + * 5. AUDIT_REPOSITORY - Repository implementation (MongoDB or In-Memory) + * 6. AuditService - Core service (depends on all above) + * + * @param options - Module configuration options + * @returns Array of NestJS providers + * + * @internal + */ +export function createAuditKitProviders(options: AuditKitModuleOptions): Provider[] { + return [ + // Configuration provider + { + provide: AUDIT_KIT_OPTIONS, + useValue: options, + }, + + // ID Generator provider + { + provide: ID_GENERATOR, + useFactory: (): IIdGenerator => { + const config = options.idGenerator ?? { type: "nanoid" }; + + switch (config.type) { + case "nanoid": + default: { + const options: { + defaultLength?: number; + defaultAlphabet?: string; + } = {}; + + if (config.defaultLength !== undefined) { + options.defaultLength = config.defaultLength; + } + if (config.defaultAlphabet !== undefined) { + options.defaultAlphabet = config.defaultAlphabet; + } + + return new NanoidIdGenerator(options); + } + } + }, + }, + + // Timestamp Provider provider + { + provide: TIMESTAMP_PROVIDER, + useFactory: (): ITimestampProvider => { + const config = options.timestampProvider ?? { type: "system" }; + + switch (config.type) { + case "system": + default: { + const options: { + defaultTimezone?: "utc" | "local"; + defaultPrecision?: "second" | "millisecond" | "microsecond"; + } = {}; + + if (config.defaultTimezone !== undefined) { + options.defaultTimezone = config.defaultTimezone; + } + if (config.defaultPrecision !== undefined) { + options.defaultPrecision = config.defaultPrecision; + } + + return new SystemTimestampProvider(options); + } + } + }, + }, + + // Change Detector provider + { + provide: CHANGE_DETECTOR, + useFactory: (): IChangeDetector => { + const config = options.changeDetector ?? { type: "deep-diff" }; + + switch (config.type) { + case "deep-diff": + default: + return new DeepDiffChangeDetector(); + } + }, + }, + + // Repository provider + { + provide: AUDIT_REPOSITORY, + useFactory: async (): Promise => { + const config = options.repository; + + switch (config.type) { + case "mongodb": { + // If a model is provided, use it directly + if (config.model) { + return new MongoAuditRepository(config.model); + } + + // Otherwise, create a connection and model + if (!config.uri) { + throw new Error( + "MongoDB repository requires either 'uri' or 'model' to be configured", + ); + } + + const connectionOptions: Partial = {}; + if (config.database !== undefined) { + connectionOptions.dbName = config.database; + } + + const connection = await connect(config.uri, connectionOptions as ConnectOptions); + + const model = connection.model("AuditLog", AuditLogSchema); + return new MongoAuditRepository(model); + } + + case "in-memory": + default: + return new InMemoryAuditRepository(); + } + }, + }, + + // Core AuditService + { + provide: AuditService, + useFactory: ( + repository: IAuditLogRepository, + idGenerator: IIdGenerator, + timestampProvider: ITimestampProvider, + changeDetector: IChangeDetector, + ) => { + return new AuditService(repository, idGenerator, timestampProvider, changeDetector); + }, + inject: [AUDIT_REPOSITORY, ID_GENERATOR, TIMESTAMP_PROVIDER, CHANGE_DETECTOR], + }, + ]; +} + +/** + * Creates async providers for module configuration. + * + * Used when options are provided via useFactory/useClass/useExisting. + * + * @param options - Async module options + * @returns Array of async option providers + * + * @internal + */ +export function createAuditKitAsyncProviders(options: { + useFactory?: ( + // eslint-disable-next-line no-unused-vars + ...args: any[] + ) => Promise | AuditKitModuleOptions; + inject?: any[]; + useClass?: any; + useExisting?: any; +}): Provider[] { + if (options.useFactory) { + return [ + { + provide: AUDIT_KIT_OPTIONS, + useFactory: options.useFactory, + inject: options.inject ?? [], + }, + ]; + } + + if (options.useClass) { + return [ + { + provide: AUDIT_KIT_OPTIONS, + useFactory: async (optionsFactory: { + createAuditKitOptions: () => Promise | AuditKitModuleOptions; + }) => { + return await optionsFactory.createAuditKitOptions(); + }, + inject: [options.useClass], + }, + { + provide: options.useClass, + useClass: options.useClass, + }, + ]; + } + + if (options.useExisting) { + return [ + { + provide: AUDIT_KIT_OPTIONS, + useFactory: async (optionsFactory: { + createAuditKitOptions: () => Promise | AuditKitModuleOptions; + }) => { + return await optionsFactory.createAuditKitOptions(); + }, + inject: [options.useExisting], + }, + ]; + } + + throw new Error("Invalid async options: must provide useFactory, useClass, or useExisting"); +}