logixia 1.2.1 → 1.3.0
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/{index-BDRSTjUt.d.ts → index-CHIsdA9n.d.ts} +53 -2
- package/dist/index-CHIsdA9n.d.ts.map +1 -0
- package/dist/{index-Drrzn-Yg.d.mts → index-iDTW2-eY.d.mts} +53 -2
- package/dist/index-iDTW2-eY.d.mts.map +1 -0
- package/dist/index.d.mts +89 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +89 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/dist/{logitron-logger.module-iO8DPE7_.mjs → logitron-logger.module-2AzkadqZ.mjs} +226 -8
- package/dist/logitron-logger.module-2AzkadqZ.mjs.map +1 -0
- package/dist/{logitron-logger.module-X6nGDVGC.js → logitron-logger.module-BqNKp0Fs.js} +240 -4
- package/dist/logitron-logger.module-BqNKp0Fs.js.map +1 -0
- package/dist/{logitron-logger.module-CY3t8yK6.d.mts → logitron-logger.module-C0G8JGVf.d.ts} +6 -4
- package/dist/logitron-logger.module-C0G8JGVf.d.ts.map +1 -0
- package/dist/{logitron-logger.module-DgEldK9V.d.ts → logitron-logger.module-DQKaZTJL.d.mts} +6 -4
- package/dist/logitron-logger.module-DQKaZTJL.d.mts.map +1 -0
- package/dist/middleware.d.mts +83 -0
- package/dist/middleware.d.mts.map +1 -0
- package/dist/middleware.d.ts +83 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +132 -0
- package/dist/middleware.js.map +1 -0
- package/dist/middleware.mjs +130 -0
- package/dist/middleware.mjs.map +1 -0
- package/dist/nest.d.mts +2 -2
- package/dist/nest.d.ts +2 -2
- package/dist/nest.js +2 -2
- package/dist/nest.mjs +2 -2
- package/dist/testing.d.mts +67 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.d.ts +67 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +164 -0
- package/dist/testing.js.map +1 -0
- package/dist/testing.mjs +163 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/{transport.manager-Vi__pagn.mjs → transport.manager-5VVdqS3o.mjs} +51 -2
- package/dist/transport.manager-5VVdqS3o.mjs.map +1 -0
- package/dist/{transport.manager-C3Xr7Tvi.js → transport.manager-DCOm4uIQ.js} +51 -2
- package/dist/transport.manager-DCOm4uIQ.js.map +1 -0
- package/dist/transports.d.mts +38 -1
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +38 -1
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +1 -1
- package/dist/transports.mjs +1 -1
- package/package.json +21 -1
- package/dist/index-BDRSTjUt.d.ts.map +0 -1
- package/dist/index-Drrzn-Yg.d.mts.map +0 -1
- package/dist/logitron-logger.module-CY3t8yK6.d.mts.map +0 -1
- package/dist/logitron-logger.module-DgEldK9V.d.ts.map +0 -1
- package/dist/logitron-logger.module-X6nGDVGC.js.map +0 -1
- package/dist/logitron-logger.module-iO8DPE7_.mjs.map +0 -1
- package/dist/transport.manager-C3Xr7Tvi.js.map +0 -1
- package/dist/transport.manager-Vi__pagn.mjs.map +0 -1
package/dist/nest.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import
|
|
1
|
+
import { a as TraceMiddleware, g as setTraceId, i as WebSocketTraceInterceptor, m as getCurrentTraceId, n as LOGIXIA_LOGGER_PREFIX, o as KafkaTraceInterceptor, p as generateTraceId, r as LogixiaLoggerModule, s as LogixiaLoggerService, t as LOGIXIA_LOGGER_CONFIG } from "./logitron-logger.module-2AzkadqZ.mjs";
|
|
2
|
+
import "./transport.manager-5VVdqS3o.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/core/request-context.ts
|
|
5
5
|
var RequestContextManager = class {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { l as IBaseLogger, m as LogEntry } from "./index-iDTW2-eY.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/testing/mock-logger.d.ts
|
|
4
|
+
|
|
5
|
+
interface MockLogCall {
|
|
6
|
+
level: string;
|
|
7
|
+
message: string;
|
|
8
|
+
data?: Record<string, unknown>;
|
|
9
|
+
/** Full LogEntry shape for assertions that inspect the complete record. */
|
|
10
|
+
entry: Pick<LogEntry, 'level' | 'message' | 'context'> & {
|
|
11
|
+
data?: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
type LogMatcher = string | RegExp | ((call: MockLogCall) => boolean) | Partial<Record<'message' | 'level' | string, unknown>>;
|
|
15
|
+
interface MockLoggerInstance {
|
|
16
|
+
/** The logger itself — pass this where an IBaseLogger is expected. */
|
|
17
|
+
readonly logger: IBaseLogger;
|
|
18
|
+
/** Every call recorded across all levels, in insertion order. */
|
|
19
|
+
readonly calls: MockLogCall[];
|
|
20
|
+
/**
|
|
21
|
+
* Get all calls for a specific level (case-insensitive). If `level` is
|
|
22
|
+
* omitted, returns all calls across every level.
|
|
23
|
+
*/
|
|
24
|
+
getCalls(level?: string): MockLogCall[];
|
|
25
|
+
/** Return the most recent call, optionally filtered by level. */
|
|
26
|
+
getLastCall(level?: string): MockLogCall | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Assert that at least one recorded call matches `matcher`.
|
|
29
|
+
*
|
|
30
|
+
* - string → exact message match
|
|
31
|
+
* - RegExp → message test
|
|
32
|
+
* - function → receives the full MockLogCall
|
|
33
|
+
* - object → every key/value must match (supports RegExp values)
|
|
34
|
+
*
|
|
35
|
+
* Throws an Error with a descriptive message if the assertion fails (works
|
|
36
|
+
* with both Vitest `expect` and Jest matchers via `.toThrow()`, or you can
|
|
37
|
+
* let it propagate directly).
|
|
38
|
+
*/
|
|
39
|
+
expectLog(level: string, matcher?: LogMatcher): void;
|
|
40
|
+
/**
|
|
41
|
+
* Assert that NO recorded call matches `matcher` at the given level.
|
|
42
|
+
*/
|
|
43
|
+
expectNoLog(level: string, matcher?: LogMatcher): void;
|
|
44
|
+
/** Clear all recorded calls. Call between tests. */
|
|
45
|
+
reset(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Silence — when true, the mock suppresses all console output from the
|
|
48
|
+
* logger. Default: true.
|
|
49
|
+
*/
|
|
50
|
+
silent: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a mock logger for use in tests.
|
|
54
|
+
*
|
|
55
|
+
* The returned object implements `IBaseLogger` and additionally exposes `.calls`,
|
|
56
|
+
* `.getCalls()`, `.getLastCall()`, `.expectLog()`, `.expectNoLog()`, and `.reset()`.
|
|
57
|
+
*
|
|
58
|
+
* @param options.silent Suppress console output (default: true)
|
|
59
|
+
* @param options.context Optional context label written into every `entry.context`
|
|
60
|
+
*/
|
|
61
|
+
declare function createMockLogger(options?: {
|
|
62
|
+
silent?: boolean;
|
|
63
|
+
context?: string;
|
|
64
|
+
}): MockLoggerInstance;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { type LogMatcher, type MockLogCall, type MockLoggerInstance, createMockLogger };
|
|
67
|
+
//# sourceMappingURL=testing.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.mts","names":[],"sources":["../src/testing/mock-logger.ts"],"sourcesContent":[],"mappings":";;;;AAoCmB,UAhBF,WAAA,CAgBE;EAED,KAAA,EAAA,MAAA;EAKU,OAAA,EAAA,MAAA;EAEG,IAAA,CAAA,EAtBtB,MAsBsB,CAAA,MAAA,EAAA,OAAA,CAAA;EAaM;EAIE,KAAA,EArC9B,IAqC8B,CArCzB,QAqCyB,EAAA,OAAA,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA;IAAU,IAAA,CAAA,EArCmB,MAqCnB,CAAA,MAAA,EAAA,OAAA,CAAA;EA2DjC,CAAA;;KA7FJ,UAAA,YAER,iBACQ,2BACR,QAAQ;UAEK,kBAAA;;mBAEE;;kBAED;;;;;4BAKU;;+BAEG;;;;;;;;;;;;;qCAaM;;;;uCAIE;;;;;;;;;;;;;;;;;;iBA2DvB,gBAAA;;;IAEb"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { l as IBaseLogger, m as LogEntry } from "./index-CHIsdA9n.js";
|
|
2
|
+
|
|
3
|
+
//#region src/testing/mock-logger.d.ts
|
|
4
|
+
|
|
5
|
+
interface MockLogCall {
|
|
6
|
+
level: string;
|
|
7
|
+
message: string;
|
|
8
|
+
data?: Record<string, unknown>;
|
|
9
|
+
/** Full LogEntry shape for assertions that inspect the complete record. */
|
|
10
|
+
entry: Pick<LogEntry, 'level' | 'message' | 'context'> & {
|
|
11
|
+
data?: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
type LogMatcher = string | RegExp | ((call: MockLogCall) => boolean) | Partial<Record<'message' | 'level' | string, unknown>>;
|
|
15
|
+
interface MockLoggerInstance {
|
|
16
|
+
/** The logger itself — pass this where an IBaseLogger is expected. */
|
|
17
|
+
readonly logger: IBaseLogger;
|
|
18
|
+
/** Every call recorded across all levels, in insertion order. */
|
|
19
|
+
readonly calls: MockLogCall[];
|
|
20
|
+
/**
|
|
21
|
+
* Get all calls for a specific level (case-insensitive). If `level` is
|
|
22
|
+
* omitted, returns all calls across every level.
|
|
23
|
+
*/
|
|
24
|
+
getCalls(level?: string): MockLogCall[];
|
|
25
|
+
/** Return the most recent call, optionally filtered by level. */
|
|
26
|
+
getLastCall(level?: string): MockLogCall | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Assert that at least one recorded call matches `matcher`.
|
|
29
|
+
*
|
|
30
|
+
* - string → exact message match
|
|
31
|
+
* - RegExp → message test
|
|
32
|
+
* - function → receives the full MockLogCall
|
|
33
|
+
* - object → every key/value must match (supports RegExp values)
|
|
34
|
+
*
|
|
35
|
+
* Throws an Error with a descriptive message if the assertion fails (works
|
|
36
|
+
* with both Vitest `expect` and Jest matchers via `.toThrow()`, or you can
|
|
37
|
+
* let it propagate directly).
|
|
38
|
+
*/
|
|
39
|
+
expectLog(level: string, matcher?: LogMatcher): void;
|
|
40
|
+
/**
|
|
41
|
+
* Assert that NO recorded call matches `matcher` at the given level.
|
|
42
|
+
*/
|
|
43
|
+
expectNoLog(level: string, matcher?: LogMatcher): void;
|
|
44
|
+
/** Clear all recorded calls. Call between tests. */
|
|
45
|
+
reset(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Silence — when true, the mock suppresses all console output from the
|
|
48
|
+
* logger. Default: true.
|
|
49
|
+
*/
|
|
50
|
+
silent: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a mock logger for use in tests.
|
|
54
|
+
*
|
|
55
|
+
* The returned object implements `IBaseLogger` and additionally exposes `.calls`,
|
|
56
|
+
* `.getCalls()`, `.getLastCall()`, `.expectLog()`, `.expectNoLog()`, and `.reset()`.
|
|
57
|
+
*
|
|
58
|
+
* @param options.silent Suppress console output (default: true)
|
|
59
|
+
* @param options.context Optional context label written into every `entry.context`
|
|
60
|
+
*/
|
|
61
|
+
declare function createMockLogger(options?: {
|
|
62
|
+
silent?: boolean;
|
|
63
|
+
context?: string;
|
|
64
|
+
}): MockLoggerInstance;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { type LogMatcher, type MockLogCall, type MockLoggerInstance, createMockLogger };
|
|
67
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","names":[],"sources":["../src/testing/mock-logger.ts"],"sourcesContent":[],"mappings":";;;;AAoCmB,UAhBF,WAAA,CAgBE;EAED,KAAA,EAAA,MAAA;EAKU,OAAA,EAAA,MAAA;EAEG,IAAA,CAAA,EAtBtB,MAsBsB,CAAA,MAAA,EAAA,OAAA,CAAA;EAaM;EAIE,KAAA,EArC9B,IAqC8B,CArCzB,QAqCyB,EAAA,OAAA,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA;IAAU,IAAA,CAAA,EArCmB,MAqCnB,CAAA,MAAA,EAAA,OAAA,CAAA;EA2DjC,CAAA;;KA7FJ,UAAA,YAER,iBACQ,2BACR,QAAQ;UAEK,kBAAA;;mBAEE;;kBAED;;;;;4BAKU;;+BAEG;;;;;;;;;;;;;qCAaM;;;;uCAIE;;;;;;;;;;;;;;;;;;iBA2DvB,gBAAA;;;IAEb"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/testing/mock-logger.ts
|
|
3
|
+
function resolveCallField(call, key) {
|
|
4
|
+
var _call$data;
|
|
5
|
+
if (key === "message") return call.message;
|
|
6
|
+
if (key === "level") return call.level;
|
|
7
|
+
return ((_call$data = call.data) === null || _call$data === void 0 ? void 0 : _call$data[key]) ?? call.entry[key];
|
|
8
|
+
}
|
|
9
|
+
function formatMatcherDescription(matcher) {
|
|
10
|
+
if (matcher === void 0) return "(any)";
|
|
11
|
+
if (matcher instanceof RegExp) return matcher.toString();
|
|
12
|
+
return JSON.stringify(matcher);
|
|
13
|
+
}
|
|
14
|
+
function matchesCall(call, matcher) {
|
|
15
|
+
if (matcher === void 0) return true;
|
|
16
|
+
if (typeof matcher === "string") return call.message === matcher;
|
|
17
|
+
if (matcher instanceof RegExp) return matcher.test(call.message);
|
|
18
|
+
if (typeof matcher === "function") return matcher(call);
|
|
19
|
+
for (const [key, expected] of Object.entries(matcher)) {
|
|
20
|
+
const actual = resolveCallField(call, key);
|
|
21
|
+
if (expected instanceof RegExp) {
|
|
22
|
+
if (!expected.test(String(actual ?? ""))) return false;
|
|
23
|
+
} else if (actual !== expected) return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function formatCalls(calls) {
|
|
28
|
+
if (calls.length === 0) return " (no calls recorded)";
|
|
29
|
+
return calls.map((c) => ` [${c.level}] "${c.message}" ${c.data ? JSON.stringify(c.data) : ""}`).join("\n");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a mock logger for use in tests.
|
|
33
|
+
*
|
|
34
|
+
* The returned object implements `IBaseLogger` and additionally exposes `.calls`,
|
|
35
|
+
* `.getCalls()`, `.getLastCall()`, `.expectLog()`, `.expectNoLog()`, and `.reset()`.
|
|
36
|
+
*
|
|
37
|
+
* @param options.silent Suppress console output (default: true)
|
|
38
|
+
* @param options.context Optional context label written into every `entry.context`
|
|
39
|
+
*/
|
|
40
|
+
function createMockLogger(options = {}) {
|
|
41
|
+
const { silent = true, context } = options;
|
|
42
|
+
const _calls = [];
|
|
43
|
+
function record(level, messageOrError, data) {
|
|
44
|
+
const message = messageOrError instanceof Error ? messageOrError.message : messageOrError;
|
|
45
|
+
const extraData = messageOrError instanceof Error ? {
|
|
46
|
+
...data,
|
|
47
|
+
error: {
|
|
48
|
+
message: messageOrError.message,
|
|
49
|
+
stack: messageOrError.stack
|
|
50
|
+
}
|
|
51
|
+
} : data;
|
|
52
|
+
_calls.push({
|
|
53
|
+
level,
|
|
54
|
+
message,
|
|
55
|
+
...extraData !== void 0 ? { data: extraData } : {},
|
|
56
|
+
entry: {
|
|
57
|
+
level,
|
|
58
|
+
message,
|
|
59
|
+
...context !== void 0 ? { context } : {},
|
|
60
|
+
...extraData !== void 0 ? { data: extraData } : {}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const logger = {
|
|
65
|
+
async error(messageOrError, data) {
|
|
66
|
+
record("error", messageOrError, data);
|
|
67
|
+
},
|
|
68
|
+
async warn(message, data) {
|
|
69
|
+
record("warn", message, data);
|
|
70
|
+
},
|
|
71
|
+
async info(message, data) {
|
|
72
|
+
record("info", message, data);
|
|
73
|
+
},
|
|
74
|
+
async debug(message, data) {
|
|
75
|
+
record("debug", message, data);
|
|
76
|
+
},
|
|
77
|
+
async trace(message, data) {
|
|
78
|
+
record("trace", message, data);
|
|
79
|
+
},
|
|
80
|
+
async verbose(message, data) {
|
|
81
|
+
record("verbose", message, data);
|
|
82
|
+
},
|
|
83
|
+
async logLevel(level, message, data) {
|
|
84
|
+
record(level, message, data);
|
|
85
|
+
},
|
|
86
|
+
time(_label) {},
|
|
87
|
+
async timeEnd(_label) {
|
|
88
|
+
return 0;
|
|
89
|
+
},
|
|
90
|
+
async timeAsync(_label, fn) {
|
|
91
|
+
return fn();
|
|
92
|
+
},
|
|
93
|
+
setLevel(_level) {},
|
|
94
|
+
getLevel() {
|
|
95
|
+
return "info";
|
|
96
|
+
},
|
|
97
|
+
setContext(_ctx) {},
|
|
98
|
+
getContext() {
|
|
99
|
+
return context;
|
|
100
|
+
},
|
|
101
|
+
enableField(_f) {},
|
|
102
|
+
disableField(_f) {},
|
|
103
|
+
isFieldEnabled(_f) {
|
|
104
|
+
return true;
|
|
105
|
+
},
|
|
106
|
+
getFieldState() {
|
|
107
|
+
return {};
|
|
108
|
+
},
|
|
109
|
+
resetFieldState() {},
|
|
110
|
+
enableTransportLevelPrompting() {},
|
|
111
|
+
disableTransportLevelPrompting() {},
|
|
112
|
+
setTransportLevels(_id, _levels) {},
|
|
113
|
+
getTransportLevels(_id) {},
|
|
114
|
+
clearTransportLevelPreferences() {},
|
|
115
|
+
getAvailableTransports() {
|
|
116
|
+
return [];
|
|
117
|
+
},
|
|
118
|
+
child(ctx, _data) {
|
|
119
|
+
return createMockLogger({
|
|
120
|
+
silent,
|
|
121
|
+
context: ctx
|
|
122
|
+
}).logger;
|
|
123
|
+
},
|
|
124
|
+
async close() {}
|
|
125
|
+
};
|
|
126
|
+
const instance = {
|
|
127
|
+
get logger() {
|
|
128
|
+
return logger;
|
|
129
|
+
},
|
|
130
|
+
get calls() {
|
|
131
|
+
return _calls;
|
|
132
|
+
},
|
|
133
|
+
getCalls(level) {
|
|
134
|
+
if (!level) return [..._calls];
|
|
135
|
+
const l = level.toLowerCase();
|
|
136
|
+
return _calls.filter((c) => c.level.toLowerCase() === l);
|
|
137
|
+
},
|
|
138
|
+
getLastCall(level) {
|
|
139
|
+
const filtered = level ? _calls.filter((c) => c.level.toLowerCase() === level.toLowerCase()) : _calls;
|
|
140
|
+
return filtered[filtered.length - 1];
|
|
141
|
+
},
|
|
142
|
+
expectLog(level, matcher) {
|
|
143
|
+
if (!instance.getCalls(level).some((c) => matchesCall(c, matcher))) {
|
|
144
|
+
const matcherDesc = formatMatcherDescription(matcher);
|
|
145
|
+
throw new Error(`Expected at least one [${level}] log matching ${matcherDesc}.\nRecorded calls:\n${formatCalls(_calls)}`);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
expectNoLog(level, matcher) {
|
|
149
|
+
if (instance.getCalls(level).some((c) => matchesCall(c, matcher))) {
|
|
150
|
+
const matcherDesc = formatMatcherDescription(matcher);
|
|
151
|
+
throw new Error(`Expected NO [${level}] log matching ${matcherDesc}, but one was found.\nRecorded calls:\n${formatCalls(_calls)}`);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
reset() {
|
|
155
|
+
_calls.length = 0;
|
|
156
|
+
},
|
|
157
|
+
silent
|
|
158
|
+
};
|
|
159
|
+
return instance;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
exports.createMockLogger = createMockLogger;
|
|
164
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","names":["_calls: MockLogCall[]","logger: IBaseLogger","instance: MockLoggerInstance"],"sources":["../src/testing/mock-logger.ts"],"sourcesContent":["/**\n * logixia/testing — Zero-dependency mock logger for Vitest & Jest.\n *\n * @example\n * ```ts\n * import { createMockLogger } from 'logixia/testing';\n *\n * const mock = createMockLogger();\n * await myService.doSomething(mock.logger);\n *\n * mock.expectLog('info', { message: 'order created' });\n * mock.expectLog('error', { message: /failed/ });\n * mock.reset();\n * ```\n */\n\nimport type { IBaseLogger, LogEntry } from '../types';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface MockLogCall {\n level: string;\n message: string;\n data?: Record<string, unknown>;\n /** Full LogEntry shape for assertions that inspect the complete record. */\n entry: Pick<LogEntry, 'level' | 'message' | 'context'> & { data?: Record<string, unknown> };\n}\n\nexport type LogMatcher =\n | string\n | RegExp\n | ((call: MockLogCall) => boolean)\n | Partial<Record<'message' | 'level' | string, unknown>>;\n\nexport interface MockLoggerInstance {\n /** The logger itself — pass this where an IBaseLogger is expected. */\n readonly logger: IBaseLogger;\n /** Every call recorded across all levels, in insertion order. */\n readonly calls: MockLogCall[];\n /**\n * Get all calls for a specific level (case-insensitive). If `level` is\n * omitted, returns all calls across every level.\n */\n getCalls(level?: string): MockLogCall[];\n /** Return the most recent call, optionally filtered by level. */\n getLastCall(level?: string): MockLogCall | undefined;\n /**\n * Assert that at least one recorded call matches `matcher`.\n *\n * - string → exact message match\n * - RegExp → message test\n * - function → receives the full MockLogCall\n * - object → every key/value must match (supports RegExp values)\n *\n * Throws an Error with a descriptive message if the assertion fails (works\n * with both Vitest `expect` and Jest matchers via `.toThrow()`, or you can\n * let it propagate directly).\n */\n expectLog(level: string, matcher?: LogMatcher): void;\n /**\n * Assert that NO recorded call matches `matcher` at the given level.\n */\n expectNoLog(level: string, matcher?: LogMatcher): void;\n /** Clear all recorded calls. Call between tests. */\n reset(): void;\n /**\n * Silence — when true, the mock suppresses all console output from the\n * logger. Default: true.\n */\n silent: boolean;\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction resolveCallField(call: MockLogCall, key: string): unknown {\n if (key === 'message') return call.message;\n if (key === 'level') return call.level;\n return call.data?.[key] ?? (call.entry as Record<string, unknown>)[key];\n}\n\nfunction formatMatcherDescription(matcher: LogMatcher | undefined): string {\n if (matcher === undefined) return '(any)';\n if (matcher instanceof RegExp) return matcher.toString();\n return JSON.stringify(matcher);\n}\n\nfunction matchesCall(call: MockLogCall, matcher?: LogMatcher): boolean {\n if (matcher === undefined) return true;\n if (typeof matcher === 'string') return call.message === matcher;\n if (matcher instanceof RegExp) return matcher.test(call.message);\n if (typeof matcher === 'function') return matcher(call);\n // Object shape: every key/value must match\n for (const [key, expected] of Object.entries(matcher)) {\n const actual = resolveCallField(call, key);\n if (expected instanceof RegExp) {\n if (!expected.test(String(actual ?? ''))) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction formatCalls(calls: MockLogCall[]): string {\n if (calls.length === 0) return ' (no calls recorded)';\n return calls\n .map((c) => ` [${c.level}] \"${c.message}\" ${c.data ? JSON.stringify(c.data) : ''}`)\n .join('\\n');\n}\n\n// ── Implementation ────────────────────────────────────────────────────────────\n\n/**\n * Create a mock logger for use in tests.\n *\n * The returned object implements `IBaseLogger` and additionally exposes `.calls`,\n * `.getCalls()`, `.getLastCall()`, `.expectLog()`, `.expectNoLog()`, and `.reset()`.\n *\n * @param options.silent Suppress console output (default: true)\n * @param options.context Optional context label written into every `entry.context`\n */\nexport function createMockLogger(\n options: { silent?: boolean; context?: string } = {}\n): MockLoggerInstance {\n const { silent = true, context } = options;\n const _calls: MockLogCall[] = [];\n\n function record(\n level: string,\n messageOrError: string | Error,\n data?: Record<string, unknown>\n ): void {\n const message = messageOrError instanceof Error ? messageOrError.message : messageOrError;\n const extraData =\n messageOrError instanceof Error\n ? { ...data, error: { message: messageOrError.message, stack: messageOrError.stack } }\n : data;\n\n _calls.push({\n level,\n message,\n ...(extraData !== undefined ? { data: extraData } : {}),\n entry: {\n level,\n message,\n ...(context !== undefined ? { context } : {}),\n ...(extraData !== undefined ? { data: extraData } : {}),\n },\n });\n }\n\n const logger: IBaseLogger = {\n async error(messageOrError, data) {\n record('error', messageOrError as string | Error, data);\n },\n async warn(message, data) {\n record('warn', message, data);\n },\n async info(message, data) {\n record('info', message, data);\n },\n async debug(message, data) {\n record('debug', message, data);\n },\n async trace(message, data) {\n record('trace', message, data);\n },\n async verbose(message, data) {\n record('verbose', message, data);\n },\n async logLevel(level, message, data) {\n record(level, message, data);\n },\n\n time(_label) {\n /* no-op in tests */\n },\n async timeEnd(_label) {\n return 0;\n },\n async timeAsync(_label, fn) {\n return fn();\n },\n\n setLevel(_level) {\n /* no-op */\n },\n getLevel() {\n return 'info';\n },\n setContext(_ctx) {\n /* no-op */\n },\n getContext() {\n return context;\n },\n\n enableField(_f) {\n /* no-op */\n },\n disableField(_f) {\n /* no-op */\n },\n isFieldEnabled(_f) {\n return true;\n },\n getFieldState() {\n return {};\n },\n resetFieldState() {\n /* no-op */\n },\n\n enableTransportLevelPrompting() {\n /* no-op */\n },\n disableTransportLevelPrompting() {\n /* no-op */\n },\n setTransportLevels(_id, _levels) {\n /* no-op */\n },\n getTransportLevels(_id): string[] | undefined {\n return undefined;\n },\n clearTransportLevelPreferences() {\n /* no-op */\n },\n getAvailableTransports() {\n return [];\n },\n\n child(ctx, _data) {\n return createMockLogger({ silent, context: ctx }).logger;\n },\n async close() {\n /* no-op */\n },\n };\n\n const instance: MockLoggerInstance = {\n get logger() {\n return logger;\n },\n get calls() {\n return _calls;\n },\n\n getCalls(level?: string): MockLogCall[] {\n if (!level) return [..._calls];\n const l = level.toLowerCase();\n return _calls.filter((c) => c.level.toLowerCase() === l);\n },\n\n getLastCall(level?: string): MockLogCall | undefined {\n const filtered = level\n ? _calls.filter((c) => c.level.toLowerCase() === level.toLowerCase())\n : _calls;\n return filtered[filtered.length - 1];\n },\n\n expectLog(level: string, matcher?: LogMatcher): void {\n const levelCalls = instance.getCalls(level);\n const matched = levelCalls.some((c) => matchesCall(c, matcher));\n if (!matched) {\n const matcherDesc = formatMatcherDescription(matcher);\n throw new Error(\n `Expected at least one [${level}] log matching ${matcherDesc}.\\n` +\n `Recorded calls:\\n${formatCalls(_calls)}`\n );\n }\n },\n\n expectNoLog(level: string, matcher?: LogMatcher): void {\n const levelCalls = instance.getCalls(level);\n const matched = levelCalls.some((c) => matchesCall(c, matcher));\n if (matched) {\n const matcherDesc = formatMatcherDescription(matcher);\n throw new Error(\n `Expected NO [${level}] log matching ${matcherDesc}, but one was found.\\n` +\n `Recorded calls:\\n${formatCalls(_calls)}`\n );\n }\n },\n\n reset(): void {\n _calls.length = 0;\n },\n\n silent,\n };\n\n return instance;\n}\n"],"mappings":"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"}
|
package/dist/testing.mjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
//#region src/testing/mock-logger.ts
|
|
2
|
+
function resolveCallField(call, key) {
|
|
3
|
+
var _call$data;
|
|
4
|
+
if (key === "message") return call.message;
|
|
5
|
+
if (key === "level") return call.level;
|
|
6
|
+
return ((_call$data = call.data) === null || _call$data === void 0 ? void 0 : _call$data[key]) ?? call.entry[key];
|
|
7
|
+
}
|
|
8
|
+
function formatMatcherDescription(matcher) {
|
|
9
|
+
if (matcher === void 0) return "(any)";
|
|
10
|
+
if (matcher instanceof RegExp) return matcher.toString();
|
|
11
|
+
return JSON.stringify(matcher);
|
|
12
|
+
}
|
|
13
|
+
function matchesCall(call, matcher) {
|
|
14
|
+
if (matcher === void 0) return true;
|
|
15
|
+
if (typeof matcher === "string") return call.message === matcher;
|
|
16
|
+
if (matcher instanceof RegExp) return matcher.test(call.message);
|
|
17
|
+
if (typeof matcher === "function") return matcher(call);
|
|
18
|
+
for (const [key, expected] of Object.entries(matcher)) {
|
|
19
|
+
const actual = resolveCallField(call, key);
|
|
20
|
+
if (expected instanceof RegExp) {
|
|
21
|
+
if (!expected.test(String(actual ?? ""))) return false;
|
|
22
|
+
} else if (actual !== expected) return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function formatCalls(calls) {
|
|
27
|
+
if (calls.length === 0) return " (no calls recorded)";
|
|
28
|
+
return calls.map((c) => ` [${c.level}] "${c.message}" ${c.data ? JSON.stringify(c.data) : ""}`).join("\n");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a mock logger for use in tests.
|
|
32
|
+
*
|
|
33
|
+
* The returned object implements `IBaseLogger` and additionally exposes `.calls`,
|
|
34
|
+
* `.getCalls()`, `.getLastCall()`, `.expectLog()`, `.expectNoLog()`, and `.reset()`.
|
|
35
|
+
*
|
|
36
|
+
* @param options.silent Suppress console output (default: true)
|
|
37
|
+
* @param options.context Optional context label written into every `entry.context`
|
|
38
|
+
*/
|
|
39
|
+
function createMockLogger(options = {}) {
|
|
40
|
+
const { silent = true, context } = options;
|
|
41
|
+
const _calls = [];
|
|
42
|
+
function record(level, messageOrError, data) {
|
|
43
|
+
const message = messageOrError instanceof Error ? messageOrError.message : messageOrError;
|
|
44
|
+
const extraData = messageOrError instanceof Error ? {
|
|
45
|
+
...data,
|
|
46
|
+
error: {
|
|
47
|
+
message: messageOrError.message,
|
|
48
|
+
stack: messageOrError.stack
|
|
49
|
+
}
|
|
50
|
+
} : data;
|
|
51
|
+
_calls.push({
|
|
52
|
+
level,
|
|
53
|
+
message,
|
|
54
|
+
...extraData !== void 0 ? { data: extraData } : {},
|
|
55
|
+
entry: {
|
|
56
|
+
level,
|
|
57
|
+
message,
|
|
58
|
+
...context !== void 0 ? { context } : {},
|
|
59
|
+
...extraData !== void 0 ? { data: extraData } : {}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const logger = {
|
|
64
|
+
async error(messageOrError, data) {
|
|
65
|
+
record("error", messageOrError, data);
|
|
66
|
+
},
|
|
67
|
+
async warn(message, data) {
|
|
68
|
+
record("warn", message, data);
|
|
69
|
+
},
|
|
70
|
+
async info(message, data) {
|
|
71
|
+
record("info", message, data);
|
|
72
|
+
},
|
|
73
|
+
async debug(message, data) {
|
|
74
|
+
record("debug", message, data);
|
|
75
|
+
},
|
|
76
|
+
async trace(message, data) {
|
|
77
|
+
record("trace", message, data);
|
|
78
|
+
},
|
|
79
|
+
async verbose(message, data) {
|
|
80
|
+
record("verbose", message, data);
|
|
81
|
+
},
|
|
82
|
+
async logLevel(level, message, data) {
|
|
83
|
+
record(level, message, data);
|
|
84
|
+
},
|
|
85
|
+
time(_label) {},
|
|
86
|
+
async timeEnd(_label) {
|
|
87
|
+
return 0;
|
|
88
|
+
},
|
|
89
|
+
async timeAsync(_label, fn) {
|
|
90
|
+
return fn();
|
|
91
|
+
},
|
|
92
|
+
setLevel(_level) {},
|
|
93
|
+
getLevel() {
|
|
94
|
+
return "info";
|
|
95
|
+
},
|
|
96
|
+
setContext(_ctx) {},
|
|
97
|
+
getContext() {
|
|
98
|
+
return context;
|
|
99
|
+
},
|
|
100
|
+
enableField(_f) {},
|
|
101
|
+
disableField(_f) {},
|
|
102
|
+
isFieldEnabled(_f) {
|
|
103
|
+
return true;
|
|
104
|
+
},
|
|
105
|
+
getFieldState() {
|
|
106
|
+
return {};
|
|
107
|
+
},
|
|
108
|
+
resetFieldState() {},
|
|
109
|
+
enableTransportLevelPrompting() {},
|
|
110
|
+
disableTransportLevelPrompting() {},
|
|
111
|
+
setTransportLevels(_id, _levels) {},
|
|
112
|
+
getTransportLevels(_id) {},
|
|
113
|
+
clearTransportLevelPreferences() {},
|
|
114
|
+
getAvailableTransports() {
|
|
115
|
+
return [];
|
|
116
|
+
},
|
|
117
|
+
child(ctx, _data) {
|
|
118
|
+
return createMockLogger({
|
|
119
|
+
silent,
|
|
120
|
+
context: ctx
|
|
121
|
+
}).logger;
|
|
122
|
+
},
|
|
123
|
+
async close() {}
|
|
124
|
+
};
|
|
125
|
+
const instance = {
|
|
126
|
+
get logger() {
|
|
127
|
+
return logger;
|
|
128
|
+
},
|
|
129
|
+
get calls() {
|
|
130
|
+
return _calls;
|
|
131
|
+
},
|
|
132
|
+
getCalls(level) {
|
|
133
|
+
if (!level) return [..._calls];
|
|
134
|
+
const l = level.toLowerCase();
|
|
135
|
+
return _calls.filter((c) => c.level.toLowerCase() === l);
|
|
136
|
+
},
|
|
137
|
+
getLastCall(level) {
|
|
138
|
+
const filtered = level ? _calls.filter((c) => c.level.toLowerCase() === level.toLowerCase()) : _calls;
|
|
139
|
+
return filtered[filtered.length - 1];
|
|
140
|
+
},
|
|
141
|
+
expectLog(level, matcher) {
|
|
142
|
+
if (!instance.getCalls(level).some((c) => matchesCall(c, matcher))) {
|
|
143
|
+
const matcherDesc = formatMatcherDescription(matcher);
|
|
144
|
+
throw new Error(`Expected at least one [${level}] log matching ${matcherDesc}.\nRecorded calls:\n${formatCalls(_calls)}`);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
expectNoLog(level, matcher) {
|
|
148
|
+
if (instance.getCalls(level).some((c) => matchesCall(c, matcher))) {
|
|
149
|
+
const matcherDesc = formatMatcherDescription(matcher);
|
|
150
|
+
throw new Error(`Expected NO [${level}] log matching ${matcherDesc}, but one was found.\nRecorded calls:\n${formatCalls(_calls)}`);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
reset() {
|
|
154
|
+
_calls.length = 0;
|
|
155
|
+
},
|
|
156
|
+
silent
|
|
157
|
+
};
|
|
158
|
+
return instance;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
export { createMockLogger };
|
|
163
|
+
//# sourceMappingURL=testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":["_calls: MockLogCall[]","logger: IBaseLogger","instance: MockLoggerInstance"],"sources":["../src/testing/mock-logger.ts"],"sourcesContent":["/**\n * logixia/testing — Zero-dependency mock logger for Vitest & Jest.\n *\n * @example\n * ```ts\n * import { createMockLogger } from 'logixia/testing';\n *\n * const mock = createMockLogger();\n * await myService.doSomething(mock.logger);\n *\n * mock.expectLog('info', { message: 'order created' });\n * mock.expectLog('error', { message: /failed/ });\n * mock.reset();\n * ```\n */\n\nimport type { IBaseLogger, LogEntry } from '../types';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface MockLogCall {\n level: string;\n message: string;\n data?: Record<string, unknown>;\n /** Full LogEntry shape for assertions that inspect the complete record. */\n entry: Pick<LogEntry, 'level' | 'message' | 'context'> & { data?: Record<string, unknown> };\n}\n\nexport type LogMatcher =\n | string\n | RegExp\n | ((call: MockLogCall) => boolean)\n | Partial<Record<'message' | 'level' | string, unknown>>;\n\nexport interface MockLoggerInstance {\n /** The logger itself — pass this where an IBaseLogger is expected. */\n readonly logger: IBaseLogger;\n /** Every call recorded across all levels, in insertion order. */\n readonly calls: MockLogCall[];\n /**\n * Get all calls for a specific level (case-insensitive). If `level` is\n * omitted, returns all calls across every level.\n */\n getCalls(level?: string): MockLogCall[];\n /** Return the most recent call, optionally filtered by level. */\n getLastCall(level?: string): MockLogCall | undefined;\n /**\n * Assert that at least one recorded call matches `matcher`.\n *\n * - string → exact message match\n * - RegExp → message test\n * - function → receives the full MockLogCall\n * - object → every key/value must match (supports RegExp values)\n *\n * Throws an Error with a descriptive message if the assertion fails (works\n * with both Vitest `expect` and Jest matchers via `.toThrow()`, or you can\n * let it propagate directly).\n */\n expectLog(level: string, matcher?: LogMatcher): void;\n /**\n * Assert that NO recorded call matches `matcher` at the given level.\n */\n expectNoLog(level: string, matcher?: LogMatcher): void;\n /** Clear all recorded calls. Call between tests. */\n reset(): void;\n /**\n * Silence — when true, the mock suppresses all console output from the\n * logger. Default: true.\n */\n silent: boolean;\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction resolveCallField(call: MockLogCall, key: string): unknown {\n if (key === 'message') return call.message;\n if (key === 'level') return call.level;\n return call.data?.[key] ?? (call.entry as Record<string, unknown>)[key];\n}\n\nfunction formatMatcherDescription(matcher: LogMatcher | undefined): string {\n if (matcher === undefined) return '(any)';\n if (matcher instanceof RegExp) return matcher.toString();\n return JSON.stringify(matcher);\n}\n\nfunction matchesCall(call: MockLogCall, matcher?: LogMatcher): boolean {\n if (matcher === undefined) return true;\n if (typeof matcher === 'string') return call.message === matcher;\n if (matcher instanceof RegExp) return matcher.test(call.message);\n if (typeof matcher === 'function') return matcher(call);\n // Object shape: every key/value must match\n for (const [key, expected] of Object.entries(matcher)) {\n const actual = resolveCallField(call, key);\n if (expected instanceof RegExp) {\n if (!expected.test(String(actual ?? ''))) return false;\n } else {\n if (actual !== expected) return false;\n }\n }\n return true;\n}\n\nfunction formatCalls(calls: MockLogCall[]): string {\n if (calls.length === 0) return ' (no calls recorded)';\n return calls\n .map((c) => ` [${c.level}] \"${c.message}\" ${c.data ? JSON.stringify(c.data) : ''}`)\n .join('\\n');\n}\n\n// ── Implementation ────────────────────────────────────────────────────────────\n\n/**\n * Create a mock logger for use in tests.\n *\n * The returned object implements `IBaseLogger` and additionally exposes `.calls`,\n * `.getCalls()`, `.getLastCall()`, `.expectLog()`, `.expectNoLog()`, and `.reset()`.\n *\n * @param options.silent Suppress console output (default: true)\n * @param options.context Optional context label written into every `entry.context`\n */\nexport function createMockLogger(\n options: { silent?: boolean; context?: string } = {}\n): MockLoggerInstance {\n const { silent = true, context } = options;\n const _calls: MockLogCall[] = [];\n\n function record(\n level: string,\n messageOrError: string | Error,\n data?: Record<string, unknown>\n ): void {\n const message = messageOrError instanceof Error ? messageOrError.message : messageOrError;\n const extraData =\n messageOrError instanceof Error\n ? { ...data, error: { message: messageOrError.message, stack: messageOrError.stack } }\n : data;\n\n _calls.push({\n level,\n message,\n ...(extraData !== undefined ? { data: extraData } : {}),\n entry: {\n level,\n message,\n ...(context !== undefined ? { context } : {}),\n ...(extraData !== undefined ? { data: extraData } : {}),\n },\n });\n }\n\n const logger: IBaseLogger = {\n async error(messageOrError, data) {\n record('error', messageOrError as string | Error, data);\n },\n async warn(message, data) {\n record('warn', message, data);\n },\n async info(message, data) {\n record('info', message, data);\n },\n async debug(message, data) {\n record('debug', message, data);\n },\n async trace(message, data) {\n record('trace', message, data);\n },\n async verbose(message, data) {\n record('verbose', message, data);\n },\n async logLevel(level, message, data) {\n record(level, message, data);\n },\n\n time(_label) {\n /* no-op in tests */\n },\n async timeEnd(_label) {\n return 0;\n },\n async timeAsync(_label, fn) {\n return fn();\n },\n\n setLevel(_level) {\n /* no-op */\n },\n getLevel() {\n return 'info';\n },\n setContext(_ctx) {\n /* no-op */\n },\n getContext() {\n return context;\n },\n\n enableField(_f) {\n /* no-op */\n },\n disableField(_f) {\n /* no-op */\n },\n isFieldEnabled(_f) {\n return true;\n },\n getFieldState() {\n return {};\n },\n resetFieldState() {\n /* no-op */\n },\n\n enableTransportLevelPrompting() {\n /* no-op */\n },\n disableTransportLevelPrompting() {\n /* no-op */\n },\n setTransportLevels(_id, _levels) {\n /* no-op */\n },\n getTransportLevels(_id): string[] | undefined {\n return undefined;\n },\n clearTransportLevelPreferences() {\n /* no-op */\n },\n getAvailableTransports() {\n return [];\n },\n\n child(ctx, _data) {\n return createMockLogger({ silent, context: ctx }).logger;\n },\n async close() {\n /* no-op */\n },\n };\n\n const instance: MockLoggerInstance = {\n get logger() {\n return logger;\n },\n get calls() {\n return _calls;\n },\n\n getCalls(level?: string): MockLogCall[] {\n if (!level) return [..._calls];\n const l = level.toLowerCase();\n return _calls.filter((c) => c.level.toLowerCase() === l);\n },\n\n getLastCall(level?: string): MockLogCall | undefined {\n const filtered = level\n ? _calls.filter((c) => c.level.toLowerCase() === level.toLowerCase())\n : _calls;\n return filtered[filtered.length - 1];\n },\n\n expectLog(level: string, matcher?: LogMatcher): void {\n const levelCalls = instance.getCalls(level);\n const matched = levelCalls.some((c) => matchesCall(c, matcher));\n if (!matched) {\n const matcherDesc = formatMatcherDescription(matcher);\n throw new Error(\n `Expected at least one [${level}] log matching ${matcherDesc}.\\n` +\n `Recorded calls:\\n${formatCalls(_calls)}`\n );\n }\n },\n\n expectNoLog(level: string, matcher?: LogMatcher): void {\n const levelCalls = instance.getCalls(level);\n const matched = levelCalls.some((c) => matchesCall(c, matcher));\n if (matched) {\n const matcherDesc = formatMatcherDescription(matcher);\n throw new Error(\n `Expected NO [${level}] log matching ${matcherDesc}, but one was found.\\n` +\n `Recorded calls:\\n${formatCalls(_calls)}`\n );\n }\n },\n\n reset(): void {\n _calls.length = 0;\n },\n\n silent,\n };\n\n return instance;\n}\n"],"mappings":";AA0EA,SAAS,iBAAiB,MAAmB,KAAsB;;AACjE,KAAI,QAAQ,UAAW,QAAO,KAAK;AACnC,KAAI,QAAQ,QAAS,QAAO,KAAK;AACjC,uBAAO,KAAK,8DAAO,SAAS,KAAK,MAAkC;;AAGrE,SAAS,yBAAyB,SAAyC;AACzE,KAAI,YAAY,OAAW,QAAO;AAClC,KAAI,mBAAmB,OAAQ,QAAO,QAAQ,UAAU;AACxD,QAAO,KAAK,UAAU,QAAQ;;AAGhC,SAAS,YAAY,MAAmB,SAA+B;AACrE,KAAI,YAAY,OAAW,QAAO;AAClC,KAAI,OAAO,YAAY,SAAU,QAAO,KAAK,YAAY;AACzD,KAAI,mBAAmB,OAAQ,QAAO,QAAQ,KAAK,KAAK,QAAQ;AAChE,KAAI,OAAO,YAAY,WAAY,QAAO,QAAQ,KAAK;AAEvD,MAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,QAAQ,EAAE;EACrD,MAAM,SAAS,iBAAiB,MAAM,IAAI;AAC1C,MAAI,oBAAoB,QACtB;OAAI,CAAC,SAAS,KAAK,OAAO,UAAU,GAAG,CAAC,CAAE,QAAO;aAE7C,WAAW,SAAU,QAAO;;AAGpC,QAAO;;AAGT,SAAS,YAAY,OAA8B;AACjD,KAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAO,MACJ,KAAK,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,OAAO,KAAK,UAAU,EAAE,KAAK,GAAG,KAAK,CACnF,KAAK,KAAK;;;;;;;;;;;AAcf,SAAgB,iBACd,UAAkD,EAAE,EAChC;CACpB,MAAM,EAAE,SAAS,MAAM,YAAY;CACnC,MAAMA,SAAwB,EAAE;CAEhC,SAAS,OACP,OACA,gBACA,MACM;EACN,MAAM,UAAU,0BAA0B,QAAQ,eAAe,UAAU;EAC3E,MAAM,YACJ,0BAA0B,QACtB;GAAE,GAAG;GAAM,OAAO;IAAE,SAAS,eAAe;IAAS,OAAO,eAAe;IAAO;GAAE,GACpF;AAEN,SAAO,KAAK;GACV;GACA;GACA,GAAI,cAAc,SAAY,EAAE,MAAM,WAAW,GAAG,EAAE;GACtD,OAAO;IACL;IACA;IACA,GAAI,YAAY,SAAY,EAAE,SAAS,GAAG,EAAE;IAC5C,GAAI,cAAc,SAAY,EAAE,MAAM,WAAW,GAAG,EAAE;IACvD;GACF,CAAC;;CAGJ,MAAMC,SAAsB;EAC1B,MAAM,MAAM,gBAAgB,MAAM;AAChC,UAAO,SAAS,gBAAkC,KAAK;;EAEzD,MAAM,KAAK,SAAS,MAAM;AACxB,UAAO,QAAQ,SAAS,KAAK;;EAE/B,MAAM,KAAK,SAAS,MAAM;AACxB,UAAO,QAAQ,SAAS,KAAK;;EAE/B,MAAM,MAAM,SAAS,MAAM;AACzB,UAAO,SAAS,SAAS,KAAK;;EAEhC,MAAM,MAAM,SAAS,MAAM;AACzB,UAAO,SAAS,SAAS,KAAK;;EAEhC,MAAM,QAAQ,SAAS,MAAM;AAC3B,UAAO,WAAW,SAAS,KAAK;;EAElC,MAAM,SAAS,OAAO,SAAS,MAAM;AACnC,UAAO,OAAO,SAAS,KAAK;;EAG9B,KAAK,QAAQ;EAGb,MAAM,QAAQ,QAAQ;AACpB,UAAO;;EAET,MAAM,UAAU,QAAQ,IAAI;AAC1B,UAAO,IAAI;;EAGb,SAAS,QAAQ;EAGjB,WAAW;AACT,UAAO;;EAET,WAAW,MAAM;EAGjB,aAAa;AACX,UAAO;;EAGT,YAAY,IAAI;EAGhB,aAAa,IAAI;EAGjB,eAAe,IAAI;AACjB,UAAO;;EAET,gBAAgB;AACd,UAAO,EAAE;;EAEX,kBAAkB;EAIlB,gCAAgC;EAGhC,iCAAiC;EAGjC,mBAAmB,KAAK,SAAS;EAGjC,mBAAmB,KAA2B;EAG9C,iCAAiC;EAGjC,yBAAyB;AACvB,UAAO,EAAE;;EAGX,MAAM,KAAK,OAAO;AAChB,UAAO,iBAAiB;IAAE;IAAQ,SAAS;IAAK,CAAC,CAAC;;EAEpD,MAAM,QAAQ;EAGf;CAED,MAAMC,WAA+B;EACnC,IAAI,SAAS;AACX,UAAO;;EAET,IAAI,QAAQ;AACV,UAAO;;EAGT,SAAS,OAA+B;AACtC,OAAI,CAAC,MAAO,QAAO,CAAC,GAAG,OAAO;GAC9B,MAAM,IAAI,MAAM,aAAa;AAC7B,UAAO,OAAO,QAAQ,MAAM,EAAE,MAAM,aAAa,KAAK,EAAE;;EAG1D,YAAY,OAAyC;GACnD,MAAM,WAAW,QACb,OAAO,QAAQ,MAAM,EAAE,MAAM,aAAa,KAAK,MAAM,aAAa,CAAC,GACnE;AACJ,UAAO,SAAS,SAAS,SAAS;;EAGpC,UAAU,OAAe,SAA4B;AAGnD,OAAI,CAFe,SAAS,SAAS,MAAM,CAChB,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,EACjD;IACZ,MAAM,cAAc,yBAAyB,QAAQ;AACrD,UAAM,IAAI,MACR,0BAA0B,MAAM,iBAAiB,YAAY,sBACvC,YAAY,OAAO,GAC1C;;;EAIL,YAAY,OAAe,SAA4B;AAGrD,OAFmB,SAAS,SAAS,MAAM,CAChB,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,EAClD;IACX,MAAM,cAAc,yBAAyB,QAAQ;AACrD,UAAM,IAAI,MACR,gBAAgB,MAAM,iBAAiB,YAAY,yCAC7B,YAAY,OAAO,GAC1C;;;EAIL,QAAc;AACZ,UAAO,SAAS;;EAGlB;EACD;AAED,QAAO"}
|
|
@@ -1546,6 +1546,9 @@ var SegmentTransport = class extends AnalyticsTransport {
|
|
|
1546
1546
|
|
|
1547
1547
|
//#endregion
|
|
1548
1548
|
//#region src/transports/transport.manager.ts
|
|
1549
|
+
function _sleep(ms) {
|
|
1550
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1551
|
+
}
|
|
1549
1552
|
var TransportManager = class extends EventEmitter {
|
|
1550
1553
|
constructor(config = {}) {
|
|
1551
1554
|
super();
|
|
@@ -1660,7 +1663,7 @@ var TransportManager = class extends EventEmitter {
|
|
|
1660
1663
|
const startTime = Date.now();
|
|
1661
1664
|
const metrics = this.metrics.get(id);
|
|
1662
1665
|
try {
|
|
1663
|
-
await
|
|
1666
|
+
await this._writeWithRetry(transport, entry);
|
|
1664
1667
|
const writeTime = Date.now() - startTime;
|
|
1665
1668
|
metrics.logsWritten++;
|
|
1666
1669
|
metrics.lastWrite = new Date(startTime);
|
|
@@ -1672,6 +1675,52 @@ var TransportManager = class extends EventEmitter {
|
|
|
1672
1675
|
throw error;
|
|
1673
1676
|
}
|
|
1674
1677
|
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Feature 9: Multi-transport retry + failover.
|
|
1680
|
+
*
|
|
1681
|
+
* Attempts `transport.write()` up to `retry.maxRetries + 1` times with the
|
|
1682
|
+
* configured backoff strategy. If all attempts fail and a fallback transport
|
|
1683
|
+
* is configured, routes the entry there instead.
|
|
1684
|
+
*/
|
|
1685
|
+
async _writeWithRetry(transport, entry) {
|
|
1686
|
+
const retryCfg = transport.retry;
|
|
1687
|
+
if (!retryCfg || !retryCfg.maxRetries) {
|
|
1688
|
+
await transport.write(entry);
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
const { maxRetries, backoff = "exponential", delay: baseDelay = 500, maxDelay = 3e4, fallback, onExhausted } = retryCfg;
|
|
1692
|
+
let lastError = /* @__PURE__ */ new Error("Unknown transport error");
|
|
1693
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
1694
|
+
await transport.write(entry);
|
|
1695
|
+
return;
|
|
1696
|
+
} catch (err) {
|
|
1697
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1698
|
+
if (attempt < maxRetries) {
|
|
1699
|
+
let wait;
|
|
1700
|
+
switch (backoff) {
|
|
1701
|
+
case "fixed":
|
|
1702
|
+
wait = baseDelay;
|
|
1703
|
+
break;
|
|
1704
|
+
case "linear":
|
|
1705
|
+
wait = baseDelay * (attempt + 1);
|
|
1706
|
+
break;
|
|
1707
|
+
case "exponential":
|
|
1708
|
+
default: wait = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
|
|
1709
|
+
}
|
|
1710
|
+
this.emit("transport:retry", transport.name, attempt + 1, maxRetries, wait);
|
|
1711
|
+
await _sleep(wait);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
if (fallback) {
|
|
1715
|
+
this.emit("transport:fallback", transport.name, fallback.name);
|
|
1716
|
+
try {
|
|
1717
|
+
await fallback.write(entry);
|
|
1718
|
+
return;
|
|
1719
|
+
} catch {}
|
|
1720
|
+
}
|
|
1721
|
+
onExhausted === null || onExhausted === void 0 || onExhausted(lastError, entry);
|
|
1722
|
+
throw lastError;
|
|
1723
|
+
}
|
|
1675
1724
|
shouldTransportHandle(transport, level, transportId) {
|
|
1676
1725
|
if (transportId && this.transportLevelPreferences.has(transportId)) return this.transportLevelPreferences.get(transportId).includes(level.toLowerCase());
|
|
1677
1726
|
if (!transport.level) return true;
|
|
@@ -1846,4 +1895,4 @@ var TransportManager = class extends EventEmitter {
|
|
|
1846
1895
|
|
|
1847
1896
|
//#endregion
|
|
1848
1897
|
export { FileTransport as a, DatabaseTransport as c, internalLog as d, internalWarn as f, GoogleAnalyticsTransport as i, ConsoleTransport as l, SegmentTransport as n, DataDogTransport as o, MixpanelTransport as r, AnalyticsTransport as s, TransportManager as t, internalError as u };
|
|
1849
|
-
//# sourceMappingURL=transport.manager-
|
|
1898
|
+
//# sourceMappingURL=transport.manager-5VVdqS3o.mjs.map
|