adorn-api 1.1.11 → 1.1.13

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.
Files changed (75) hide show
  1. package/README.md +18 -0
  2. package/dist/adapter/express/types.d.ts +3 -46
  3. package/dist/adapter/fastify/coercion.d.ts +12 -0
  4. package/dist/adapter/fastify/coercion.js +289 -0
  5. package/dist/adapter/fastify/controllers.d.ts +7 -0
  6. package/dist/adapter/fastify/controllers.js +201 -0
  7. package/dist/adapter/fastify/index.d.ts +14 -0
  8. package/dist/adapter/fastify/index.js +67 -0
  9. package/dist/adapter/fastify/multipart.d.ts +26 -0
  10. package/dist/adapter/fastify/multipart.js +75 -0
  11. package/dist/adapter/fastify/openapi.d.ts +10 -0
  12. package/dist/adapter/fastify/openapi.js +76 -0
  13. package/dist/adapter/fastify/response-serializer.d.ts +2 -0
  14. package/dist/adapter/fastify/response-serializer.js +162 -0
  15. package/dist/adapter/fastify/types.d.ts +100 -0
  16. package/dist/adapter/fastify/types.js +2 -0
  17. package/dist/adapter/metal-orm/index.d.ts +1 -1
  18. package/dist/adapter/metal-orm/types.d.ts +23 -0
  19. package/dist/adapter/native/coercion.d.ts +12 -0
  20. package/dist/adapter/native/coercion.js +289 -0
  21. package/dist/adapter/native/controllers.d.ts +17 -0
  22. package/dist/adapter/native/controllers.js +215 -0
  23. package/dist/adapter/native/index.d.ts +14 -0
  24. package/dist/adapter/native/index.js +127 -0
  25. package/dist/adapter/native/openapi.d.ts +7 -0
  26. package/dist/adapter/native/openapi.js +82 -0
  27. package/dist/adapter/native/response-serializer.d.ts +5 -0
  28. package/dist/adapter/native/response-serializer.js +160 -0
  29. package/dist/adapter/native/router.d.ts +25 -0
  30. package/dist/adapter/native/router.js +68 -0
  31. package/dist/adapter/native/types.d.ts +77 -0
  32. package/dist/adapter/native/types.js +2 -0
  33. package/dist/core/auth.d.ts +11 -12
  34. package/dist/core/auth.js +2 -2
  35. package/dist/core/logger.d.ts +3 -4
  36. package/dist/core/logger.js +2 -2
  37. package/dist/core/streaming.d.ts +10 -10
  38. package/dist/core/streaming.js +31 -19
  39. package/dist/core/types.d.ts +102 -0
  40. package/dist/index.d.ts +6 -1
  41. package/dist/index.js +16 -1
  42. package/examples/fastify/app.ts +16 -0
  43. package/examples/fastify/index.ts +21 -0
  44. package/package.json +24 -18
  45. package/src/adapter/express/controllers.ts +249 -249
  46. package/src/adapter/express/types.ts +121 -160
  47. package/src/adapter/fastify/coercion.ts +369 -0
  48. package/src/adapter/fastify/controllers.ts +255 -0
  49. package/src/adapter/fastify/index.ts +53 -0
  50. package/src/adapter/fastify/multipart.ts +94 -0
  51. package/src/adapter/fastify/openapi.ts +93 -0
  52. package/src/adapter/fastify/response-serializer.ts +179 -0
  53. package/src/adapter/fastify/types.ts +119 -0
  54. package/src/adapter/metal-orm/index.ts +3 -0
  55. package/src/adapter/metal-orm/types.ts +25 -0
  56. package/src/adapter/native/coercion.ts +369 -0
  57. package/src/adapter/native/controllers.ts +271 -0
  58. package/src/adapter/native/index.ts +116 -0
  59. package/src/adapter/native/openapi.ts +109 -0
  60. package/src/adapter/native/response-serializer.ts +177 -0
  61. package/src/adapter/native/router.ts +90 -0
  62. package/src/adapter/native/types.ts +96 -0
  63. package/src/core/auth.ts +314 -315
  64. package/src/core/health.ts +234 -235
  65. package/src/core/logger.ts +245 -247
  66. package/src/core/streaming.ts +342 -330
  67. package/src/core/types.ts +115 -0
  68. package/src/index.ts +46 -16
  69. package/tests/e2e/fastify.e2e.test.ts +174 -0
  70. package/tests/native.test.ts +191 -0
  71. package/tests/typecheck/query-params.typecheck.ts +42 -0
  72. package/tests/unit/openapi-parameters.test.ts +97 -97
  73. package/tsconfig.json +14 -13
  74. package/tsconfig.typecheck.json +8 -0
  75. package/vitest.config.ts +47 -7
