@heavybit/pendoadmin-shared-lib 1.1.6 → 1.1.8
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/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +1 -1
- package/dist/database/index.d.ts +796 -0
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/index.js +91 -0
- package/dist/database/index.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/logging/index.d.ts.map +1 -1
- package/dist/logging/index.js +111 -10
- package/dist/logging/index.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +4 -3
- package/src/config/index.ts +1 -1
- package/src/database/index.ts +143 -0
- package/src/index.ts +4 -2
- package/src/logging/index.ts +124 -12
- package/src/types/index.ts +2 -0
package/src/logging/index.ts
CHANGED
|
@@ -1,31 +1,143 @@
|
|
|
1
1
|
import winston from 'winston';
|
|
2
|
+
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
3
|
+
import { mkdirSync } from 'fs';
|
|
4
|
+
import { resolve } from 'path';
|
|
2
5
|
|
|
3
|
-
const { combine, timestamp, json, errors, colorize,
|
|
6
|
+
const { combine, timestamp, json, errors, colorize, printf, splat } = winston.format;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Custom dev console format that renders meta cleanly and warns when callers
|
|
10
|
+
* pass a Promise as the message (a common forgotten-await bug).
|
|
11
|
+
*
|
|
12
|
+
* info: SMS sent { batchId: 'xyz', recipients: 1 }
|
|
13
|
+
* error: DB failed Error: connection refused
|
|
14
|
+
* at ...stack...
|
|
15
|
+
*/
|
|
16
|
+
const devConsoleFormat = printf((info) => {
|
|
17
|
+
const { level, message, timestamp: ts, service, stack, ...rest } = info as Record<string, unknown> & {
|
|
18
|
+
level: string;
|
|
19
|
+
message: unknown;
|
|
20
|
+
timestamp?: string;
|
|
21
|
+
service?: string;
|
|
22
|
+
stack?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Detect forgotten-await: someone passed a Promise as the message.
|
|
26
|
+
let renderedMessage: string;
|
|
27
|
+
if (message && typeof (message as { then?: unknown }).then === 'function') {
|
|
28
|
+
renderedMessage = '[Promise — forgot to await?]';
|
|
29
|
+
} else if (message instanceof Error) {
|
|
30
|
+
renderedMessage = message.message;
|
|
31
|
+
} else if (typeof message === 'object' && message !== null) {
|
|
32
|
+
renderedMessage = JSON.stringify(message);
|
|
33
|
+
} else {
|
|
34
|
+
renderedMessage = String(message ?? '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Strip winston-internal symbols/keys before serialising meta
|
|
38
|
+
const meta: Record<string, unknown> = {};
|
|
39
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
40
|
+
if (k === 'splat' || k.startsWith('Symbol(')) continue;
|
|
41
|
+
meta[k] = v;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
|
|
45
|
+
const tag = service ? `[${service}] ` : '';
|
|
46
|
+
const stackStr = stack ? `\n${stack}` : '';
|
|
47
|
+
return `${ts ?? ''} ${level}: ${tag}${renderedMessage}${metaStr}${stackStr}`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* LOG_TARGET controls where logs are shipped:
|
|
52
|
+
* console — stdout only (default for dev)
|
|
53
|
+
* file — rotating files only
|
|
54
|
+
* both — stdout + rotating files (default for staging/production)
|
|
55
|
+
*
|
|
56
|
+
* LOG_DIR — directory for log files (default: /app/logs)
|
|
57
|
+
* LOG_MAX_SIZE — max size per log file before rotation (default: 20m)
|
|
58
|
+
* LOG_MAX_FILES — retention period or count, e.g. 14d or 30 (default: 14d)
|
|
59
|
+
*/
|
|
60
|
+
function resolveLogTarget(): 'console' | 'file' | 'both' {
|
|
61
|
+
const raw = (process.env.LOG_TARGET || 'console').toLowerCase().trim();
|
|
62
|
+
if (raw === 'file' || raw === 'both') return raw;
|
|
63
|
+
return 'console';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildFileTransports(service: string, level: string): DailyRotateFile[] {
|
|
67
|
+
const logDir = resolve(process.env.LOG_DIR || '/app/logs');
|
|
68
|
+
const maxSize = process.env.LOG_MAX_SIZE || '20m';
|
|
69
|
+
const maxFiles = process.env.LOG_MAX_FILES || '14d';
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
mkdirSync(logDir, { recursive: true });
|
|
73
|
+
} catch {
|
|
74
|
+
// Ignore — directory may already exist or be read-only
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const shared = {
|
|
78
|
+
dirname: logDir,
|
|
79
|
+
datePattern: 'YYYY-MM-DD',
|
|
80
|
+
zippedArchive: true,
|
|
81
|
+
maxSize,
|
|
82
|
+
maxFiles,
|
|
83
|
+
format: combine(errors({ stack: true }), splat(), timestamp(), json()),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return [
|
|
87
|
+
new DailyRotateFile({
|
|
88
|
+
...shared,
|
|
89
|
+
filename: `${service}-%DATE%-combined.log`,
|
|
90
|
+
level,
|
|
91
|
+
}),
|
|
92
|
+
new DailyRotateFile({
|
|
93
|
+
...shared,
|
|
94
|
+
filename: `${service}-%DATE%-error.log`,
|
|
95
|
+
level: 'error',
|
|
96
|
+
}),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
4
99
|
|
|
5
100
|
export function createLogger(service: string, level?: string): winston.Logger {
|
|
6
101
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
102
|
+
const resolvedLevel = level || process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug');
|
|
103
|
+
const target = resolveLogTarget();
|
|
7
104
|
|
|
8
|
-
|
|
9
|
-
level: level || process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
|
|
10
|
-
defaultMeta: { service },
|
|
105
|
+
const consoleTransport = new winston.transports.Console({
|
|
11
106
|
format: combine(
|
|
12
107
|
errors({ stack: true }),
|
|
108
|
+
splat(),
|
|
13
109
|
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
14
|
-
isProduction ? json() : combine(colorize(),
|
|
110
|
+
isProduction ? json() : combine(colorize({ level: true }), devConsoleFormat),
|
|
15
111
|
),
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const transports: winston.transport[] = [];
|
|
115
|
+
if (target === 'console' || target === 'both') transports.push(consoleTransport);
|
|
116
|
+
if (target === 'file' || target === 'both') transports.push(...buildFileTransports(service, resolvedLevel));
|
|
117
|
+
|
|
118
|
+
return winston.createLogger({
|
|
119
|
+
level: resolvedLevel,
|
|
120
|
+
defaultMeta: { service },
|
|
121
|
+
format: combine(errors({ stack: true }), splat(), timestamp()),
|
|
122
|
+
transports,
|
|
19
123
|
});
|
|
20
124
|
}
|
|
21
125
|
|
|
22
126
|
export function createAuditLogger(service: string): winston.Logger {
|
|
127
|
+
const target = resolveLogTarget();
|
|
128
|
+
|
|
129
|
+
const consoleTransport = new winston.transports.Console({
|
|
130
|
+
format: combine(splat(), timestamp(), json()),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const transports: winston.transport[] = [];
|
|
134
|
+
if (target === 'console' || target === 'both') transports.push(consoleTransport);
|
|
135
|
+
if (target === 'file' || target === 'both') transports.push(...buildFileTransports(`${service}-audit`, 'info'));
|
|
136
|
+
|
|
23
137
|
return winston.createLogger({
|
|
24
138
|
level: 'info',
|
|
25
139
|
defaultMeta: { service, type: 'audit' },
|
|
26
|
-
format: combine(timestamp(), json()),
|
|
27
|
-
transports
|
|
28
|
-
new winston.transports.Console(),
|
|
29
|
-
],
|
|
140
|
+
format: combine(splat(), timestamp(), json()),
|
|
141
|
+
transports,
|
|
30
142
|
});
|
|
31
143
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface IJwtPayload {
|
|
|
53
53
|
roles: string[];
|
|
54
54
|
permissions: string[];
|
|
55
55
|
isSuperAdmin: boolean;
|
|
56
|
+
/** Whether the user has completed post-login OTP verification for this session. */
|
|
57
|
+
otpVerified?: boolean;
|
|
56
58
|
iat?: number;
|
|
57
59
|
exp?: number;
|
|
58
60
|
iss?: string;
|