db-backup-logging 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,11 @@ npm install mongoose express
21
21
  npm install @aws-sdk/client-s3
22
22
  ```
23
23
 
24
+ **Optional** (only if using automatic backup/cleanup schedulers):
25
+ ```bash
26
+ npm install node-cron
27
+ ```
28
+
24
29
  ---
25
30
 
26
31
  ## Quick Start
@@ -102,11 +107,52 @@ logCustomError(new Error('Payment failed'), {
102
107
  })
103
108
  ```
104
109
 
110
+ ---
111
+
112
+ ### 6. Automatic Backup Scheduler
113
+
114
+ > Requires `node-cron` installed in your project.
115
+
116
+ ```ts
117
+ import { initBackupScheduler } from 'db-backup-logging'
118
+
119
+ // Call after initializeNoticePackage()
120
+ initBackupScheduler()
121
+ ```
122
+
123
+ Schedule is resolved in priority order:
124
+ 1. `BACKUP_CRON_SCHEDULE` / `BACKUP_TIMEZONE` env vars
125
+ 2. `config.backupCron` passed to `initializeNoticePackage()`
126
+ 3. Default: `"0 2 * * *"` (2 AM daily, UTC)
127
+
128
+ ### 7. Automatic Log Cleanup Scheduler
129
+
130
+ > Requires `node-cron` installed in your project.
131
+
132
+ ```ts
133
+ import { initLogCleanupScheduler } from 'db-backup-logging'
134
+
135
+ // Call after initializeNoticePackage()
136
+ initLogCleanupScheduler()
137
+ ```
138
+
139
+ Schedule and retention are resolved in priority order:
140
+ 1. `LOG_CLEANUP_CRON_SCHEDULE` / `LOG_CLEANUP_TIMEZONE` / `LOG_RETENTION_DAYS` env vars
141
+ 2. `config.logCleanupCron` passed to `initializeNoticePackage()`
142
+ 3. Default: `"0 3 * * *"` (3 AM daily, UTC), 30 days retention
143
+
144
+
105
145
  ---
106
146
 
107
147
  ## Full Config Reference
108
148
 
109
149
  ```ts
150
+ interface CronConfig {
151
+ schedule: string // Cron expression, e.g. "0 2 * * *"
152
+ timezone?: string // IANA timezone, e.g. "Asia/Kolkata"
153
+ retentionDays?: number // Days to keep (logCleanupCron only)
154
+ }
155
+
110
156
  interface NoticeConfig {
111
157
  dbType: 'mongodb' | 'postgres' | 'mysql'
112
158
  dbUri: string // MongoDB connection URI
@@ -118,20 +164,26 @@ interface NoticeConfig {
118
164
  errorLogs: string // Collection name for error logs
119
165
  backupLogs: string // Collection name for backup logs
120
166
  }
121
- aws?: {
122
- enabled: boolean
123
- bucketName: string
124
- region: string
125
- accessKeyId: string
126
- secretAccessKey: string
127
- }
128
- local?: {
129
- enabled: boolean
130
- backupPath: string // Relative or absolute path
131
- }
167
+ aws?: { ... } // S3 config (optional)
168
+ local?: { ... } // Local backup config (optional)
169
+ backupCron?: CronConfig // Auto backup schedule (optional)
170
+ logCleanupCron?: CronConfig // Log cleanup schedule (optional)
132
171
  }
133
172
  ```
134
173
 
174
+ ### Environment Variables
175
+
176
+ | Variable | Description | Default |
177
+ |---|---|---|
178
+ | `MONGO_URI` | MongoDB connection URI | — |
179
+ | `BACKUP_PATH` | Local backup directory | — |
180
+ | `BACKUP_CRON_SCHEDULE` | Backup cron expression | `0 2 * * *` |
181
+ | `BACKUP_TIMEZONE` | Backup cron timezone | `UTC` |
182
+ | `LOG_CLEANUP_CRON_SCHEDULE` | Log cleanup cron expression | `0 3 * * *` |
183
+ | `LOG_CLEANUP_TIMEZONE` | Log cleanup timezone | `UTC` |
184
+ | `LOG_RETENTION_DAYS` | Days to retain logs | `30` |
185
+
186
+
135
187
  ---
136
188
 
137
189
  ## Exported Modules
