@expressots/core 4.0.0-preview.1 → 4.0.0-preview.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/LICENSE.md +21 -21
- package/README.md +66 -66
- package/lib/CHANGELOG.md +774 -774
- package/lib/README.md +66 -66
- package/lib/cjs/application/application-factory.js +6 -0
- package/lib/cjs/application/bootstrap.js +117 -213
- package/lib/cjs/config/define-config.js +1 -1
- package/lib/cjs/config/env-field-builders.js +47 -0
- package/lib/cjs/config/index.js +7 -1
- package/lib/cjs/framework-version.js +10 -0
- package/lib/cjs/lazy-loading/index.js +5 -1
- package/lib/cjs/lazy-loading/lazy-module-helpers.js +49 -0
- package/lib/cjs/middleware/index.js +8 -9
- package/lib/cjs/middleware/middleware-service.js +68 -12
- package/lib/cjs/middleware/presets-standalone.js +93 -0
- package/lib/cjs/provider/db-in-memory/adapter/in-memory.adapter.js +23 -0
- package/lib/cjs/provider/db-in-memory/index.js +11 -1
- package/lib/cjs/provider/db-in-memory/query/query-engine.js +28 -0
- package/lib/cjs/provider/db-in-memory/schema/decorators.js +18 -0
- package/lib/cjs/provider/db-in-memory/storage/index.js +3 -1
- package/lib/cjs/provider/db-in-memory/storage/memory-store.js +72 -1
- package/lib/cjs/provider/logger/logger.banner.js +40 -31
- package/lib/cjs/provider/logger/logger.config.js +11 -1
- package/lib/cjs/provider/logger/logger.formatter.js +22 -1
- package/lib/cjs/provider/logger/logger.provider.js +59 -9
- package/lib/cjs/provider/logger/transports/console.transport.js +69 -6
- package/lib/cjs/provider/logger/transports/file.transport.js +27 -18
- package/lib/cjs/provider/logger/utils/log-levels.js +6 -5
- package/lib/cjs/provider/validation/adapters/index.js +12 -5
- package/lib/cjs/provider/validation/adapters/yup.adapter.js +118 -0
- package/lib/cjs/provider/validation/adapters/zod.adapter.js +137 -0
- package/lib/cjs/provider/validation/index.js +5 -1
- package/lib/cjs/render/adapters/react-adapter.js +14 -14
- package/lib/cjs/render/features/type-generator.js +30 -30
- package/lib/cjs/render/features/view-debugger.js +75 -55
- package/lib/cjs/testing/fluent-request.js +7 -0
- package/lib/cjs/testing/snapshot-request.js +2 -0
- package/lib/cjs/types/application/application-factory.d.ts +6 -0
- package/lib/cjs/types/application/bootstrap.d.ts +196 -24
- package/lib/cjs/types/config/config.interfaces.d.ts +7 -1
- package/lib/cjs/types/config/env-field-builders.d.ts +39 -0
- package/lib/cjs/types/config/index.d.ts +1 -1
- package/lib/cjs/types/framework-version.d.ts +7 -0
- package/lib/cjs/types/lazy-loading/index.d.ts +1 -0
- package/lib/cjs/types/lazy-loading/lazy-module-helpers.d.ts +42 -0
- package/lib/cjs/types/middleware/index.d.ts +1 -1
- package/lib/cjs/types/middleware/middleware-service.d.ts +21 -0
- package/lib/cjs/types/middleware/presets-standalone.d.ts +75 -0
- package/lib/cjs/types/provider/db-in-memory/adapter/in-memory.adapter.d.ts +2 -0
- package/lib/cjs/types/provider/db-in-memory/index.d.ts +9 -1
- package/lib/cjs/types/provider/db-in-memory/query/query-engine.d.ts +14 -1
- package/lib/cjs/types/provider/db-in-memory/schema/decorators.d.ts +14 -0
- package/lib/cjs/types/provider/db-in-memory/storage/index.d.ts +1 -1
- package/lib/cjs/types/provider/db-in-memory/storage/memory-store.d.ts +34 -0
- package/lib/cjs/types/provider/logger/logger.banner.d.ts +1 -1
- package/lib/cjs/types/provider/logger/logger.config.d.ts +7 -0
- package/lib/cjs/types/provider/logger/logger.formatter.d.ts +10 -0
- package/lib/cjs/types/provider/logger/logger.provider.d.ts +32 -1
- package/lib/cjs/types/provider/logger/transports/console.transport.d.ts +7 -0
- package/lib/cjs/types/provider/logger/utils/log-levels.d.ts +3 -3
- package/lib/cjs/types/provider/validation/adapters/index.d.ts +7 -4
- package/lib/cjs/types/provider/validation/adapters/yup.adapter.d.ts +65 -0
- package/lib/cjs/types/provider/validation/adapters/zod.adapter.d.ts +84 -0
- package/lib/cjs/types/provider/validation/index.d.ts +1 -1
- package/lib/cjs/types/render/features/view-debugger.d.ts +10 -0
- package/lib/cjs/types/testing/testing.interfaces.d.ts +31 -6
- package/lib/esm/application/application-factory.js +6 -0
- package/lib/esm/application/bootstrap.js +117 -213
- package/lib/esm/config/define-config.js +1 -1
- package/lib/esm/config/env-field-builders.js +48 -0
- package/lib/esm/config/index.js +6 -1
- package/lib/esm/framework-version.js +7 -0
- package/lib/esm/lazy-loading/index.js +2 -0
- package/lib/esm/lazy-loading/lazy-module-helpers.js +45 -0
- package/lib/esm/middleware/index.js +3 -2
- package/lib/esm/middleware/middleware-service.js +68 -12
- package/lib/esm/middleware/presets-standalone.js +87 -0
- package/lib/esm/provider/db-in-memory/adapter/in-memory.adapter.js +23 -0
- package/lib/esm/provider/db-in-memory/index.js +9 -1
- package/lib/esm/provider/db-in-memory/query/query-engine.js +28 -0
- package/lib/esm/provider/db-in-memory/schema/decorators.js +18 -0
- package/lib/esm/provider/db-in-memory/storage/index.js +1 -1
- package/lib/esm/provider/db-in-memory/storage/memory-store.js +75 -0
- package/lib/esm/provider/logger/logger.banner.js +40 -31
- package/lib/esm/provider/logger/logger.config.js +12 -2
- package/lib/esm/provider/logger/logger.formatter.js +22 -1
- package/lib/esm/provider/logger/logger.provider.js +61 -10
- package/lib/esm/provider/logger/transports/console.transport.js +69 -6
- package/lib/esm/provider/logger/transports/file.transport.js +27 -18
- package/lib/esm/provider/logger/utils/log-levels.js +6 -5
- package/lib/esm/provider/validation/adapters/index.js +7 -4
- package/lib/esm/provider/validation/adapters/yup.adapter.js +111 -0
- package/lib/esm/provider/validation/adapters/zod.adapter.js +130 -0
- package/lib/esm/provider/validation/index.js +1 -1
- package/lib/esm/render/adapters/react-adapter.js +14 -14
- package/lib/esm/render/features/type-generator.js +30 -30
- package/lib/esm/render/features/view-debugger.js +75 -55
- package/lib/esm/testing/fluent-request.js +7 -0
- package/lib/esm/testing/snapshot-request.js +2 -0
- package/lib/esm/types/application/application-factory.d.ts +6 -0
- package/lib/esm/types/application/bootstrap.d.ts +196 -24
- package/lib/esm/types/config/config.interfaces.d.ts +7 -1
- package/lib/esm/types/config/env-field-builders.d.ts +39 -0
- package/lib/esm/types/config/index.d.ts +1 -1
- package/lib/esm/types/framework-version.d.ts +7 -0
- package/lib/esm/types/lazy-loading/index.d.ts +1 -0
- package/lib/esm/types/lazy-loading/lazy-module-helpers.d.ts +42 -0
- package/lib/esm/types/middleware/index.d.ts +1 -1
- package/lib/esm/types/middleware/middleware-service.d.ts +21 -0
- package/lib/esm/types/middleware/presets-standalone.d.ts +75 -0
- package/lib/esm/types/provider/db-in-memory/adapter/in-memory.adapter.d.ts +2 -0
- package/lib/esm/types/provider/db-in-memory/index.d.ts +9 -1
- package/lib/esm/types/provider/db-in-memory/query/query-engine.d.ts +14 -1
- package/lib/esm/types/provider/db-in-memory/schema/decorators.d.ts +14 -0
- package/lib/esm/types/provider/db-in-memory/storage/index.d.ts +1 -1
- package/lib/esm/types/provider/db-in-memory/storage/memory-store.d.ts +34 -0
- package/lib/esm/types/provider/logger/logger.banner.d.ts +1 -1
- package/lib/esm/types/provider/logger/logger.config.d.ts +7 -0
- package/lib/esm/types/provider/logger/logger.formatter.d.ts +10 -0
- package/lib/esm/types/provider/logger/logger.provider.d.ts +32 -1
- package/lib/esm/types/provider/logger/transports/console.transport.d.ts +7 -0
- package/lib/esm/types/provider/logger/utils/log-levels.d.ts +3 -3
- package/lib/esm/types/provider/validation/adapters/index.d.ts +7 -4
- package/lib/esm/types/provider/validation/adapters/yup.adapter.d.ts +65 -0
- package/lib/esm/types/provider/validation/adapters/zod.adapter.d.ts +84 -0
- package/lib/esm/types/provider/validation/index.d.ts +1 -1
- package/lib/esm/types/render/features/view-debugger.d.ts +10 -0
- package/lib/esm/types/testing/testing.interfaces.d.ts +31 -6
- package/lib/package.json +23 -8
- package/package.json +23 -8
- package/lib/cjs/middleware/middleware-presets.js +0 -294
- package/lib/cjs/types/middleware/middleware-presets.d.ts +0 -90
- package/lib/esm/middleware/middleware-presets.js +0 -286
- package/lib/esm/types/middleware/middleware-presets.d.ts +0 -90
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { LogLevel, logLevelToString } from "./utils/log-levels.js";
|
|
2
2
|
import { colorCodes } from "../../console/color-codes.js";
|
|
3
3
|
import { getGlobalRedactor } from "./logger.redaction.js";
|
|
4
|
+
/**
|
|
5
|
+
* Strip ANSI escape sequences (SGR colors, cursor moves, etc.) from a
|
|
6
|
+
* string. Cheap fast-path bails out when no escape character is present.
|
|
7
|
+
*/
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
const ANSI_ESCAPE_REGEX = /\u001b\[[\d;?]*[ -/]*[@-~]/g;
|
|
10
|
+
function stripAnsiEscapes(input) {
|
|
11
|
+
if (!input || input.indexOf("\u001b") === -1)
|
|
12
|
+
return input;
|
|
13
|
+
return input.replace(ANSI_ESCAPE_REGEX, "");
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Format log entry for development (human-readable, colored).
|
|
6
17
|
* @param entry - Log entry to format
|
|
@@ -44,6 +55,13 @@ export function formatDev(entry, options) {
|
|
|
44
55
|
if (processedEntry.flow) {
|
|
45
56
|
output += formatFlow(processedEntry.flow, 2);
|
|
46
57
|
}
|
|
58
|
+
// Strip ANSI escapes once at the end when colors are disabled. This is
|
|
59
|
+
// far simpler than threading a "useColors" flag through every nested
|
|
60
|
+
// `colorText(...)` call site and the perf cost (one regex pass over a
|
|
61
|
+
// log line) is negligible relative to the I/O that follows.
|
|
62
|
+
if (options?.colors === false) {
|
|
63
|
+
return stripAnsiEscapes(output);
|
|
64
|
+
}
|
|
47
65
|
return output;
|
|
48
66
|
}
|
|
49
67
|
/**
|
|
@@ -106,7 +124,7 @@ function formatTimestamp(date) {
|
|
|
106
124
|
*/
|
|
107
125
|
function getLevelColor(level) {
|
|
108
126
|
switch (level) {
|
|
109
|
-
case LogLevel.
|
|
127
|
+
case LogLevel.ALL:
|
|
110
128
|
return "white";
|
|
111
129
|
case LogLevel.DEBUG:
|
|
112
130
|
return "blue";
|
|
@@ -331,6 +349,9 @@ export function formatGroupedDev(groupedEntry, options) {
|
|
|
331
349
|
}
|
|
332
350
|
}
|
|
333
351
|
}
|
|
352
|
+
if (options?.colors === false) {
|
|
353
|
+
return stripAnsiEscapes(output);
|
|
354
|
+
}
|
|
334
355
|
return output;
|
|
335
356
|
}
|
|
336
357
|
/**
|
|
@@ -25,11 +25,12 @@ import { LogQueryManager, LogQuery, exportToMarkdown, } from "./logger.query.js"
|
|
|
25
25
|
* - Automatic context detection (class/method names)
|
|
26
26
|
* - Request-scoped context via AsyncLocalStorage
|
|
27
27
|
* - Child logger creation with inherited context
|
|
28
|
-
* - Multiple log levels (
|
|
28
|
+
* - Multiple log levels (ALL, DEBUG, INFO, WARN, ERROR, FATAL, SILENT)
|
|
29
29
|
* - Pluggable transports (console, file, HTTP)
|
|
30
30
|
* @public API
|
|
31
31
|
*/
|
|
32
|
-
let Logger =
|
|
32
|
+
let Logger = class Logger {
|
|
33
|
+
static { Logger_1 = this; }
|
|
33
34
|
pid;
|
|
34
35
|
config;
|
|
35
36
|
transports = [];
|
|
@@ -39,21 +40,40 @@ let Logger = Logger_1 = class Logger {
|
|
|
39
40
|
groupingManager = null;
|
|
40
41
|
healthMonitor = null;
|
|
41
42
|
queryManager = null;
|
|
43
|
+
/**
|
|
44
|
+
* Module-level overrides applied by `Logger.configure(...)` on top of the
|
|
45
|
+
* built-in defaults. Used by the constructor of every future `Logger`
|
|
46
|
+
* instance so application-wide config (e.g. log level, transports) can be
|
|
47
|
+
* set once before bootstrap, before any DI container exists.
|
|
48
|
+
*/
|
|
49
|
+
static defaultOverrides = {};
|
|
42
50
|
name = "Logger Provider";
|
|
43
51
|
version = "4.1.0";
|
|
44
52
|
author = "Richard Zampieri";
|
|
45
53
|
repo = "https://github.com/expressots/expressots";
|
|
46
54
|
constructor() {
|
|
47
55
|
this.pid = process.pid;
|
|
48
|
-
|
|
49
|
-
//
|
|
56
|
+
// Merge module-level overrides on top of the built-in defaults so values
|
|
57
|
+
// set via Logger.configure(...) before container creation propagate to
|
|
58
|
+
// every instance. Caller-supplied transports replace the defaults, level
|
|
59
|
+
// is forwarded to the auto-installed console transport below.
|
|
60
|
+
const baseConfig = getDefaultLoggerConfig();
|
|
61
|
+
this.config = {
|
|
62
|
+
...baseConfig,
|
|
63
|
+
...Logger_1.defaultOverrides,
|
|
64
|
+
};
|
|
65
|
+
// Initialize with default console transport if none provided.
|
|
66
|
+
// Aligning the transport's level with the resolved logger level
|
|
67
|
+
// (which already honours `process.env.LOG_LEVEL`) keeps both
|
|
68
|
+
// filters consistent for messages emitted before any explicit
|
|
69
|
+
// `logger.configure({ level })` call.
|
|
50
70
|
if (this.config.transports.length === 0) {
|
|
51
71
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
];
|
|
72
|
+
const transport = isDevelopment
|
|
73
|
+
? ConsoleTransport.forDevelopment()
|
|
74
|
+
: ConsoleTransport.forProduction();
|
|
75
|
+
transport.level = parseLogLevel(this.config.level);
|
|
76
|
+
this.config.transports = [transport];
|
|
57
77
|
}
|
|
58
78
|
this.transports = this.config.transports;
|
|
59
79
|
// Initialize grouping manager with default config
|
|
@@ -61,6 +81,37 @@ let Logger = Logger_1 = class Logger {
|
|
|
61
81
|
// Initialize query manager with default config
|
|
62
82
|
this.queryManager = new LogQueryManager(this.config.query);
|
|
63
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Configure the default LoggerConfig used by every future `Logger`
|
|
86
|
+
* instance constructed in this process.
|
|
87
|
+
*
|
|
88
|
+
* Use this from application config files **before** bootstrap so the DI
|
|
89
|
+
* container's Logger picks up the correct level / transports / grouping /
|
|
90
|
+
* health / redaction settings without needing to inject and reconfigure it.
|
|
91
|
+
* Calls are merged: later calls override earlier ones, but unspecified
|
|
92
|
+
* keys keep their previously-set values.
|
|
93
|
+
*
|
|
94
|
+
* Already-constructed Logger instances are NOT mutated by this call —
|
|
95
|
+
* use the instance method `logger.configure(...)` to update a live one.
|
|
96
|
+
*
|
|
97
|
+
* @param config - Partial configuration to merge with the defaults.
|
|
98
|
+
* @public API
|
|
99
|
+
*/
|
|
100
|
+
static configure(config) {
|
|
101
|
+
Logger_1.defaultOverrides = {
|
|
102
|
+
...Logger_1.defaultOverrides,
|
|
103
|
+
...config,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reset the module-level overrides set by `Logger.configure`. Useful in
|
|
108
|
+
* tests to undo cross-test pollution.
|
|
109
|
+
*
|
|
110
|
+
* @public API
|
|
111
|
+
*/
|
|
112
|
+
static resetConfigure() {
|
|
113
|
+
Logger_1.defaultOverrides = {};
|
|
114
|
+
}
|
|
64
115
|
/**
|
|
65
116
|
* Configure the logger.
|
|
66
117
|
* @param config - Partial configuration to merge with defaults
|
|
@@ -132,7 +183,7 @@ let Logger = Logger_1 = class Logger {
|
|
|
132
183
|
* @public API
|
|
133
184
|
*/
|
|
134
185
|
trace(message, data) {
|
|
135
|
-
this.log(LogLevel.
|
|
186
|
+
this.log(LogLevel.ALL, message, { data });
|
|
136
187
|
}
|
|
137
188
|
/**
|
|
138
189
|
* Log a debug message (detailed diagnostic).
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { LogLevel, shouldLog } from "../utils/log-levels.js";
|
|
2
2
|
import { formatDev, formatProd } from "../logger.formatter.js";
|
|
3
|
+
/**
|
|
4
|
+
* Decide whether ANSI color codes should be emitted by default.
|
|
5
|
+
*
|
|
6
|
+
* Order of precedence (industry standard, matches chalk / supports-color):
|
|
7
|
+
* 1. `NO_COLOR` env var (any non-empty value) → never colors
|
|
8
|
+
* 2. `FORCE_COLOR` env var (any non-"0"/"false" value) → always colors
|
|
9
|
+
* 3. Process running in production → never colors
|
|
10
|
+
* 4. Stdout is a TTY → colors
|
|
11
|
+
* 5. Otherwise (pipes, redirects, cloud log capture) → no colors
|
|
12
|
+
*
|
|
13
|
+
* This means cloud platforms like Azure App Service, AWS CloudWatch,
|
|
14
|
+
* Heroku, Docker, Kubernetes, etc. — which all capture stdout into a
|
|
15
|
+
* non-TTY pipe — receive plain text by default, without users having
|
|
16
|
+
* to configure anything.
|
|
17
|
+
*/
|
|
18
|
+
function detectColorsDefault() {
|
|
19
|
+
const noColor = process.env.NO_COLOR;
|
|
20
|
+
if (noColor !== undefined && noColor !== "")
|
|
21
|
+
return false;
|
|
22
|
+
const forceColor = process.env.FORCE_COLOR;
|
|
23
|
+
if (forceColor !== undefined &&
|
|
24
|
+
forceColor !== "0" &&
|
|
25
|
+
forceColor !== "false") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (process.env.NODE_ENV === "production")
|
|
29
|
+
return false;
|
|
30
|
+
return Boolean(process.stdout && process.stdout.isTTY);
|
|
31
|
+
}
|
|
3
32
|
/**
|
|
4
33
|
* Console transport for logging to stdout/stderr.
|
|
5
34
|
* @public API
|
|
@@ -22,7 +51,7 @@ export class ConsoleTransport {
|
|
|
22
51
|
this.enabled = options?.enabled ?? true;
|
|
23
52
|
this.structured =
|
|
24
53
|
options?.structured ?? process.env.NODE_ENV === "production";
|
|
25
|
-
this.colors = options?.colors ??
|
|
54
|
+
this.colors = options?.colors ?? detectColorsDefault();
|
|
26
55
|
this.pretty = options?.pretty ?? process.env.NODE_ENV !== "production";
|
|
27
56
|
// Enable redaction by default in production
|
|
28
57
|
this.redact = options?.redact ?? process.env.NODE_ENV === "production";
|
|
@@ -30,6 +59,13 @@ export class ConsoleTransport {
|
|
|
30
59
|
}
|
|
31
60
|
/**
|
|
32
61
|
* Create a console transport optimized for development.
|
|
62
|
+
*
|
|
63
|
+
* Colors are auto-detected: they're emitted when stdout is a TTY
|
|
64
|
+
* (your terminal) and disabled when stdout is piped or captured by
|
|
65
|
+
* a cloud log collector (Azure App Service, AWS CloudWatch, Heroku,
|
|
66
|
+
* Docker, Kubernetes, etc.). Respects the standard `NO_COLOR` and
|
|
67
|
+
* `FORCE_COLOR` environment variables.
|
|
68
|
+
*
|
|
33
69
|
* @param redact - Enable redaction (default: false for development)
|
|
34
70
|
* @returns ConsoleTransport configured for development
|
|
35
71
|
* @public API
|
|
@@ -38,7 +74,7 @@ export class ConsoleTransport {
|
|
|
38
74
|
return new ConsoleTransport({
|
|
39
75
|
level: LogLevel.DEBUG,
|
|
40
76
|
structured: false,
|
|
41
|
-
colors
|
|
77
|
+
// Omit `colors` so the constructor's auto-detect kicks in.
|
|
42
78
|
pretty: true,
|
|
43
79
|
redact, // Usually disabled in development for easier debugging
|
|
44
80
|
});
|
|
@@ -62,17 +98,44 @@ export class ConsoleTransport {
|
|
|
62
98
|
if (!this.enabled || !shouldLog(entry.level, this.level)) {
|
|
63
99
|
return;
|
|
64
100
|
}
|
|
65
|
-
// Build format options with redaction settings
|
|
101
|
+
// Build format options with redaction + color settings. The color
|
|
102
|
+
// flag is honoured by `formatDev` / `formatGroupedDev`; `formatProd`
|
|
103
|
+
// (structured JSON) never emits color regardless.
|
|
66
104
|
const formatOptions = {
|
|
67
105
|
redact: this.redact,
|
|
68
106
|
redactor: this.redactor,
|
|
107
|
+
colors: this.colors,
|
|
69
108
|
};
|
|
70
109
|
const formatted = this.structured
|
|
71
110
|
? formatProd(entry, formatOptions)
|
|
72
111
|
: formatDev(entry, formatOptions);
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
|
|
112
|
+
// Strip trailing newline since console.* methods append their own.
|
|
113
|
+
const msg = formatted.endsWith("\n") ? formatted.slice(0, -1) : formatted;
|
|
114
|
+
// Route through console methods so Studio's LogCapture (which
|
|
115
|
+
// intercepts console.*) can forward these entries to the Live Logs UI.
|
|
116
|
+
// The level mapping mirrors the level chips Studio renders so messages
|
|
117
|
+
// land under the expected filter. We pass a single pre-formatted string
|
|
118
|
+
// so util.format never tries to interpret stray "%s"/"%d" tokens in
|
|
119
|
+
// user-supplied messages.
|
|
120
|
+
switch (entry.level) {
|
|
121
|
+
case LogLevel.DEBUG:
|
|
122
|
+
case LogLevel.ALL:
|
|
123
|
+
console.debug(msg);
|
|
124
|
+
break;
|
|
125
|
+
case LogLevel.INFO:
|
|
126
|
+
console.info(msg);
|
|
127
|
+
break;
|
|
128
|
+
case LogLevel.WARN:
|
|
129
|
+
console.warn(msg);
|
|
130
|
+
break;
|
|
131
|
+
case LogLevel.ERROR:
|
|
132
|
+
case LogLevel.FATAL:
|
|
133
|
+
console.error(msg);
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
console.log(msg);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
76
139
|
}
|
|
77
140
|
flush() {
|
|
78
141
|
// Console doesn't need flushing
|
|
@@ -92,29 +92,34 @@ export class FileTransport {
|
|
|
92
92
|
if (!this.enabled || !shouldLog(entry.level, this.level)) {
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.currentFileSize = await this.getFileSize(filename);
|
|
103
|
-
}
|
|
104
|
-
await this.ensureDirectory();
|
|
105
|
-
const filePath = join(this.directory, this.currentFilename);
|
|
106
|
-
const formatted = formatProd(entry) + "\n";
|
|
107
|
-
const entrySize = Buffer.byteLength(formatted, "utf8");
|
|
95
|
+
// Wrap the entire write pipeline in a single try/catch.
|
|
96
|
+
//
|
|
97
|
+
// FileTransport must NEVER throw out of `log()` — a logger that crashes
|
|
98
|
+
// the request that just wrote to it would be worse than a silent drop.
|
|
99
|
+
// We log a single line to stderr and move on; this matches the
|
|
100
|
+
// ConsoleTransport contract and keeps the framework's "logging never
|
|
101
|
+
// takes down the app" invariant.
|
|
108
102
|
try {
|
|
103
|
+
const filename = this.getFilename();
|
|
104
|
+
const shouldRotate = this.shouldRotate(filename);
|
|
105
|
+
if (shouldRotate) {
|
|
106
|
+
await this.rotateFile(filename);
|
|
107
|
+
}
|
|
108
|
+
if (filename !== this.currentFilename) {
|
|
109
|
+
this.currentFilename = filename;
|
|
110
|
+
this.currentFileSize = await this.getFileSize(filename);
|
|
111
|
+
}
|
|
112
|
+
await this.ensureDirectory();
|
|
113
|
+
const filePath = join(this.directory, this.currentFilename);
|
|
114
|
+
const formatted = formatProd(entry) + "\n";
|
|
115
|
+
const entrySize = Buffer.byteLength(formatted, "utf8");
|
|
109
116
|
await fs.appendFile(filePath, formatted, "utf8");
|
|
110
117
|
this.currentFileSize += entrySize;
|
|
111
|
-
// Check if we need to rotate after this write
|
|
112
118
|
if (this.maxSize > 0 && this.currentFileSize >= this.maxSize) {
|
|
113
119
|
await this.rotateFile(this.currentFilename);
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
catch (error) {
|
|
117
|
-
// Silently fail to avoid log loops
|
|
118
123
|
console.error(`[FileTransport] Failed to write log:`, error);
|
|
119
124
|
}
|
|
120
125
|
}
|
|
@@ -222,13 +227,17 @@ export class FileTransport {
|
|
|
222
227
|
}
|
|
223
228
|
}
|
|
224
229
|
startCleanupInterval() {
|
|
225
|
-
// Run cleanup every hour
|
|
226
230
|
this.cleanupInterval = setInterval(() => {
|
|
227
231
|
this.cleanupOldFiles().catch((error) => {
|
|
228
232
|
console.error(`[FileTransport] Cleanup failed:`, error);
|
|
229
233
|
});
|
|
230
|
-
}, 60 * 60 * 1000);
|
|
231
|
-
//
|
|
234
|
+
}, 60 * 60 * 1000);
|
|
235
|
+
// unref() so the cleanup timer never keeps the event loop alive on its own.
|
|
236
|
+
// Without this a short-lived script that uses FileTransport would hang
|
|
237
|
+
// for an hour at exit waiting on the next tick.
|
|
238
|
+
if (typeof this.cleanupInterval.unref === "function") {
|
|
239
|
+
this.cleanupInterval.unref();
|
|
240
|
+
}
|
|
232
241
|
this.cleanupOldFiles().catch((error) => {
|
|
233
242
|
console.error(`[FileTransport] Initial cleanup failed:`, error);
|
|
234
243
|
});
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export var LogLevel;
|
|
7
7
|
(function (LogLevel) {
|
|
8
|
-
/**
|
|
9
|
-
LogLevel[LogLevel["
|
|
8
|
+
/** Show all logs (ultra-detailed diagnostic information, function entry/exit) */
|
|
9
|
+
LogLevel[LogLevel["ALL"] = 0] = "ALL";
|
|
10
10
|
/** Detailed diagnostic information for debugging */
|
|
11
11
|
LogLevel[LogLevel["DEBUG"] = 1] = "DEBUG";
|
|
12
12
|
/** General informational messages */
|
|
@@ -41,8 +41,9 @@ export function parseLogLevel(level) {
|
|
|
41
41
|
return LogLevel.INFO;
|
|
42
42
|
}
|
|
43
43
|
switch (upperLevel) {
|
|
44
|
+
case "ALL":
|
|
44
45
|
case "TRACE":
|
|
45
|
-
return LogLevel.
|
|
46
|
+
return LogLevel.ALL;
|
|
46
47
|
case "DEBUG":
|
|
47
48
|
return LogLevel.DEBUG;
|
|
48
49
|
case "INFO":
|
|
@@ -67,8 +68,8 @@ export function parseLogLevel(level) {
|
|
|
67
68
|
*/
|
|
68
69
|
export function logLevelToString(level) {
|
|
69
70
|
switch (level) {
|
|
70
|
-
case LogLevel.
|
|
71
|
-
return "
|
|
71
|
+
case LogLevel.ALL:
|
|
72
|
+
return "ALL";
|
|
72
73
|
case LogLevel.DEBUG:
|
|
73
74
|
return "DEBUG";
|
|
74
75
|
case LogLevel.INFO:
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Validation Adapters
|
|
3
3
|
* @module @expressots/core/validation/adapters
|
|
4
4
|
*
|
|
5
|
-
* Built-in adapters for validation libraries.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Built-in adapters for validation libraries. The validation libraries
|
|
6
|
+
* themselves (`class-validator`, `zod`, `yup`) are *optional peer
|
|
7
|
+
* dependencies* — install only the one(s) you actually use.
|
|
8
|
+
*
|
|
9
|
+
* Additional Joi adapter is on the roadmap.
|
|
9
10
|
*/
|
|
10
11
|
export { ClassValidatorAdapter } from "./class-validator.adapter.js";
|
|
12
|
+
export { ZodValidatorAdapter, createZodValidator } from "./zod.adapter.js";
|
|
13
|
+
export { YupValidatorAdapter, createYupValidator } from "./yup.adapter.js";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yup Validation Adapter
|
|
3
|
+
* @module @expressots/core/validation
|
|
4
|
+
*
|
|
5
|
+
* Built-in adapter for the [yup](https://github.com/jquense/yup) validation
|
|
6
|
+
* library. `yup` is declared as an *optional* peer dependency: install it
|
|
7
|
+
* explicitly (`npm i yup`) to enable this adapter.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import * as yup from "yup";
|
|
12
|
+
* import { validationRegistry, createYupValidator } from "@expressots/core";
|
|
13
|
+
*
|
|
14
|
+
* validationRegistry.register(createYupValidator());
|
|
15
|
+
*
|
|
16
|
+
* const CreateUserSchema = yup.object({
|
|
17
|
+
* email: yup.string().email().required(),
|
|
18
|
+
* age: yup.number().integer().min(18).required(),
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* @controller("/users")
|
|
22
|
+
* class UsersController {
|
|
23
|
+
* @Post("/")
|
|
24
|
+
* create(@body(CreateUserSchema) input: yup.InferType<typeof CreateUserSchema>) {
|
|
25
|
+
* // input is fully typed and validated
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Adapter for [yup](https://github.com/jquense/yup). Detects yup schemas at
|
|
32
|
+
* runtime by checking for the `__isYupSchema__` brand or the presence of a
|
|
33
|
+
* `validate` + `cast` method pair.
|
|
34
|
+
*
|
|
35
|
+
* @public API
|
|
36
|
+
*/
|
|
37
|
+
export class YupValidatorAdapter {
|
|
38
|
+
name = "yup";
|
|
39
|
+
priority = 80;
|
|
40
|
+
canHandle(schema) {
|
|
41
|
+
if (schema === null || typeof schema !== "object")
|
|
42
|
+
return false;
|
|
43
|
+
const candidate = schema;
|
|
44
|
+
if (candidate.__isYupSchema__ === true)
|
|
45
|
+
return true;
|
|
46
|
+
return (typeof candidate.validate === "function" &&
|
|
47
|
+
typeof candidate.cast === "function");
|
|
48
|
+
}
|
|
49
|
+
async validate(data, schema, options) {
|
|
50
|
+
if (typeof schema.validate !== "function") {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
errors: [
|
|
54
|
+
{
|
|
55
|
+
path: "",
|
|
56
|
+
message: "YupValidatorAdapter received a schema with no validate() method.",
|
|
57
|
+
code: "invalid_schema",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const yupOptions = {
|
|
64
|
+
abortEarly: options?.abortEarly ?? false,
|
|
65
|
+
stripUnknown: options?.stripUnknown ?? false,
|
|
66
|
+
};
|
|
67
|
+
const validated = await schema.validate(data, yupOptions);
|
|
68
|
+
return { success: true, data: validated };
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const yupError = error;
|
|
72
|
+
// yup batches per-field issues into `inner` when abortEarly is false,
|
|
73
|
+
// and emits a single message otherwise.
|
|
74
|
+
const issues = yupError?.inner && yupError.inner.length > 0
|
|
75
|
+
? yupError.inner
|
|
76
|
+
: [yupError];
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
errors: this.mapIssues(issues),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async transform(data, schema) {
|
|
84
|
+
if (typeof schema.cast === "function") {
|
|
85
|
+
try {
|
|
86
|
+
return schema.cast(data);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
mapIssues(issues) {
|
|
95
|
+
return issues.map((issue) => ({
|
|
96
|
+
path: issue.path ?? "",
|
|
97
|
+
message: issue.message ?? "Validation failed",
|
|
98
|
+
code: issue.type ?? "validation_error",
|
|
99
|
+
received: issue.value,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convenience factory matching the `createXxxValidator` naming used by the
|
|
105
|
+
* other adapters. Equivalent to `new YupValidatorAdapter()`.
|
|
106
|
+
*
|
|
107
|
+
* @public API
|
|
108
|
+
*/
|
|
109
|
+
export function createYupValidator() {
|
|
110
|
+
return new YupValidatorAdapter();
|
|
111
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod Validation Adapter
|
|
3
|
+
* @module @expressots/core/validation
|
|
4
|
+
*
|
|
5
|
+
* Built-in adapter for the [zod](https://zod.dev) validation library.
|
|
6
|
+
* `zod` is declared as an *optional* peer dependency: install it explicitly
|
|
7
|
+
* (`npm i zod`) to enable this adapter; otherwise validation falls back to
|
|
8
|
+
* the next registered adapter.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { z } from "zod";
|
|
13
|
+
* import { validationRegistry, ZodValidatorAdapter, createZodValidator } from "@expressots/core";
|
|
14
|
+
*
|
|
15
|
+
* validationRegistry.register(createZodValidator());
|
|
16
|
+
*
|
|
17
|
+
* const CreateUserSchema = z.object({
|
|
18
|
+
* email: z.string().email(),
|
|
19
|
+
* age: z.number().int().min(18),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* @controller("/users")
|
|
23
|
+
* class UsersController {
|
|
24
|
+
* @Post("/")
|
|
25
|
+
* create(@body(CreateUserSchema) input: z.infer<typeof CreateUserSchema>) {
|
|
26
|
+
* // input is fully typed and validated
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Adapter for [zod](https://zod.dev). Picks up any zod schema (`z.object`,
|
|
33
|
+
* `z.string`, ...) at runtime by structural detection — no compile-time
|
|
34
|
+
* dependency on `zod` is required.
|
|
35
|
+
*
|
|
36
|
+
* @public API
|
|
37
|
+
*/
|
|
38
|
+
export class ZodValidatorAdapter {
|
|
39
|
+
name = "zod";
|
|
40
|
+
priority = 90;
|
|
41
|
+
canHandle(schema) {
|
|
42
|
+
if (schema === null || typeof schema !== "object")
|
|
43
|
+
return false;
|
|
44
|
+
const candidate = schema;
|
|
45
|
+
return (typeof candidate.safeParseAsync === "function" ||
|
|
46
|
+
typeof candidate.safeParse === "function" ||
|
|
47
|
+
typeof candidate.parseAsync === "function" ||
|
|
48
|
+
typeof candidate.parse === "function");
|
|
49
|
+
}
|
|
50
|
+
async validate(data, schema,
|
|
51
|
+
// ValidationOptions accepted for interface compliance; Zod surfaces its own
|
|
52
|
+
// configuration through the schema itself, so options are not consumed here.
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
54
|
+
_options) {
|
|
55
|
+
try {
|
|
56
|
+
// Prefer the safe variants because they encode the failure mode in the
|
|
57
|
+
// result rather than throwing.
|
|
58
|
+
if (typeof schema.safeParseAsync === "function") {
|
|
59
|
+
const r = await schema.safeParseAsync(data);
|
|
60
|
+
return r.success
|
|
61
|
+
? { success: true, data: r.data }
|
|
62
|
+
: { success: false, errors: this.mapIssues(r.error?.issues ?? []) };
|
|
63
|
+
}
|
|
64
|
+
if (typeof schema.safeParse === "function") {
|
|
65
|
+
const r = schema.safeParse(data);
|
|
66
|
+
return r.success
|
|
67
|
+
? { success: true, data: r.data }
|
|
68
|
+
: { success: false, errors: this.mapIssues(r.error?.issues ?? []) };
|
|
69
|
+
}
|
|
70
|
+
if (typeof schema.parseAsync === "function") {
|
|
71
|
+
const parsed = await schema.parseAsync(data);
|
|
72
|
+
return { success: true, data: parsed };
|
|
73
|
+
}
|
|
74
|
+
if (typeof schema.parse === "function") {
|
|
75
|
+
const parsed = schema.parse(data);
|
|
76
|
+
return { success: true, data: parsed };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
errors: [
|
|
81
|
+
{
|
|
82
|
+
path: "",
|
|
83
|
+
message: "ZodValidatorAdapter received a schema with no parse/safeParse method.",
|
|
84
|
+
code: "invalid_schema",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// zod throws ZodError for `parse`/`parseAsync`. Pull issues if present.
|
|
91
|
+
const maybeIssues = error?.issues;
|
|
92
|
+
if (Array.isArray(maybeIssues)) {
|
|
93
|
+
return { success: false, errors: this.mapIssues(maybeIssues) };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
errors: [
|
|
98
|
+
{
|
|
99
|
+
path: "",
|
|
100
|
+
message: error instanceof Error
|
|
101
|
+
? error.message
|
|
102
|
+
: "Validation failed unexpectedly",
|
|
103
|
+
code: "validation_error",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async transform(data, schema) {
|
|
110
|
+
const result = await this.validate(data, schema);
|
|
111
|
+
return result.success ? result.data : data;
|
|
112
|
+
}
|
|
113
|
+
mapIssues(issues) {
|
|
114
|
+
return issues.map((issue) => ({
|
|
115
|
+
path: issue.path.join("."),
|
|
116
|
+
message: issue.message,
|
|
117
|
+
code: issue.code ?? "validation_error",
|
|
118
|
+
received: issue.received,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Convenience factory matching the `createXxxValidator` naming used by the
|
|
124
|
+
* other adapters. Equivalent to `new ZodValidatorAdapter()`.
|
|
125
|
+
*
|
|
126
|
+
* @public API
|
|
127
|
+
*/
|
|
128
|
+
export function createZodValidator() {
|
|
129
|
+
return new ZodValidatorAdapter();
|
|
130
|
+
}
|
|
@@ -18,4 +18,4 @@ export { HelpfulErrorFormatter, } from "./helpful-error-formatter.js";
|
|
|
18
18
|
// Type inference utilities
|
|
19
19
|
export { getParameterType, getAllParameterTypes, getClassProperties, hasClassValidatorDecorators, isZodSchema, isClassConstructor, detectSchemaType, } from "./type-inference.js";
|
|
20
20
|
// Built-in adapters
|
|
21
|
-
export { ClassValidatorAdapter } from "./adapters/index.js";
|
|
21
|
+
export { ClassValidatorAdapter, ZodValidatorAdapter, createZodValidator, YupValidatorAdapter, createYupValidator, } from "./adapters/index.js";
|