@@ -1,247 +1,245 @@
1
- import type { Request, Response, NextFunction } from "express";
2
-
3
- /**
4
- * Log levels in order of severity.
5
- */
6
- export type LogLevel = "debug" | "info" | "warn" | "error";
7
-
8
- /**
9
- * Structured log entry for a request.
10
- */
11
- export interface RequestLogEntry {
12
- /** Log level */
13
- level: LogLevel;
14
- /** Log message */
15
- message: string;
16
- /** Timestamp in ISO format */
17
- timestamp: string;
18
- /** HTTP method */
19
- method: string;
20
- /** Request URL path */
21
- url: string;
22
- /** HTTP status code */
23
- status: number;
24
- /** Response time in milliseconds */
25
- responseTime: number;
26
- /** Content length of response */
27
- contentLength?: number;
28
- /** Client IP address */
29
- ip?: string;
30
- /** User agent string */
31
- userAgent?: string;
32
- /** Request ID if available */
33
- requestId?: string;
34
- /** Additional context data */
35
- context?: Record<string, unknown>;
36
- }
37
-
38
- /**
39
- * Function to output a log entry.
40
- */
41
- export type LogTransport = (entry: RequestLogEntry) => void;
42
-
43
- /**
44
- * Options for the request logger middleware.
45
- */
46
- export interface RequestLoggerOptions {
47
- /** Minimum log level to output (default: "info") */
48
- level?: LogLevel;
49
- /** Custom transport function (default: JSON to console) */
50
- transport?: LogTransport;
51
- /** Paths to skip logging (e.g., ["/health", "/health/live"]) */
52
- skip?: string[] | ((req: Request) => boolean);
53
- /** Header name for request ID (default: "x-request-id") */
54
- requestIdHeader?: string;
55
- /** Whether to generate request ID if not present (default: true) */
56
- generateRequestId?: boolean;
57
- /** Custom context extractor */
58
- context?: (req: Request, res: Response) => Record<string, unknown>;
59
- }
60
-
61
- /**
62
- * Logger interface for application-level logging.
63
- */
64
- export interface Logger {
65
- debug(message: string, context?: Record<string, unknown>): void;
66
- info(message: string, context?: Record<string, unknown>): void;
67
- warn(message: string, context?: Record<string, unknown>): void;
68
- error(message: string, context?: Record<string, unknown>): void;
69
- }
70
-
71
- /**
72
- * Options for creating a logger.
73
- */
74
- export interface LoggerOptions {
75
- /** Minimum log level to output (default: "info") */
76
- level?: LogLevel;
77
- /** Custom transport function */
78
- transport?: (entry: LogEntry) => void;
79
- /** Default context to include in all log entries */
80
- defaultContext?: Record<string, unknown>;
81
- }
82
-
83
- /**
84
- * Structured log entry for application logging.
85
- */
86
- export interface LogEntry {
87
- /** Log level */
88
- level: LogLevel;
89
- /** Log message */
90
- message: string;
91
- /** Timestamp in ISO format */
92
- timestamp: string;
93
- /** Additional context data */
94
- context?: Record<string, unknown>;
95
- }
96
-
97
- const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
98
- debug: 0,
99
- info: 1,
100
- warn: 2,
101
- error: 3
102
- };
103
-
104
- /**
105
- * Default JSON transport that logs to console.
106
- */
107
- export function jsonTransport(entry: RequestLogEntry | LogEntry): void {
108
- const output = JSON.stringify(entry);
109
- if (entry.level === "error") {
110
- console.error(output);
111
- } else if (entry.level === "warn") {
112
- console.warn(output);
113
- } else {
114
- console.log(output);
115
- }
116
- }
117
-
118
- /**
119
- * Pretty transport for development.
120
- */
121
- export function prettyTransport(entry: RequestLogEntry | LogEntry): void {
122
- const timestamp = entry.timestamp.substring(11, 23);
123
- const levelColors: Record<LogLevel, string> = {
124
- debug: "\x1b[90m",
125
- info: "\x1b[36m",
126
- warn: "\x1b[33m",
127
- error: "\x1b[31m"
128
- };
129
- const reset = "\x1b[0m";
130
- const color = levelColors[entry.level];
131
-
132
- if ("method" in entry) {
133
- const statusColor = entry.status >= 400 ? "\x1b[31m" : entry.status >= 300 ? "\x1b[33m" : "\x1b[32m";
134
- console.log(
135
- `${color}[${timestamp}]${reset} ${entry.method} ${entry.url} ${statusColor}${entry.status}${reset} ${entry.responseTime}ms`
136
- );
137
- } else {
138
- console.log(`${color}[${timestamp}] ${entry.level.toUpperCase()}${reset} ${entry.message}`);
139
- }
140
- }
141
-
142
- /**
143
- * Generates a simple unique ID.
144
- */
145
- function generateId(): string {
146
- return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
147
- }
148
-
149
- /**
150
- * Determines log level based on status code.
151
- */
152
- function statusToLevel(status: number): LogLevel {
153
- if (status >= 500) return "error";
154
- if (status >= 400) return "warn";
155
- return "info";
156
- }
157
-
158
- /**
159
- * Creates an Express middleware for request logging.
160
- * @param options - Logger configuration options
161
- * @returns Express middleware function
162
- */
163
- export function requestLogger(options: RequestLoggerOptions = {}) {
164
- const minLevel = options.level ?? "info";
165
- const transport = options.transport ?? jsonTransport;
166
- const requestIdHeader = options.requestIdHeader ?? "x-request-id";
167
- const generateRequestId = options.generateRequestId ?? true;
168
- const skipPaths = Array.isArray(options.skip) ? new Set(options.skip) : null;
169
- const skipFn = typeof options.skip === "function" ? options.skip : null;
170
- const contextFn = options.context;
171
-
172
- return (req: Request, res: Response, next: NextFunction): void => {
173
- if (skipPaths?.has(req.path) || skipFn?.(req)) {
174
- next();
175
- return;
176
- }
177
-
178
- const startTime = process.hrtime.bigint();
179
- let requestId = req.headers[requestIdHeader] as string | undefined;
180
- if (!requestId && generateRequestId) {
181
- requestId = generateId();
182
- }
183
- if (requestId) {
184
- res.setHeader(requestIdHeader, requestId);
185
- }
186
-
187
- const originalEnd = res.end;
188
- res.end = function (this: Response, ...args: Parameters<Response["end"]>) {
189
- const endTime = process.hrtime.bigint();
190
- const responseTime = Number(endTime - startTime) / 1_000_000;
191
-
192
- const level = statusToLevel(res.statusCode);
193
- if (LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[minLevel]) {
194
- const entry: RequestLogEntry = {
195
- level,
196
- message: `${req.method} ${req.path} ${res.statusCode}`,
197
- timestamp: new Date().toISOString(),
198
- method: req.method,
199
- url: req.originalUrl || req.url,
200
- status: res.statusCode,
201
- responseTime: Math.round(responseTime * 100) / 100,
202
- contentLength: res.get("content-length") ? parseInt(res.get("content-length")!, 10) : undefined,
203
- ip: req.ip || (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim(),
204
- userAgent: req.headers["user-agent"],
205
- requestId,
206
- context: contextFn?.(req, res)
207
- };
208
- transport(entry);
209
- }
210
-
211
- return originalEnd.apply(this, args);
212
- } as Response["end"];
213
-
214
- next();
215
- };
216
- }
217
-
218
- /**
219
- * Creates a structured logger for application-level logging.
220
- * @param options - Logger configuration options
221
- * @returns Logger instance
222
- */
223
- export function createLogger(options: LoggerOptions = {}): Logger {
224
- const minLevel = options.level ?? "info";
225
- const transport = options.transport ?? jsonTransport;
226
- const defaultContext = options.defaultContext;
227
-
228
- function log(level: LogLevel, message: string, context?: Record<string, unknown>): void {
229
- if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[minLevel]) {
230
- return;
231
- }
232
- const entry: LogEntry = {
233
- level,
234
- message,
235
- timestamp: new Date().toISOString(),
236
- context: context || defaultContext ? { ...defaultContext, ...context } : undefined
237
- };
238
- transport(entry);
239
- }
240
-
241
- return {
242
- debug: (message, context) => log("debug", message, context),
243
- info: (message, context) => log("info", message, context),
244
- warn: (message, context) => log("warn", message, context),
245
- error: (message, context) => log("error", message, context)
246
- };
247
- }
1
+ /**
2
+ * Log levels in order of severity.
3
+ */
4
+ export type LogLevel = "debug" | "info" | "warn" | "error";
5
+
6
+ /**
7
+ * Structured log entry for a request.
8
+ */
9
+ export interface RequestLogEntry {
10
+ /** Log level */
11
+ level: LogLevel;
12
+ /** Log message */
13
+ message: string;
14
+ /** Timestamp in ISO format */
15
+ timestamp: string;
16
+ /** HTTP method */
17
+ method: string;
18
+ /** Request URL path */
19
+ url: string;
20
+ /** HTTP status code */
21
+ status: number;
22
+ /** Response time in milliseconds */
23
+ responseTime: number;
24
+ /** Content length of response */
25
+ contentLength?: number;
26
+ /** Client IP address */
27
+ ip?: string;
28
+ /** User agent string */
29
+ userAgent?: string;
30
+ /** Request ID if available */
31
+ requestId?: string;
32
+ /** Additional context data */
33
+ context?: Record<string, unknown>;
34
+ }
35
+
36
+ /**
37
+ * Function to output a log entry.
38
+ */
39
+ export type LogTransport = (entry: RequestLogEntry) => void;
40
+
41
+ /**
42
+ * Options for the request logger middleware.
43
+ */
44
+ export interface RequestLoggerOptions {
45
+ /** Minimum log level to output (default: "info") */
46
+ level?: LogLevel;
47
+ /** Custom transport function (default: JSON to console) */
48
+ transport?: LogTransport;
49
+ /** Paths to skip logging (e.g., ["/health", "/health/live"]) */
50
+ skip?: string[] | ((req: any) => boolean);
51
+ /** Header name for request ID (default: "x-request-id") */
52
+ requestIdHeader?: string;
53
+ /** Whether to generate request ID if not present (default: true) */
54
+ generateRequestId?: boolean;
55
+ /** Custom context extractor */
56
+ context?: (req: any, res: any) => Record<string, unknown>;
57
+ }
58
+
59
+ /**
60
+ * Logger interface for application-level logging.
61
+ */
62
+ export interface Logger {
63
+ debug(message: string, context?: Record<string, unknown>): void;
64
+ info(message: string, context?: Record<string, unknown>): void;
65
+ warn(message: string, context?: Record<string, unknown>): void;
66
+ error(message: string, context?: Record<string, unknown>): void;
67
+ }
68
+
69
+ /**
70
+ * Options for creating a logger.
71
+ */
72
+ export interface LoggerOptions {
73
+ /** Minimum log level to output (default: "info") */
74
+ level?: LogLevel;
75
+ /** Custom transport function */
76
+ transport?: (entry: LogEntry) => void;
77
+ /** Default context to include in all log entries */
78
+ defaultContext?: Record<string, unknown>;
79
+ }
80
+
81
+ /**
82
+ * Structured log entry for application logging.
83
+ */
84
+ export interface LogEntry {
85
+ /** Log level */
86
+ level: LogLevel;
87
+ /** Log message */
88
+ message: string;
89
+ /** Timestamp in ISO format */
90
+ timestamp: string;
91
+ /** Additional context data */
92
+ context?: Record<string, unknown>;
93
+ }
94
+
95
+ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
96
+ debug: 0,
97
+ info: 1,
98
+ warn: 2,
99
+ error: 3
100
+ };
101
+
102
+ /**
103
+ * Default JSON transport that logs to console.
104
+ */
105
+ export function jsonTransport(entry: RequestLogEntry | LogEntry): void {
106
+ const output = JSON.stringify(entry);
107
+ if (entry.level === "error") {
108
+ console.error(output);
109
+ } else if (entry.level === "warn") {
110
+ console.warn(output);
111
+ } else {
112
+ console.log(output);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Pretty transport for development.
118
+ */
119
+ export function prettyTransport(entry: RequestLogEntry | LogEntry): void {
120
+ const timestamp = entry.timestamp.substring(11, 23);
121
+ const levelColors: Record<LogLevel, string> = {
122
+ debug: "\x1b[90m",
123
+ info: "\x1b[36m",
124
+ warn: "\x1b[33m",
125
+ error: "\x1b[31m"
126
+ };
127
+ const reset = "\x1b[0m";
128
+ const color = levelColors[entry.level];
129
+
130
+ if ("method" in entry) {
131
+ const statusColor = entry.status >= 400 ? "\x1b[31m" : entry.status >= 300 ? "\x1b[33m" : "\x1b[32m";
132
+ console.log(
133
+ `${color}[${timestamp}]${reset} ${entry.method} ${entry.url} ${statusColor}${entry.status}${reset} ${entry.responseTime}ms`
134
+ );
135
+ } else {
136
+ console.log(`${color}[${timestamp}] ${entry.level.toUpperCase()}${reset} ${entry.message}`);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Generates a simple unique ID.
142
+ */
143
+ function generateId(): string {
144
+ return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`;
145
+ }
146
+
147
+ /**
148
+ * Determines log level based on status code.
149
+ */
150
+ function statusToLevel(status: number): LogLevel {
151
+ if (status >= 500) return "error";
152
+ if (status >= 400) return "warn";
153
+ return "info";
154
+ }
155
+
156
+ /**
157
+ * Creates an Express middleware for request logging.
158
+ * @param options - Logger configuration options
159
+ * @returns Express middleware function
160
+ */
161
+ export function requestLogger(options: RequestLoggerOptions = {}) {
162
+ const minLevel = options.level ?? "info";
163
+ const transport = options.transport ?? jsonTransport;
164
+ const requestIdHeader = options.requestIdHeader ?? "x-request-id";
165
+ const generateRequestId = options.generateRequestId ?? true;
166
+ const skipPaths = Array.isArray(options.skip) ? new Set(options.skip) : null;
167
+ const skipFn = typeof options.skip === "function" ? options.skip : null;
168
+ const contextFn = options.context;
169
+
170
+ return (req: any, res: any, next: (err?: any) => void): void => {
171
+ if (skipPaths?.has(req.path) || skipFn?.(req)) {
172
+ next();
173
+ return;
174
+ }
175
+
176
+ const startTime = process.hrtime.bigint();
177
+ let requestId = req.headers[requestIdHeader] as string | undefined;
178
+ if (!requestId && generateRequestId) {
179
+ requestId = generateId();
180
+ }
181
+ if (requestId && res.setHeader) {
182
+ res.setHeader(requestIdHeader, requestId);
183
+ }
184
+
185
+ const originalEnd = res.end;
186
+ res.end = function (this: any, ...args: any[]) {
187
+ const endTime = process.hrtime.bigint();
188
+ const responseTime = Number(endTime - startTime) / 1_000_000;
189
+
190
+ const level = statusToLevel(res.statusCode);
191
+ if (LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[minLevel]) {
192
+ const entry: RequestLogEntry = {
193
+ level,
194
+ message: `${req.method} ${req.path} ${res.statusCode}`,
195
+ timestamp: new Date().toISOString(),
196
+ method: req.method,
197
+ url: req.originalUrl || req.url,
198
+ status: res.statusCode,
199
+ responseTime: Math.round(responseTime * 100) / 100,
200
+ contentLength: res.get ? parseInt(res.get("content-length")!, 10) : undefined,
201
+ ip: req.ip || (req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim(),
202
+ userAgent: req.headers["user-agent"],
203
+ requestId,
204
+ context: contextFn?.(req, res)
205
+ };
206
+ transport(entry);
207
+ }
208
+
209
+ return originalEnd.apply(this, args);
210
+ } as any;
211
+
212
+ next();
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Creates a structured logger for application-level logging.
218
+ * @param options - Logger configuration options
219
+ * @returns Logger instance
220
+ */
221
+ export function createLogger(options: LoggerOptions = {}): Logger {
222
+ const minLevel = options.level ?? "info";
223
+ const transport = options.transport ?? jsonTransport;
224
+ const defaultContext = options.defaultContext;
225
+
226
+ function log(level: LogLevel, message: string, context?: Record<string, unknown>): void {
227
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[minLevel]) {
228
+ return;
229
+ }
230
+ const entry: LogEntry = {
231
+ level,
232
+ message,
233
+ timestamp: new Date().toISOString(),
234
+ context: context || defaultContext ? { ...defaultContext, ...context } : undefined
235
+ };
236
+ transport(entry);
237
+ }
238
+
239
+ return {
240
+ debug: (message, context) => log("debug", message, context),
241
+ info: (message, context) => log("info", message, context),
242
+ warn: (message, context) => log("warn", message, context),
243
+ error: (message, context) => log("error", message, context)
244
+ };
245
+ }