@@ -143,8 +195,10 @@ interface NoticeConfig {
143
195
  | `errorMiddleware()` | Middleware | Express error-handling middleware |
144
196
  | `logCustomError(error, context?)` | Function | Manually log any error |
145
197
  | `manualBackupTrigger()` | Async Function | Trigger a database backup on demand |
198
+ | `initBackupScheduler()` | Function | Start automatic backup cron job *(requires node-cron)* |
199
+ | `initLogCleanupScheduler()` | Function | Start automatic log cleanup cron job *(requires node-cron)* |
146
200
 
147
- All types are also exported: `NoticeConfig`, `AWSConfig`, `LocalConfig`, `NoticeTables`, `RequestLogEntry`, `ErrorLogEntry`, `BackupLogEntry`.
201
+ All types are also exported: `NoticeConfig`, `AWSConfig`, `LocalConfig`, `NoticeTables`, `CronConfig`, `RequestLogEntry`, `ErrorLogEntry`, `BackupLogEntry`.
148
202
 
149
203
  ---
150
204
  ## Notes
package/dist/index.d.mts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
2
 
3
+ interface CronConfig {
4
+ /** Cron expression, e.g. "0 2 * * *" for 2 AM daily */
5
+ schedule: string;
6
+ /** IANA timezone string, e.g. "Asia/Kolkata" */
7
+ timezone?: string;
8
+ /** How many days to retain items before cleanup (applies to logCleanupCron) */
9
+ retentionDays?: number;
10
+ }
3
11
  interface AWSConfig {
4
12
  enabled: boolean;
5
13
  bucketName: string;
@@ -25,6 +33,10 @@ interface NoticeConfig {
25
33
  environment?: string;
26
34
  aws?: AWSConfig;
27
35
  local?: LocalConfig;
36
+ /** Cron config for automatic database backups (requires node-cron peer dep) */
37
+ backupCron?: CronConfig;
38
+ /** Cron config for automatic log cleanup (requires node-cron peer dep) */
39
+ logCleanupCron?: CronConfig;
28
40
  }
29
41
  interface RequestLogEntry {
30
42
  method: string;
@@ -102,4 +114,28 @@ declare function logCustomError(error: Error | string, context?: {
102
114
  };
103
115
  }): void;
104
116
 
105
- export { type AWSConfig, type BackupLogEntry, type ErrorLogEntry, type LocalConfig, type NoticeConfig, type NoticeTables, type RequestLogEntry, errorMiddleware, initializeNoticePackage, logCustomError, manualBackupTrigger, requestLogger };
117
+ /**
118
+ * Start an automatic backup cron job.
119
+ *
120
+ * Schedule and timezone are resolved in order:
121
+ * 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE
122
+ * 2. config.backupCron passed to initializeNoticePackage()
123
+ * 3. Built-in defaults ("0 2 * * *" = 2 AM daily, UTC)
124
+ *
125
+ * Requires node-cron >= 3.0.0 installed in the host project.
126
+ */
127
+ declare function initBackupScheduler(): void;
128
+
129
+ /**
130
+ * Start an automatic log cleanup cron job.
131
+ *
132
+ * Schedule, timezone and retention days are resolved in order:
133
+ * 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS
134
+ * 2. config.logCleanupCron passed to initializeNoticePackage()
135
+ * 3. Built-in defaults ("0 3 * * *" = 3 AM daily, UTC, 30 days retention)
136
+ *
137
+ * Requires node-cron >= 3.0.0 installed in the host project.
138
+ */
139
+ declare function initLogCleanupScheduler(): void;
140
+
141
+ export { type AWSConfig, type BackupLogEntry, type CronConfig, type ErrorLogEntry, type LocalConfig, type NoticeConfig, type NoticeTables, type RequestLogEntry, errorMiddleware, initBackupScheduler, initLogCleanupScheduler, initializeNoticePackage, logCustomError, manualBackupTrigger, requestLogger };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
2
 
3
+ interface CronConfig {
4
+ /** Cron expression, e.g. "0 2 * * *" for 2 AM daily */
5
+ schedule: string;
6
+ /** IANA timezone string, e.g. "Asia/Kolkata" */
7
+ timezone?: string;
8
+ /** How many days to retain items before cleanup (applies to logCleanupCron) */
9
+ retentionDays?: number;
10
+ }
3
11
  interface AWSConfig {
4
12
  enabled: boolean;
5
13
  bucketName: string;
@@ -25,6 +33,10 @@ interface NoticeConfig {
25
33
  environment?: string;
26
34
  aws?: AWSConfig;
27
35
  local?: LocalConfig;
36
+ /** Cron config for automatic database backups (requires node-cron peer dep) */
37
+ backupCron?: CronConfig;
38
+ /** Cron config for automatic log cleanup (requires node-cron peer dep) */
39
+ logCleanupCron?: CronConfig;
28
40
  }
29
41
  interface RequestLogEntry {
30
42
  method: string;
@@ -102,4 +114,28 @@ declare function logCustomError(error: Error | string, context?: {
102
114
  };
103
115
  }): void;
104
116
 
105
- export { type AWSConfig, type BackupLogEntry, type ErrorLogEntry, type LocalConfig, type NoticeConfig, type NoticeTables, type RequestLogEntry, errorMiddleware, initializeNoticePackage, logCustomError, manualBackupTrigger, requestLogger };
117
+ /**
118
+ * Start an automatic backup cron job.
119
+ *
120
+ * Schedule and timezone are resolved in order:
121
+ * 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE
122
+ * 2. config.backupCron passed to initializeNoticePackage()
123
+ * 3. Built-in defaults ("0 2 * * *" = 2 AM daily, UTC)
124
+ *
125
+ * Requires node-cron >= 3.0.0 installed in the host project.
126
+ */
127
+ declare function initBackupScheduler(): void;
128
+
129
+ /**
130
+ * Start an automatic log cleanup cron job.
131
+ *
132
+ * Schedule, timezone and retention days are resolved in order:
133
+ * 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS
134
+ * 2. config.logCleanupCron passed to initializeNoticePackage()
135
+ * 3. Built-in defaults ("0 3 * * *" = 3 AM daily, UTC, 30 days retention)
136
+ *
137
+ * Requires node-cron >= 3.0.0 installed in the host project.
138
+ */
139
+ declare function initLogCleanupScheduler(): void;
140
+
141
+ export { type AWSConfig, type BackupLogEntry, type CronConfig, type ErrorLogEntry, type LocalConfig, type NoticeConfig, type NoticeTables, type RequestLogEntry, errorMiddleware, initBackupScheduler, initLogCleanupScheduler, initializeNoticePackage, logCustomError, manualBackupTrigger, requestLogger };
package/dist/index.js CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  errorMiddleware: () => errorMiddleware,
34
+ initBackupScheduler: () => initBackupScheduler,
35
+ initLogCleanupScheduler: () => initLogCleanupScheduler,
34
36
  initializeNoticePackage: () => initializeNoticePackage,
35
37
  logCustomError: () => logCustomError,
36
38
  manualBackupTrigger: () => manualBackupTrigger,
@@ -404,9 +406,98 @@ async function logBackup(entry) {
404
406
  async function manualBackupTrigger() {
405
407
  await performBackup();
406
408
  }
409
+
410
+ // src/schedulers/backup.ts
411
+ function loadNodeCron() {
412
+ try {
413
+ return require("node-cron");
414
+ } catch {
415
+ throw new Error(
416
+ "[db-backup-logging] node-cron is required to use initBackupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
417
+ );
418
+ }
419
+ }
420
+ function initBackupScheduler() {
421
+ const cron = loadNodeCron();
422
+ const config = getConfig();
423
+ const schedule = process.env.BACKUP_CRON_SCHEDULE || config.backupCron?.schedule || "0 2 * * *";
424
+ const timezone = process.env.BACKUP_TIMEZONE || config.backupCron?.timezone || "UTC";
425
+ if (!cron.validate(schedule)) {
426
+ throw new Error(
427
+ `[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
428
+ );
429
+ }
430
+ cron.schedule(
431
+ schedule,
432
+ async () => {
433
+ console.log("[db-backup-logging] Backup scheduler triggered.");
434
+ await manualBackupTrigger();
435
+ },
436
+ { timezone }
437
+ );
438
+ console.log(
439
+ `[db-backup-logging] Backup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}"`
440
+ );
441
+ }
442
+
443
+ // src/schedulers/cleanup.ts
444
+ function loadNodeCron2() {
445
+ try {
446
+ return require("node-cron");
447
+ } catch {
448
+ throw new Error(
449
+ "[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
450
+ );
451
+ }
452
+ }
453
+ async function purgeOldLogs(retentionDays) {
454
+ const config = getConfig();
455
+ const cutoff = /* @__PURE__ */ new Date();
456
+ cutoff.setDate(cutoff.getDate() - retentionDays);
457
+ const collections = [config.tables.requestLogs, config.tables.errorLogs];
458
+ for (const collectionName of collections) {
459
+ try {
460
+ const Model2 = getModel(collectionName);
461
+ const result = await Model2.deleteMany({ createdAt: { $lt: cutoff } });
462
+ console.log(
463
+ `[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from "${collectionName}" older than ${retentionDays} day(s).`
464
+ );
465
+ } catch (err) {
466
+ console.error(
467
+ `[db-backup-logging] Log cleanup failed for "${collectionName}":`,
468
+ err instanceof Error ? err.message : err
469
+ );
470
+ }
471
+ }
472
+ }
473
+ function initLogCleanupScheduler() {
474
+ const cron = loadNodeCron2();
475
+ const config = getConfig();
476
+ const schedule = process.env.LOG_CLEANUP_CRON_SCHEDULE || config.logCleanupCron?.schedule || "0 3 * * *";
477
+ const timezone = process.env.LOG_CLEANUP_TIMEZONE || config.logCleanupCron?.timezone || "UTC";
478
+ const retentionDays = Number(process.env.LOG_RETENTION_DAYS) || config.logCleanupCron?.retentionDays || 30;
479
+ if (!cron.validate(schedule)) {
480
+ throw new Error(
481
+ `[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
482
+ );
483
+ }
484
+ cron.schedule(
485
+ schedule,
486
+ async () => {
487
+ console.log("[db-backup-logging] Log cleanup scheduler triggered.");
488
+ await purgeOldLogs(retentionDays);
489
+ },
490
+ { timezone }
491
+ );
492
+ console.log(
493
+ `[db-backup-logging] Log cleanup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}", retentionDays: ${retentionDays}`
494
+ );
495
+ }
407
496
  // Annotate the CommonJS export names for ESM import in node:
408
497
  0 && (module.exports = {
409
498
  errorMiddleware,
499
+ initBackupScheduler,
500
+ initLogCleanupScheduler,
410
501
  initializeNoticePackage,
411
502
  logCustomError,
412
503
  manualBackupTrigger,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts"],"sourcesContent":["// ─── Public API ─────────────────────────────────────────────────────────────\n\nexport { initializeNoticePackage } from './init'\nexport { requestLogger } from './logger'\nexport { manualBackupTrigger } from './backup'\nexport { logCustomError, errorMiddleware } from './exceptions'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type {\n NoticeConfig,\n AWSConfig,\n LocalConfig,\n NoticeTables,\n RequestLogEntry,\n ErrorLogEntry,\n BackupLogEntry,\n} from './types'\n","import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[db-backup-logging] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for db-backup-logging to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[db-backup-logging] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the db-backup-logging package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[db-backup-logging] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[db-backup-logging] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[db-backup-logging] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[db-backup-logging] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[db-backup-logging] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n\n try {\n require.resolve('@aws-sdk/client-s3')\n } catch {\n throw new Error(\n '[db-backup-logging] AWS backup is enabled in config, but \"@aws-sdk/client-s3\" is not installed. Please run: npm install @aws-sdk/client-s3'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[db-backup-logging] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[db-backup-logging] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[db-backup-logging] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[db-backup-logging] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[db-backup-logging] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[db-backup-logging] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,IAAI,UAA+B;AAE5B,SAAS,UAAU,QAA4B;AACpD,YAAU,EAAE,GAAG,OAAO;AACxB;AAEO,SAAS,YAA0B;AACxC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACfA,sBAAoD;AAGpD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,IAAI,kBAAqC;AAElC,SAAS,kBAA8B;AAC5C,MAAI,CAAC,iBAAiB;AACpB,sBAAkB,gBAAAA,QAAS,iBAAiB,UAAU,EAAE,KAAK;AAAA,EAC/D;AACA,SAAO;AACT;AAMO,SAAS,SAAS,gBAAoC;AAC3D,MAAI,WAAW,IAAI,cAAc,GAAG;AAClC,WAAO,WAAW,IAAI,cAAc;AAAA,EACtC;AAEA,QAAM,SAAS,IAAI;AAAA,IACjB,CAAC;AAAA,IACD;AAAA,MACE,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,YAAY;AAAA,IACd;AAAA,EACF;AAGA,QAAM,OAAmB,gBAAgB;AAGzC,MAAI;AACJ,MAAI;AACF,YAAQ,KAAK,MAAM,cAAc;AAAA,EACnC,QAAQ;AACN,YAAQ,KAAK,MAAM,gBAAgB,MAAM;AAAA,EAC3C;AAEA,aAAW,IAAI,gBAAgB,KAAK;AACpC,SAAO;AACT;AAKO,SAAS,WAAmB;AACjC,SAAO,UAAU,EAAE;AACrB;;;AC7CA,IAAM,QAAqB,CAAC;AAC5B,IAAI,eAAe;AACnB,IAAM,aAAa;AACnB,IAAM,oBAAoB;AAC1B,IAAM,cAAc;AAEpB,IAAI,aAAoD;AAMjD,SAAS,QAAQ,gBAAwB,MAAqC;AACnF,QAAM,KAAK,EAAE,gBAAgB,KAAK,CAAC;AAGnC,MAAI,CAAC,YAAY;AACf,iBAAa,YAAY,MAAM;AAC7B,YAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACxB,GAAG,iBAAiB;AAGpB,QAAI,cAAc,OAAO,eAAe,YAAY,WAAW,YAAY;AACzE,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF;AAGA,MAAI,MAAM,UAAU,YAAY;AAC9B,UAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACxB;AACF;AAKA,eAAsB,QAAuB;AAC3C,MAAI,gBAAgB,MAAM,WAAW,EAAG;AACxC,iBAAe;AAEf,QAAM,QAAQ,MAAM,OAAO,GAAG,UAAU;AAGxC,QAAM,UAAU,oBAAI,IAAuC;AAC3D,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,QAAQ,IAAI,KAAK,cAAc,KAAK,CAAC;AAClD,SAAK,KAAK,KAAK,IAAI;AACnB,YAAQ,IAAI,KAAK,gBAAgB,IAAI;AAAA,EACvC;AAEA,aAAW,CAAC,gBAAgB,IAAI,KAAK,SAAS;AAC5C,QAAI,UAAU;AACd,WAAO,UAAU,aAAa;AAC5B,UAAI;AACF,cAAMC,SAAQ,SAAS,cAAc;AACrC,cAAMA,OAAM,WAAW,MAAM,EAAE,SAAS,MAAM,CAAC;AAC/C;AAAA,MACF,SAAS,KAAK;AACZ;AACA,YAAI,WAAW,aAAa;AAC1B,kBAAQ;AAAA,YACN,uCAAuC,KAAK,MAAM,aAAa,cAAc,WAAW,WAAW;AAAA,YACnG,eAAe,QAAQ,IAAI,UAAU;AAAA,UACvC;AAAA,QACF,OAAO;AAEL,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC;AAAA,QACvD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,iBAAe;AAGf,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACxB;AACF;;;AC9EA,SAAS,SACP,OACA,SAIM;AACN,MAAI;AACF,UAAM,SAAS,UAAU;AACzB,UAAM,MAAM,OAAO,UAAU,WAAW,IAAI,MAAM,KAAK,IAAI;AAE3D,UAAM,WAAoC;AAAA,MACxC,cAAc,IAAI;AAAA,MAClB,YAAY,IAAI,SAAS;AAAA,MACzB,gBAAgB,SAAS,MACrB;AAAA,QACE,QAAQ,QAAQ,IAAI;AAAA,QACpB,KAAK,QAAQ,IAAI,eAAe,QAAQ,IAAI;AAAA,QAC5C,SAAS,QAAQ,IAAI;AAAA,QACrB,MAAM,QAAQ,IAAI,QAAQ;AAAA,MAC5B,IACA;AAAA,MACJ,QACE,SAAS,UACR,SAAS,KAAa,MAAM,MAC5B,SAAS,KAAa,MAAM,OAC7B;AAAA,MACF,aAAa,OAAO,eAAe;AAAA,MACnC,aAAa,OAAO,eAAe,QAAQ,IAAI,YAAY;AAAA,MAC3D,WAAW,oBAAI,KAAK;AAAA,IACtB;AAEA,YAAQ,OAAO,OAAO,WAAW,QAAQ;AAAA,EAC3C,QAAQ;AAAA,EAER;AACF;AAMO,SAAS,4BAAkC;AAEhD,QAAM,mBAAmB,QAAQ,UAAU,mBAAmB,EAAE,MAAM;AACtE,QAAM,oBAAoB,QAAQ,UAAU,oBAAoB,EAAE,MAAM;AAGxE,UAAQ,GAAG,qBAAqB,CAAC,UAAiB;AAChD,aAAS,KAAK;AAAA,EAGhB,CAAC;AAGD,UAAQ,GAAG,sBAAsB,CAAC,WAAoB;AACpD,UAAM,QAAQ,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC;AACzE,aAAS,KAAK;AAAA,EAChB,CAAC;AACH;AAMO,SAAS,kBAAkB;AAChC,SAAO,CAAC,KAAY,KAAc,KAAe,SAA6B;AAC5E,aAAS,KAAK,EAAE,IAAI,CAAC;AAGrB,SAAK,GAAG;AAAA,EACV;AACF;AAKO,SAAS,eACd,OACA,SASM;AACN,MAAI;AACF,UAAM,SAAS,UAAU;AACzB,UAAM,MAAM,OAAO,UAAU,WAAW,IAAI,MAAM,KAAK,IAAI;AAE3D,UAAM,WAAoC;AAAA,MACxC,cAAc,IAAI;AAAA,MAClB,YAAY,IAAI,SAAS;AAAA,MACzB,gBAAgB,SAAS,kBAAkB;AAAA,MAC3C,QAAQ,SAAS,UAAU;AAAA,MAC3B,aAAa,OAAO,eAAe;AAAA,MACnC,aAAa,OAAO,eAAe,QAAQ,IAAI,YAAY;AAAA,MAC3D,WAAW,oBAAI,KAAK;AAAA,IACtB;AAEA,YAAQ,OAAO,OAAO,WAAW,QAAQ;AAAA,EAC3C,QAAQ;AAAA,EAER;AACF;;;ACxGO,SAAS,wBAAwB,QAA4B;AAElE,MAAI,CAAC,OAAO,QAAQ;AAClB,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACA,MAAI,CAAC,OAAO,OAAO;AACjB,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,MAAI,CAAC,OAAO,QAAQ;AAClB,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AACA,MAAI,CAAC,OAAO,OAAO,eAAe,CAAC,OAAO,OAAO,aAAa,CAAC,OAAO,OAAO,YAAY;AACvF,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,KAAK,SAAS;AACvB,QAAI,CAAC,OAAO,IAAI,cAAc,CAAC,OAAO,IAAI,QAAQ;AAChD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,IAAI,iBAAiB;AAC1D,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,sBAAgB,oBAAoB;AAAA,IACtC,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,OAAO,SAAS;AACzB,QAAI,CAAC,OAAO,MAAM,YAAY;AAC5B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,YAAU,MAAM;AAGhB,4BAA0B;AAE1B,UAAQ;AAAA,IACN,qDAAqD,OAAO,MAAM,cAAc,OAAO,eAAe,SAAS,UAAU,OAAO,eAAe,QAAQ,IAAI,YAAY,aAAa;AAAA,EACtL;AACF;;;AC1DO,SAAS,gBAAgB;AAC9B,SAAO,CAAC,KAAc,KAAe,SAA6B;AAChE,UAAM,YAAY,KAAK,IAAI;AAG3B,UAAM,eAAe,IAAI,KAAK,KAAK,GAAG;AACtC,QAAI,eAAwB;AAE5B,QAAI,OAAO,SAAU,MAAW;AAC9B,qBAAe;AACf,aAAO,aAAa,IAAI;AAAA,IAC1B;AAGA,QAAI,GAAG,UAAU,MAAM;AACrB,UAAI;AACF,cAAM,SAAS,UAAU;AACzB,cAAM,eAAe,KAAK,IAAI,IAAI;AAElC,cAAM,WAAoC;AAAA,UACxC,QAAQ,IAAI;AAAA,UACZ,KAAK,IAAI,eAAe,IAAI;AAAA,UAC5B,SAAS,IAAI;AAAA,UACb,aAAa,IAAI,QAAQ;AAAA,UACzB,gBAAgB,IAAI;AAAA,UACpB,cAAc,gBAAgB;AAAA,UAC9B;AAAA,UACA,QAAS,IAAY,MAAM,MAAO,IAAY,MAAM,OAAQ,IAAY,UAAU;AAAA,UAClF,WACG,IAAI,QAAQ,iBAAiB,GAAc,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,KAChE,IAAI,QAAQ,iBACZ;AAAA,UACF,aAAa,OAAO,eAAe;AAAA,UACnC,aAAa,OAAO,eAAe,QAAQ,IAAI,YAAY;AAAA,UAC3D,WAAW,oBAAI,KAAK;AAAA,QACtB;AAEA,gBAAQ,OAAO,OAAO,aAAa,QAAQ;AAAA,MAC7C,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAED,SAAK;AAAA,EACP;AACF;;;ACtDA,2BAAqB;AACrB,kBAA0B;AAC1B,WAAsB;AACtB,SAAoB;AAIpB,IAAM,gBAAY,uBAAU,yBAAI;AAKhC,SAAS,oBAA4B;AACnC,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,KAAK,IAAI,YAAY,EAAE,QAAQ,SAAS,GAAG;AACjD,SAAO,UAAU,EAAE;AACrB;AAMA,eAAe,gBAA+B;AAC5C,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,SAAS;AACvB,QAAM,WAAW,kBAAkB;AACnC,QAAM,cAAc,GAAG,QAAQ;AAG/B,QAAM,UAAe,aAAQ,QAAQ,IAAI,GAAG,qBAAqB;AACjE,MAAI,CAAI,cAAW,OAAO,GAAG;AAC3B,IAAG,aAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AAEA,QAAM,UAAe,UAAK,SAAS,QAAQ;AAC3C,QAAM,cAAmB,UAAK,SAAS,WAAW;AAElD,MAAI;AAEF,YAAQ,IAAI,wCAAwC,WAAW,EAAE;AACjE,UAAM,UAAU,oBAAoB,KAAK,YAAY,OAAO,GAAG;AAG/D,UAAM,UAAU,WAAW,WAAW,MAAM,QAAQ,KAAK,EAAE,KAAK,QAAQ,CAAC;AAEzE,UAAM,QAAW,YAAS,WAAW;AACrC,UAAM,WAAW,MAAM;AAGvB,QAAI,OAAO,OAAO,SAAS;AACzB,YAAM,WAAgB,aAAQ,QAAQ,IAAI,GAAG,OAAO,MAAM,UAAU;AACpE,UAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,QAAG,aAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,MAC5C;AACA,YAAM,YAAiB,UAAK,UAAU,WAAW;AACjD,MAAG,gBAAa,aAAa,SAAS;AACtC,cAAQ,IAAI,6CAA6C,SAAS,EAAE;AAGpE,YAAM,UAAU;AAAA,QACd,gBAAgB;AAAA,QAChB,YAAY;AAAA,QACZ,UAAU;AAAA,QACV;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAGA,QAAI,OAAO,KAAK,SAAS;AACvB,YAAM,WAAW,aAAa,aAAa,QAAQ;AAAA,IACrD;AAGA,QAAO,cAAW,WAAW,GAAG;AAC9B,MAAG,cAAW,WAAW;AAAA,IAC3B;AACA,QAAO,cAAW,OAAO,GAAG;AAC1B,MAAG,UAAO,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACrD;AAEA,YAAQ,IAAI,yCAAyC,WAAW,EAAE;AAAA,EACpE,SAAS,KAAK;AACZ,YAAQ,MAAM,sCAAsC,eAAe,QAAQ,IAAI,UAAU,GAAG;AAE5F,UAAM,UAAU;AAAA,MACd,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,UAAU,OAAO,KAAK,UAAU,OAAO;AAAA,MACvC,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IAC/D,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAGnB;AACF;AAKA,eAAe,WAAW,UAAkB,UAAkB,UAAiC;AAC7F,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAO,IAAK,OAAM,IAAI,MAAM,yBAAyB;AAE1D,QAAM,EAAE,UAAU,iBAAiB,IAAI,MAAM,OAAO,oBAAoB;AAExE,QAAM,WAAW,IAAI,SAAS;AAAA,IAC5B,QAAQ,OAAO,IAAI;AAAA,IACnB,aAAa;AAAA,MACX,aAAa,OAAO,IAAI;AAAA,MACxB,iBAAiB,OAAO,IAAI;AAAA,IAC9B;AAAA,EACF,CAAC;AAED,QAAM,aAAgB,gBAAa,QAAQ;AAE3C,QAAM,UAAU,IAAI,iBAAiB;AAAA,IACnC,QAAQ,OAAO,IAAI;AAAA,IACnB,KAAK,WAAW,QAAQ;AAAA,IACxB,MAAM;AAAA,IACN,aAAa;AAAA,EACf,CAAC;AAED,QAAM,SAAS,KAAK,OAAO;AAC3B,UAAQ,IAAI,sDAAsD,QAAQ,EAAE;AAE5E,QAAM,UAAU;AAAA,IACd,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,UAAU;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,EACV,CAAC;AACH;AAKA,eAAe,UAAU,OAOP;AAChB,MAAI;AACF,UAAM,SAAS,UAAU;AACzB,UAAMC,SAAQ,SAAS,OAAO,OAAO,UAAU;AAC/C,UAAMA,OAAM,OAAO;AAAA,MACjB,GAAG;AAAA,MACH,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ;AAAA,MACN;AAAA,MACA,eAAe,QAAQ,IAAI,UAAU;AAAA,IACvC;AAAA,EACF;AACF;AAKA,eAAsB,sBAAqC;AACzD,QAAM,cAAc;AACtB;","names":["mongoose","Model","Model"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts","../src/schedulers/backup.ts","../src/schedulers/cleanup.ts"],"sourcesContent":["// ─── Public API ─────────────────────────────────────────────────────────────\n\nexport { initializeNoticePackage } from './init'\nexport { requestLogger } from './logger'\nexport { manualBackupTrigger } from './backup'\nexport { logCustomError, errorMiddleware } from './exceptions'\n\n// ─── Schedulers (requires node-cron peer dependency) ────────────────────────\n\nexport { initBackupScheduler } from './schedulers/backup'\nexport { initLogCleanupScheduler } from './schedulers/cleanup'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type {\n NoticeConfig,\n AWSConfig,\n LocalConfig,\n NoticeTables,\n CronConfig,\n RequestLogEntry,\n ErrorLogEntry,\n BackupLogEntry,\n} from './types'\n","import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[db-backup-logging] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for db-backup-logging to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[db-backup-logging] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the db-backup-logging package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[db-backup-logging] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[db-backup-logging] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[db-backup-logging] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[db-backup-logging] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[db-backup-logging] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n\n try {\n require.resolve('@aws-sdk/client-s3')\n } catch {\n throw new Error(\n '[db-backup-logging] AWS backup is enabled in config, but \"@aws-sdk/client-s3\" is not installed. Please run: npm install @aws-sdk/client-s3'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[db-backup-logging] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[db-backup-logging] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[db-backup-logging] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[db-backup-logging] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[db-backup-logging] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[db-backup-logging] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n","import { getConfig } from '../utils/config'\nimport { manualBackupTrigger } from '../backup'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initBackupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Start an automatic backup cron job.\n *\n * Schedule and timezone are resolved in order:\n * 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE\n * 2. config.backupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 2 * * *\" = 2 AM daily, UTC)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initBackupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.BACKUP_CRON_SCHEDULE ||\n config.backupCron?.schedule ||\n '0 2 * * *'\n\n const timezone =\n process.env.BACKUP_TIMEZONE ||\n config.backupCron?.timezone ||\n 'UTC'\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Backup scheduler triggered.')\n await manualBackupTrigger()\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Backup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\"`\n )\n}\n","import { getConfig } from '../utils/config'\nimport { getModel } from '../utils/db'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Delete request/error log documents older than retentionDays.\n */\nasync function purgeOldLogs(retentionDays: number): Promise<void> {\n const config = getConfig()\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - retentionDays)\n\n const collections = [config.tables.requestLogs, config.tables.errorLogs]\n\n for (const collectionName of collections) {\n try {\n const Model = getModel(collectionName)\n const result = await Model.deleteMany({ createdAt: { $lt: cutoff } })\n console.log(\n `[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from \"${collectionName}\" older than ${retentionDays} day(s).`\n )\n } catch (err) {\n console.error(\n `[db-backup-logging] Log cleanup failed for \"${collectionName}\":`,\n err instanceof Error ? err.message : err\n )\n }\n }\n}\n\n/**\n * Start an automatic log cleanup cron job.\n *\n * Schedule, timezone and retention days are resolved in order:\n * 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS\n * 2. config.logCleanupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 3 * * *\" = 3 AM daily, UTC, 30 days retention)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initLogCleanupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.LOG_CLEANUP_CRON_SCHEDULE ||\n config.logCleanupCron?.schedule ||\n '0 3 * * *'\n\n const timezone =\n process.env.LOG_CLEANUP_TIMEZONE ||\n config.logCleanupCron?.timezone ||\n 'UTC'\n\n const retentionDays =\n Number(process.env.LOG_RETENTION_DAYS) ||\n config.logCleanupCron?.retentionDays ||\n 30\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Log cleanup scheduler triggered.')\n await purgeOldLogs(retentionDays)\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Log cleanup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\", retentionDays: ${retentionDays}`\n )\n}\n"],"mappings":"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","names":["mongoose","Model","Model","loadNodeCron","Model"]}
package/dist/index.mjs CHANGED
@@ -371,8 +371,97 @@ async function logBackup(entry) {
371
371
  async function manualBackupTrigger() {
372
372
  await performBackup();
373
373
  }
374
+
375
+ // src/schedulers/backup.ts
376
+ function loadNodeCron() {
377
+ try {
378
+ return __require("node-cron");
379
+ } catch {
380
+ throw new Error(
381
+ "[db-backup-logging] node-cron is required to use initBackupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
382
+ );
383
+ }
384
+ }
385
+ function initBackupScheduler() {
386
+ const cron = loadNodeCron();
387
+ const config = getConfig();
388
+ const schedule = process.env.BACKUP_CRON_SCHEDULE || config.backupCron?.schedule || "0 2 * * *";
389
+ const timezone = process.env.BACKUP_TIMEZONE || config.backupCron?.timezone || "UTC";
390
+ if (!cron.validate(schedule)) {
391
+ throw new Error(
392
+ `[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
393
+ );
394
+ }
395
+ cron.schedule(
396
+ schedule,
397
+ async () => {
398
+ console.log("[db-backup-logging] Backup scheduler triggered.");
399
+ await manualBackupTrigger();
400
+ },
401
+ { timezone }
402
+ );
403
+ console.log(
404
+ `[db-backup-logging] Backup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}"`
405
+ );
406
+ }
407
+
408
+ // src/schedulers/cleanup.ts
409
+ function loadNodeCron2() {
410
+ try {
411
+ return __require("node-cron");
412
+ } catch {
413
+ throw new Error(
414
+ "[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\nPlease install it in your root project:\n npm install node-cron\n yarn add node-cron"
415
+ );
416
+ }
417
+ }
418
+ async function purgeOldLogs(retentionDays) {
419
+ const config = getConfig();
420
+ const cutoff = /* @__PURE__ */ new Date();
421
+ cutoff.setDate(cutoff.getDate() - retentionDays);
422
+ const collections = [config.tables.requestLogs, config.tables.errorLogs];
423
+ for (const collectionName of collections) {
424
+ try {
425
+ const Model2 = getModel(collectionName);
426
+ const result = await Model2.deleteMany({ createdAt: { $lt: cutoff } });
427
+ console.log(
428
+ `[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from "${collectionName}" older than ${retentionDays} day(s).`
429
+ );
430
+ } catch (err) {
431
+ console.error(
432
+ `[db-backup-logging] Log cleanup failed for "${collectionName}":`,
433
+ err instanceof Error ? err.message : err
434
+ );
435
+ }
436
+ }
437
+ }
438
+ function initLogCleanupScheduler() {
439
+ const cron = loadNodeCron2();
440
+ const config = getConfig();
441
+ const schedule = process.env.LOG_CLEANUP_CRON_SCHEDULE || config.logCleanupCron?.schedule || "0 3 * * *";
442
+ const timezone = process.env.LOG_CLEANUP_TIMEZONE || config.logCleanupCron?.timezone || "UTC";
443
+ const retentionDays = Number(process.env.LOG_RETENTION_DAYS) || config.logCleanupCron?.retentionDays || 30;
444
+ if (!cron.validate(schedule)) {
445
+ throw new Error(
446
+ `[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: "${schedule}". Please provide a valid cron expression.`
447
+ );
448
+ }
449
+ cron.schedule(
450
+ schedule,
451
+ async () => {
452
+ console.log("[db-backup-logging] Log cleanup scheduler triggered.");
453
+ await purgeOldLogs(retentionDays);
454
+ },
455
+ { timezone }
456
+ );
457
+ console.log(
458
+ `[db-backup-logging] Log cleanup scheduler started \u2014 schedule: "${schedule}", timezone: "${timezone}", retentionDays: ${retentionDays}`
459
+ );
460
+ }
374
461
  export {
375
462
  errorMiddleware,
463
+ initBackupScheduler,
464
+ initLogCleanupScheduler,
376
465
  initializeNoticePackage,
377
466
  logCustomError,
378
467
  manualBackupTrigger,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts"],"sourcesContent":["import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[db-backup-logging] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for db-backup-logging to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[db-backup-logging] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the db-backup-logging package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[db-backup-logging] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[db-backup-logging] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[db-backup-logging] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[db-backup-logging] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[db-backup-logging] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n\n try {\n require.resolve('@aws-sdk/client-s3')\n } catch {\n throw new Error(\n '[db-backup-logging] AWS backup is enabled in config, but \"@aws-sdk/client-s3\" is not installed. Please run: npm install @aws-sdk/client-s3'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[db-backup-logging] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[db-backup-logging] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[db-backup-logging] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[db-backup-logging] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[db-backup-logging] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[db-backup-logging] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n"],"mappings":"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","names":["Model","Model"]}
1
+ {"version":3,"sources":["../src/utils/config.ts","../src/utils/db.ts","../src/utils/queue.ts","../src/exceptions/index.ts","../src/init.ts","../src/logger/index.ts","../src/backup/index.ts","../src/schedulers/backup.ts","../src/schedulers/cleanup.ts"],"sourcesContent":["import type { NoticeConfig } from '../types'\n\nlet _config: NoticeConfig | null = null\n\nexport function setConfig(config: NoticeConfig): void {\n _config = { ...config }\n}\n\nexport function getConfig(): NoticeConfig {\n if (!_config) {\n throw new Error(\n '[db-backup-logging] Package not initialized. Call initializeNoticePackage() first.'\n )\n }\n return _config\n}\n","import mongoose, { Schema, Model, Connection } from 'mongoose'\nimport { getConfig } from './config'\n\nconst modelCache = new Map<string, Model<any>>()\n\nlet localConnection: Connection | null = null\n\nexport function getDbConnection(): Connection {\n if (!localConnection) {\n localConnection = mongoose.createConnection(getConfig().dbUri)\n }\n return localConnection\n}\n\n/**\n * Gets or creates a Mongoose model for the given collection name.\n * Uses a flexible mixed schema to support any log structure.\n */\nexport function getModel(collectionName: string): Model<any> {\n if (modelCache.has(collectionName)) {\n return modelCache.get(collectionName)!\n }\n\n const schema = new Schema(\n {},\n {\n strict: false,\n timestamps: false,\n collection: collectionName,\n }\n )\n\n // Use a dedicated connection for db-backup-logging to avoid 'npm link' duplication issues\n const conn: Connection = getDbConnection()\n\n // Avoid OverwriteModelError — check if model already exists\n let model: Model<any>\n try {\n model = conn.model(collectionName)\n } catch {\n model = conn.model(collectionName, schema)\n }\n\n modelCache.set(collectionName, model)\n return model\n}\n\n/**\n * Creates a separate Mongoose connection for backup purposes (mongodump URI).\n */\nexport function getDbUri(): string {\n return getConfig().dbUri\n}\n","import { getModel } from './db'\n\ninterface QueueItem {\n collectionName: string\n data: Record<string, unknown>\n}\n\nconst queue: QueueItem[] = []\nlet isProcessing = false\nconst BATCH_SIZE = 50\nconst FLUSH_INTERVAL_MS = 5000\nconst MAX_RETRIES = 3\n\nlet flushTimer: ReturnType<typeof setInterval> | null = null\n\n/**\n * Enqueue a log entry for async writing to the database.\n * This never throws — all errors are silently logged to console.\n */\nexport function enqueue(collectionName: string, data: Record<string, unknown>): void {\n queue.push({ collectionName, data })\n\n // Start the flush interval if not already running\n if (!flushTimer) {\n flushTimer = setInterval(() => {\n flush().catch(() => {})\n }, FLUSH_INTERVAL_MS)\n\n // Unref so the timer doesn't keep the process alive\n if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {\n flushTimer.unref()\n }\n }\n\n // If queue reaches batch size, flush immediately\n if (queue.length >= BATCH_SIZE) {\n flush().catch(() => {})\n }\n}\n\n/**\n * Flush all queued log entries to the database.\n */\nexport async function flush(): Promise<void> {\n if (isProcessing || queue.length === 0) return\n isProcessing = true\n\n const batch = queue.splice(0, BATCH_SIZE)\n\n // Group by collection\n const grouped = new Map<string, Record<string, unknown>[]>()\n for (const item of batch) {\n const list = grouped.get(item.collectionName) || []\n list.push(item.data)\n grouped.set(item.collectionName, list)\n }\n\n for (const [collectionName, docs] of grouped) {\n let retries = 0\n while (retries < MAX_RETRIES) {\n try {\n const Model = getModel(collectionName)\n await Model.insertMany(docs, { ordered: false })\n break\n } catch (err) {\n retries++\n if (retries >= MAX_RETRIES) {\n console.error(\n `[db-backup-logging] Failed to write ${docs.length} logs to \"${collectionName}\" after ${MAX_RETRIES} retries:`,\n err instanceof Error ? err.message : err\n )\n } else {\n // Brief delay before retry\n await new Promise((r) => setTimeout(r, 100 * retries))\n }\n }\n }\n }\n\n isProcessing = false\n\n // If there are more items in the queue, continue flushing\n if (queue.length > 0) {\n flush().catch(() => {})\n }\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Log an error entry to the configured errorLogs collection.\n */\nfunction logError(\n error: Error | string,\n context?: {\n req?: Request\n userId?: string\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.req\n ? {\n method: context.req.method,\n url: context.req.originalUrl || context.req.url,\n headers: context.req.headers,\n body: context.req.body || null,\n }\n : null,\n userId:\n context?.userId ||\n (context?.req as any)?.user?.id ||\n (context?.req as any)?.user?._id ||\n null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent — error logging failure must never cause further errors\n }\n}\n\n/**\n * Register global process-level exception handlers.\n * Chains with existing handlers — does NOT override them.\n */\nexport function registerExceptionHandlers(): void {\n // Store references to any existing handlers so we can chain\n const existingUncaught = process.listeners('uncaughtException').slice()\n const existingRejection = process.listeners('unhandledRejection').slice()\n\n // uncaughtException\n process.on('uncaughtException', (error: Error) => {\n logError(error)\n // Don't prevent the default crash behavior for truly unrecoverable errors\n // The existing handlers will still fire since we're adding, not replacing\n })\n\n // unhandledRejection\n process.on('unhandledRejection', (reason: unknown) => {\n const error = reason instanceof Error ? reason : new Error(String(reason))\n logError(error)\n })\n}\n\n/**\n * Express error-handling middleware.\n * Must be registered AFTER all routes: app.use(errorMiddleware())\n */\nexport function errorMiddleware() {\n return (err: Error, req: Request, res: Response, next: NextFunction): void => {\n logError(err, { req })\n\n // Pass to the next error handler (don't swallow the error)\n next(err)\n }\n}\n\n/**\n * Public export — manually log a custom error with optional context.\n */\nexport function logCustomError(\n error: Error | string,\n context?: {\n userId?: string\n requestContext?: {\n method?: string\n url?: string\n headers?: Record<string, string | string[] | undefined>\n body?: unknown\n }\n }\n): void {\n try {\n const config = getConfig()\n const err = typeof error === 'string' ? new Error(error) : error\n\n const logEntry: Record<string, unknown> = {\n errorMessage: err.message,\n stackTrace: err.stack || null,\n requestContext: context?.requestContext || null,\n userId: context?.userId || null,\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.errorLogs, logEntry)\n } catch {\n // Silent\n }\n}\n","import type { NoticeConfig } from './types'\nimport { setConfig } from './utils/config'\nimport { registerExceptionHandlers } from './exceptions'\n\n/**\n * Initialize the db-backup-logging package.\n * Must be called once during application startup, after mongoose.connect().\n *\n * @param config - Full package configuration\n */\nexport function initializeNoticePackage(config: NoticeConfig): void {\n // Validate required fields\n if (!config.dbType) {\n throw new Error('[db-backup-logging] \"dbType\" is required in config.')\n }\n if (!config.dbUri) {\n throw new Error('[db-backup-logging] \"dbUri\" is required in config.')\n }\n if (!config.tables) {\n throw new Error('[db-backup-logging] \"tables\" configuration is required.')\n }\n if (!config.tables.requestLogs || !config.tables.errorLogs || !config.tables.backupLogs) {\n throw new Error(\n '[db-backup-logging] All table names (requestLogs, errorLogs, backupLogs) must be configured.'\n )\n }\n\n // Validate AWS config if enabled\n if (config.aws?.enabled) {\n if (!config.aws.bucketName || !config.aws.region) {\n throw new Error(\n '[db-backup-logging] AWS \"bucketName\" and \"region\" are required when AWS is enabled.'\n )\n }\n if (!config.aws.accessKeyId || !config.aws.secretAccessKey) {\n throw new Error(\n '[db-backup-logging] AWS \"accessKeyId\" and \"secretAccessKey\" are required when AWS is enabled.'\n )\n }\n\n try {\n require.resolve('@aws-sdk/client-s3')\n } catch {\n throw new Error(\n '[db-backup-logging] AWS backup is enabled in config, but \"@aws-sdk/client-s3\" is not installed. Please run: npm install @aws-sdk/client-s3'\n )\n }\n }\n\n // Validate local config if enabled\n if (config.local?.enabled) {\n if (!config.local.backupPath) {\n throw new Error(\n '[db-backup-logging] Local \"backupPath\" is required when local backup is enabled.'\n )\n }\n }\n\n // Store config\n setConfig(config)\n\n // Register global exception handlers\n registerExceptionHandlers()\n\n console.log(\n `[db-backup-logging] Initialized successfully (db: ${config.dbType}, service: ${config.serviceName || 'default'}, env: ${config.environment || process.env.NODE_ENV || 'development'})`\n )\n}\n","import type { Request, Response, NextFunction } from 'express'\nimport { getConfig } from '../utils/config'\nimport { enqueue } from '../utils/queue'\n\n/**\n * Express middleware factory for request/response logging.\n * Captures method, URL, headers, body, response, timing, user, IP.\n * All logging is non-blocking via the async queue.\n */\nexport function requestLogger() {\n return (req: Request, res: Response, next: NextFunction): void => {\n const startTime = Date.now()\n\n // Capture the original res.json to intercept the response body\n const originalJson = res.json.bind(res)\n let responseBody: unknown = undefined\n\n res.json = function (body: any) {\n responseBody = body\n return originalJson(body)\n }\n\n // Hook into the 'finish' event to log after response is sent\n res.on('finish', () => {\n try {\n const config = getConfig()\n const responseTime = Date.now() - startTime\n\n const logEntry: Record<string, unknown> = {\n method: req.method,\n url: req.originalUrl || req.url,\n headers: req.headers,\n requestBody: req.body || null,\n responseStatus: res.statusCode,\n responseBody: responseBody ?? null,\n responseTime,\n userId: (req as any).user?.id || (req as any).user?._id || (req as any).userId || null,\n ipAddress:\n (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ||\n req.socket?.remoteAddress ||\n 'unknown',\n serviceName: config.serviceName || null,\n environment: config.environment || process.env.NODE_ENV || null,\n createdAt: new Date(),\n }\n\n enqueue(config.tables.requestLogs, logEntry)\n } catch {\n // Silent — logging failure must never affect the request\n }\n })\n\n next()\n }\n}\n","import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport * as path from 'path'\nimport * as fs from 'fs'\nimport { getConfig } from '../utils/config'\nimport { getModel, getDbUri } from '../utils/db'\n\nconst execAsync = promisify(exec)\n\n/**\n * Generate a timestamp-based backup file name.\n */\nfunction getBackupFileName(): string {\n const now = new Date()\n const ts = now.toISOString().replace(/[:.]/g, '-')\n return `backup-${ts}`\n}\n\n/**\n * Perform a full MongoDB backup using mongodump.\n * Saves to local filesystem and/or uploads to S3 based on config.\n */\nasync function performBackup(): Promise<void> {\n const config = getConfig()\n const dbUri = getDbUri()\n const fileName = getBackupFileName()\n const archiveName = `${fileName}.zip`\n\n // Temp directory for backup\n const tempDir = path.resolve(process.cwd(), '.notice-backups-tmp')\n if (!fs.existsSync(tempDir)) {\n fs.mkdirSync(tempDir, { recursive: true })\n }\n\n const dumpDir = path.join(tempDir, fileName)\n const archivePath = path.join(tempDir, archiveName)\n\n try {\n // Run mongodump to a directory\n console.log(`[db-backup-logging] Starting backup: ${archiveName}`)\n await execAsync(`mongodump --uri=\"${dbUri}\" --out=\"${dumpDir}\"`)\n\n // Zip the directory\n await execAsync(`zip -r \"${archivePath}\" \"${fileName}\"`, { cwd: tempDir })\n\n const stats = fs.statSync(archivePath)\n const fileSize = stats.size\n\n // ── Save to local storage ──\n if (config.local?.enabled) {\n const localDir = path.resolve(process.cwd(), config.local.backupPath)\n if (!fs.existsSync(localDir)) {\n fs.mkdirSync(localDir, { recursive: true })\n }\n const localPath = path.join(localDir, archiveName)\n fs.copyFileSync(archivePath, localPath)\n console.log(`[db-backup-logging] Backup saved locally: ${localPath}`)\n\n // Log to database\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: 'local',\n fileSize,\n status: 'success',\n })\n }\n\n // ── Upload to S3 ──\n if (config.aws?.enabled) {\n await uploadToS3(archivePath, archiveName, fileSize)\n }\n\n // Cleanup temp files\n if (fs.existsSync(archivePath)) {\n fs.unlinkSync(archivePath)\n }\n if (fs.existsSync(dumpDir)) {\n fs.rmSync(dumpDir, { recursive: true, force: true })\n }\n\n console.log(`[db-backup-logging] Backup completed: ${archiveName}`)\n } catch (err) {\n console.error('[db-backup-logging] Backup failed:', err instanceof Error ? err.message : err)\n\n await logBackup({\n backupFileName: archiveName,\n backupType: 'full',\n location: config.aws?.enabled ? 's3' : 'local',\n fileSize: 0,\n status: 'failed',\n errorMessage: err instanceof Error ? err.message : String(err),\n }).catch(() => {})\n\n // Don't rethrow — backup failure should not crash the host app\n }\n}\n\n/**\n * Upload backup archive to AWS S3.\n */\nasync function uploadToS3(filePath: string, fileName: string, fileSize: number): Promise<void> {\n const config = getConfig()\n if (!config.aws) throw new Error('AWS config not provided')\n\n const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3')\n\n const s3Client = new S3Client({\n region: config.aws.region,\n credentials: {\n accessKeyId: config.aws.accessKeyId,\n secretAccessKey: config.aws.secretAccessKey,\n },\n })\n\n const fileBuffer = fs.readFileSync(filePath)\n\n const command = new PutObjectCommand({\n Bucket: config.aws.bucketName,\n Key: `backups/${fileName}`,\n Body: fileBuffer,\n ContentType: 'application/zip',\n })\n\n await s3Client.send(command)\n console.log(`[db-backup-logging] Backup uploaded to S3: backups/${fileName}`)\n\n await logBackup({\n backupFileName: fileName,\n backupType: 'full',\n location: 's3',\n fileSize,\n status: 'success',\n })\n}\n\n/**\n * Write a backup log entry to the configured backupLogs collection.\n */\nasync function logBackup(entry: {\n backupFileName: string\n backupType: string\n location: 'local' | 's3'\n fileSize: number\n status: 'success' | 'failed'\n errorMessage?: string\n}): Promise<void> {\n try {\n const config = getConfig()\n const Model = getModel(config.tables.backupLogs)\n await Model.create({\n ...entry,\n createdAt: new Date(),\n })\n } catch (err) {\n console.error(\n '[db-backup-logging] Failed to write backup log:',\n err instanceof Error ? err.message : err\n )\n }\n}\n\n/**\n * Public export — trigger a manual database backup.\n */\nexport async function manualBackupTrigger(): Promise<void> {\n await performBackup()\n}\n","import { getConfig } from '../utils/config'\nimport { manualBackupTrigger } from '../backup'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initBackupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Start an automatic backup cron job.\n *\n * Schedule and timezone are resolved in order:\n * 1. Environment variables BACKUP_CRON_SCHEDULE / BACKUP_TIMEZONE\n * 2. config.backupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 2 * * *\" = 2 AM daily, UTC)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initBackupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.BACKUP_CRON_SCHEDULE ||\n config.backupCron?.schedule ||\n '0 2 * * *'\n\n const timezone =\n process.env.BACKUP_TIMEZONE ||\n config.backupCron?.timezone ||\n 'UTC'\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid BACKUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Backup scheduler triggered.')\n await manualBackupTrigger()\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Backup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\"`\n )\n}\n","import { getConfig } from '../utils/config'\nimport { getModel } from '../utils/db'\n\n/**\n * Dynamically resolve node-cron from the host project.\n * Throws a helpful error if not installed.\n */\nfunction loadNodeCron(): typeof import('node-cron') {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n return require('node-cron')\n } catch {\n throw new Error(\n '[db-backup-logging] node-cron is required to use initLogCleanupScheduler().\\n' +\n 'Please install it in your root project:\\n' +\n ' npm install node-cron\\n' +\n ' yarn add node-cron'\n )\n }\n}\n\n/**\n * Delete request/error log documents older than retentionDays.\n */\nasync function purgeOldLogs(retentionDays: number): Promise<void> {\n const config = getConfig()\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - retentionDays)\n\n const collections = [config.tables.requestLogs, config.tables.errorLogs]\n\n for (const collectionName of collections) {\n try {\n const Model = getModel(collectionName)\n const result = await Model.deleteMany({ createdAt: { $lt: cutoff } })\n console.log(\n `[db-backup-logging] Log cleanup: removed ${result.deletedCount} record(s) from \"${collectionName}\" older than ${retentionDays} day(s).`\n )\n } catch (err) {\n console.error(\n `[db-backup-logging] Log cleanup failed for \"${collectionName}\":`,\n err instanceof Error ? err.message : err\n )\n }\n }\n}\n\n/**\n * Start an automatic log cleanup cron job.\n *\n * Schedule, timezone and retention days are resolved in order:\n * 1. Environment variables LOG_CLEANUP_CRON_SCHEDULE / LOG_CLEANUP_TIMEZONE / LOG_RETENTION_DAYS\n * 2. config.logCleanupCron passed to initializeNoticePackage()\n * 3. Built-in defaults (\"0 3 * * *\" = 3 AM daily, UTC, 30 days retention)\n *\n * Requires node-cron >= 3.0.0 installed in the host project.\n */\nexport function initLogCleanupScheduler(): void {\n const cron = loadNodeCron()\n const config = getConfig()\n\n const schedule =\n process.env.LOG_CLEANUP_CRON_SCHEDULE ||\n config.logCleanupCron?.schedule ||\n '0 3 * * *'\n\n const timezone =\n process.env.LOG_CLEANUP_TIMEZONE ||\n config.logCleanupCron?.timezone ||\n 'UTC'\n\n const retentionDays =\n Number(process.env.LOG_RETENTION_DAYS) ||\n config.logCleanupCron?.retentionDays ||\n 30\n\n if (!cron.validate(schedule)) {\n throw new Error(\n `[db-backup-logging] Invalid LOG_CLEANUP_CRON_SCHEDULE: \"${schedule}\". ` +\n 'Please provide a valid cron expression.'\n )\n }\n\n cron.schedule(\n schedule,\n async () => {\n console.log('[db-backup-logging] Log cleanup scheduler triggered.')\n await purgeOldLogs(retentionDays)\n },\n { timezone }\n )\n\n console.log(\n `[db-backup-logging] Log cleanup scheduler started — schedule: \"${schedule}\", timezone: \"${timezone}\", retentionDays: ${retentionDays}`\n )\n}\n"],"mappings":"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","names":["Model","Model","loadNodeCron","Model"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "db-backup-logging",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Production-ready NPM package for database backup, request/response logging, and exception tracking",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -32,9 +32,10 @@
32
32
  "author": "",
33
33
  "license": "ISC",
34
34
  "peerDependencies": {
35
- "mongoose": ">=6.0.0",
35
+ "@aws-sdk/client-s3": "^3.490.0",
36
36
  "express": ">=4.0.0",
37
- "@aws-sdk/client-s3": "^3.490.0"
37
+ "mongoose": ">=6.0.0",
38
+ "node-cron": ">=3.0.0"
38
39
  },
39
40
  "peerDependenciesMeta": {
40
41
  "mongoose": {
@@ -45,18 +46,21 @@
45
46
  },
46
47
  "@aws-sdk/client-s3": {
47
48
  "optional": true
49
+ },
50
+ "node-cron": {
51
+ "optional": true
48
52
  }
49
53
  },
50
54
  "devDependencies": {
51
55
  "@types/express": "^4.17.21",
52
56
  "@types/node": "^20.11.0",
57
+ "@types/node-cron": "^3.0.11",
53
58
  "express": "^4.18.2",
54
59
  "mongoose": "^8.1.0",
55
60
  "tsup": "^8.0.1",
56
- "typescript": "^5.3.3",
57
- "@aws-sdk/client-s3": "^3.490.0"
61
+ "typescript": "^5.3.3"
58
62
  },
59
63
  "engines": {
60
64
  "node": ">=16.0.0"
61
65
  }
62
- }
66
+ }