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 +66 -12
- package/dist/index.d.mts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +89 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -6
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.
|
|
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
|
-
"
|
|
35
|
+
"@aws-sdk/client-s3": "^3.490.0",
|
|
36
36
|
"express": ">=4.0.0",
|
|
37
|
-
"
|
|
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
|
+
}
|