@spfn/core 0.1.0-alpha.88 → 0.2.0-beta.1

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 (71) hide show
  1. package/README.md +1046 -384
  2. package/dist/boss-D-fGtVgM.d.ts +187 -0
  3. package/dist/cache/index.d.ts +13 -33
  4. package/dist/cache/index.js +14 -703
  5. package/dist/cache/index.js.map +1 -1
  6. package/dist/codegen/index.d.ts +167 -17
  7. package/dist/codegen/index.js +76 -1419
  8. package/dist/codegen/index.js.map +1 -1
  9. package/dist/config/index.d.ts +1191 -0
  10. package/dist/config/index.js +264 -0
  11. package/dist/config/index.js.map +1 -0
  12. package/dist/db/index.d.ts +728 -59
  13. package/dist/db/index.js +1028 -1225
  14. package/dist/db/index.js.map +1 -1
  15. package/dist/env/index.d.ts +579 -308
  16. package/dist/env/index.js +438 -930
  17. package/dist/env/index.js.map +1 -1
  18. package/dist/errors/index.d.ts +417 -29
  19. package/dist/errors/index.js +359 -98
  20. package/dist/errors/index.js.map +1 -1
  21. package/dist/event/index.d.ts +108 -0
  22. package/dist/event/index.js +122 -0
  23. package/dist/event/index.js.map +1 -0
  24. package/dist/job/index.d.ts +172 -0
  25. package/dist/job/index.js +361 -0
  26. package/dist/job/index.js.map +1 -0
  27. package/dist/logger/index.d.ts +20 -79
  28. package/dist/logger/index.js +82 -387
  29. package/dist/logger/index.js.map +1 -1
  30. package/dist/middleware/index.d.ts +2 -11
  31. package/dist/middleware/index.js +49 -703
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/nextjs/index.d.ts +120 -0
  34. package/dist/nextjs/index.js +416 -0
  35. package/dist/nextjs/index.js.map +1 -0
  36. package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +288 -262
  37. package/dist/nextjs/server.js +568 -0
  38. package/dist/nextjs/server.js.map +1 -0
  39. package/dist/route/index.d.ts +667 -25
  40. package/dist/route/index.js +437 -1287
  41. package/dist/route/index.js.map +1 -1
  42. package/dist/route/types.d.ts +38 -0
  43. package/dist/route/types.js +3 -0
  44. package/dist/route/types.js.map +1 -0
  45. package/dist/server/index.d.ts +201 -67
  46. package/dist/server/index.js +921 -3182
  47. package/dist/server/index.js.map +1 -1
  48. package/dist/types-BGl4QL1w.d.ts +77 -0
  49. package/dist/types-DRG2XMTR.d.ts +157 -0
  50. package/package.json +52 -47
  51. package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
  52. package/dist/client/index.d.ts +0 -358
  53. package/dist/client/index.js +0 -357
  54. package/dist/client/index.js.map +0 -1
  55. package/dist/client/nextjs/index.js +0 -371
  56. package/dist/client/nextjs/index.js.map +0 -1
  57. package/dist/codegen/generators/index.d.ts +0 -19
  58. package/dist/codegen/generators/index.js +0 -1404
  59. package/dist/codegen/generators/index.js.map +0 -1
  60. package/dist/database-errors-BNNmLTJE.d.ts +0 -86
  61. package/dist/events/index.d.ts +0 -183
  62. package/dist/events/index.js +0 -77
  63. package/dist/events/index.js.map +0 -1
  64. package/dist/index-DHiAqhKv.d.ts +0 -101
  65. package/dist/index.d.ts +0 -8
  66. package/dist/index.js +0 -3674
  67. package/dist/index.js.map +0 -1
  68. package/dist/types/index.d.ts +0 -121
  69. package/dist/types/index.js +0 -38
  70. package/dist/types/index.js.map +0 -1
  71. package/dist/types-BXibIEyj.d.ts +0 -60
@@ -1,718 +1,61 @@
1
- import { existsSync, mkdirSync, createWriteStream, statSync, readdirSync, renameSync, unlinkSync, accessSync, constants, writeFileSync } from 'fs';
2
- import { join } from 'path';
1
+ import { SerializableError } from '@spfn/core/errors';
2
+ import { logger } from '@spfn/core/logger';
3
+ import { env } from '@spfn/core/config';
3
4
  import { randomBytes } from 'crypto';
4
5
 
5
- // src/logger/types.ts
6
- var LOG_LEVEL_PRIORITY = {
7
- debug: 0,
8
- info: 1,
9
- warn: 2,
10
- error: 3,
11
- fatal: 4
12
- };
13
-
14
- // src/logger/formatters.ts
15
- var SENSITIVE_KEYS = [
16
- "password",
17
- "passwd",
18
- "pwd",
19
- "secret",
20
- "token",
21
- "apikey",
22
- "api_key",
23
- "accesstoken",
24
- "access_token",
25
- "refreshtoken",
26
- "refresh_token",
27
- "authorization",
28
- "auth",
29
- "cookie",
30
- "session",
31
- "sessionid",
32
- "session_id",
33
- "privatekey",
34
- "private_key",
35
- "creditcard",
36
- "credit_card",
37
- "cardnumber",
38
- "card_number",
39
- "cvv",
40
- "ssn",
41
- "pin"
42
- ];
43
- var MASKED_VALUE = "***MASKED***";
44
- function isSensitiveKey(key) {
45
- const lowerKey = key.toLowerCase();
46
- return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive));
47
- }
48
- function maskSensitiveData(data) {
49
- if (data === null || data === void 0) {
50
- return data;
51
- }
52
- if (Array.isArray(data)) {
53
- return data.map((item) => maskSensitiveData(item));
54
- }
55
- if (typeof data === "object") {
56
- const masked = {};
57
- for (const [key, value] of Object.entries(data)) {
58
- if (isSensitiveKey(key)) {
59
- masked[key] = MASKED_VALUE;
60
- } else if (typeof value === "object" && value !== null) {
61
- masked[key] = maskSensitiveData(value);
62
- } else {
63
- masked[key] = value;
64
- }
65
- }
66
- return masked;
67
- }
68
- return data;
69
- }
70
- var COLORS = {
71
- reset: "\x1B[0m",
72
- bright: "\x1B[1m",
73
- dim: "\x1B[2m",
74
- // 로그 레벨 컬러
75
- debug: "\x1B[36m",
76
- // cyan
77
- info: "\x1B[32m",
78
- // green
79
- warn: "\x1B[33m",
80
- // yellow
81
- error: "\x1B[31m",
82
- // red
83
- fatal: "\x1B[35m",
84
- // magenta
85
- // 추가 컬러
86
- gray: "\x1B[90m"
87
- };
88
- function formatTimestamp(date) {
89
- return date.toISOString();
90
- }
91
- function formatTimestampHuman(date) {
92
- const year = date.getFullYear();
93
- const month = String(date.getMonth() + 1).padStart(2, "0");
94
- const day = String(date.getDate()).padStart(2, "0");
95
- const hours = String(date.getHours()).padStart(2, "0");
96
- const minutes = String(date.getMinutes()).padStart(2, "0");
97
- const seconds = String(date.getSeconds()).padStart(2, "0");
98
- const ms = String(date.getMilliseconds()).padStart(3, "0");
99
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
100
- }
101
- function formatError(error) {
102
- const lines = [];
103
- lines.push(`${error.name}: ${error.message}`);
104
- if (error.stack) {
105
- const stackLines = error.stack.split("\n").slice(1);
106
- lines.push(...stackLines);
107
- }
108
- return lines.join("\n");
109
- }
110
- function formatConsole(metadata, colorize = true) {
111
- const parts = [];
112
- const timestamp = formatTimestampHuman(metadata.timestamp);
113
- if (colorize) {
114
- parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);
115
- } else {
116
- parts.push(`[${timestamp}]`);
117
- }
118
- if (metadata.module) {
119
- if (colorize) {
120
- parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);
121
- } else {
122
- parts.push(`[module=${metadata.module}]`);
123
- }
124
- }
125
- if (metadata.context && Object.keys(metadata.context).length > 0) {
126
- Object.entries(metadata.context).forEach(([key, value]) => {
127
- let valueStr;
128
- if (typeof value === "string") {
129
- valueStr = value;
130
- } else if (typeof value === "object" && value !== null) {
131
- try {
132
- valueStr = JSON.stringify(value);
133
- } catch (error) {
134
- valueStr = "[circular]";
135
- }
136
- } else {
137
- valueStr = String(value);
138
- }
139
- if (colorize) {
140
- parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);
141
- } else {
142
- parts.push(`[${key}=${valueStr}]`);
143
- }
144
- });
145
- }
146
- const levelStr = metadata.level.toUpperCase();
147
- if (colorize) {
148
- const color = COLORS[metadata.level];
149
- parts.push(`${color}(${levelStr})${COLORS.reset}:`);
150
- } else {
151
- parts.push(`(${levelStr}):`);
152
- }
153
- if (colorize) {
154
- parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);
6
+ // src/middleware/error-handler.ts
7
+ var errorLogger = logger.child("@spfn/core:error-handler");
8
+ function logError(err, logData, includeStack) {
9
+ const logLevel = logData.statusCode >= 500 ? "error" : "warn";
10
+ if (includeStack) {
11
+ errorLogger[logLevel]("Error occurred", err, logData);
155
12
  } else {
156
- parts.push(metadata.message);
13
+ errorLogger[logLevel]("Error occurred", logData);
157
14
  }
158
- let output = parts.join(" ");
159
- if (metadata.error) {
160
- output += "\n" + formatError(metadata.error);
161
- }
162
- return output;
163
15
  }
164
- function formatJSON(metadata) {
165
- const obj = {
166
- timestamp: formatTimestamp(metadata.timestamp),
167
- level: metadata.level,
168
- message: metadata.message
169
- };
170
- if (metadata.module) {
171
- obj.module = metadata.module;
172
- }
173
- if (metadata.context) {
174
- obj.context = metadata.context;
175
- }
176
- if (metadata.error) {
177
- obj.error = {
178
- name: metadata.error.name,
179
- message: metadata.error.message,
180
- stack: metadata.error.stack
181
- };
182
- }
183
- return JSON.stringify(obj);
16
+ function isSerializableError(err) {
17
+ return err instanceof SerializableError || typeof err.toJSON === "function" && typeof err.statusCode === "number";
184
18
  }
185
-
186
- // src/logger/logger.ts
187
- var Logger = class _Logger {
188
- config;
189
- module;
190
- constructor(config) {
191
- this.config = config;
192
- this.module = config.module;
193
- }
194
- /**
195
- * Get current log level
196
- */
197
- get level() {
198
- return this.config.level;
199
- }
200
- /**
201
- * Create child logger (per module)
202
- */
203
- child(module) {
204
- return new _Logger({
205
- ...this.config,
206
- module
207
- });
208
- }
209
- /**
210
- * Debug log
211
- */
212
- debug(message, context) {
213
- this.log("debug", message, void 0, context);
214
- }
215
- /**
216
- * Info log
217
- */
218
- info(message, context) {
219
- this.log("info", message, void 0, context);
220
- }
221
- warn(message, errorOrContext, context) {
222
- if (errorOrContext instanceof Error) {
223
- this.log("warn", message, errorOrContext, context);
224
- } else {
225
- this.log("warn", message, void 0, errorOrContext);
226
- }
227
- }
228
- error(message, errorOrContext, context) {
229
- if (errorOrContext instanceof Error) {
230
- this.log("error", message, errorOrContext, context);
231
- } else {
232
- this.log("error", message, void 0, errorOrContext);
233
- }
234
- }
235
- fatal(message, errorOrContext, context) {
236
- if (errorOrContext instanceof Error) {
237
- this.log("fatal", message, errorOrContext, context);
238
- } else {
239
- this.log("fatal", message, void 0, errorOrContext);
240
- }
241
- }
242
- /**
243
- * Log processing (internal)
244
- */
245
- log(level, message, error, context) {
246
- if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level]) {
247
- return;
248
- }
249
- const metadata = {
250
- timestamp: /* @__PURE__ */ new Date(),
251
- level,
252
- message,
253
- module: this.module,
254
- error,
255
- // Mask sensitive information in context to prevent credential leaks
256
- context: context ? maskSensitiveData(context) : void 0
257
- };
258
- this.processTransports(metadata);
259
- }
260
- /**
261
- * Process Transports
262
- */
263
- processTransports(metadata) {
264
- const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
265
- Promise.all(promises).catch((error) => {
266
- const errorMessage = error instanceof Error ? error.message : String(error);
267
- process.stderr.write(`[Logger] Transport error: ${errorMessage}
268
- `);
269
- });
270
- }
271
- /**
272
- * Transport log (error-safe)
273
- */
274
- async safeTransportLog(transport, metadata) {
275
- try {
276
- await transport.log(metadata);
277
- } catch (error) {
278
- const errorMessage = error instanceof Error ? error.message : String(error);
279
- process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
280
- `);
281
- }
282
- }
283
- /**
284
- * Close all Transports
285
- */
286
- async close() {
287
- const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
288
- await Promise.all(closePromises);
289
- }
290
- };
291
-
292
- // src/logger/transports/console.ts
293
- var ConsoleTransport = class {
294
- name = "console";
295
- level;
296
- enabled;
297
- colorize;
298
- constructor(config) {
299
- this.level = config.level;
300
- this.enabled = config.enabled;
301
- this.colorize = config.colorize ?? true;
302
- }
303
- async log(metadata) {
304
- if (!this.enabled) {
305
- return;
306
- }
307
- if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
308
- return;
309
- }
310
- const message = formatConsole(metadata, this.colorize);
311
- if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
312
- console.error(message);
313
- } else {
314
- console.log(message);
315
- }
316
- }
317
- };
318
- var FileTransport = class {
319
- name = "file";
320
- level;
321
- enabled;
322
- logDir;
323
- maxFileSize;
324
- maxFiles;
325
- currentStream = null;
326
- currentFilename = null;
327
- constructor(config) {
328
- this.level = config.level;
329
- this.enabled = config.enabled;
330
- this.logDir = config.logDir;
331
- this.maxFileSize = config.maxFileSize ?? 10 * 1024 * 1024;
332
- this.maxFiles = config.maxFiles ?? 10;
333
- if (!existsSync(this.logDir)) {
334
- mkdirSync(this.logDir, { recursive: true });
335
- }
336
- }
337
- async log(metadata) {
338
- if (!this.enabled) {
339
- return;
340
- }
341
- if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
342
- return;
343
- }
344
- const message = formatJSON(metadata);
345
- const filename = this.getLogFilename(metadata.timestamp);
346
- if (this.currentFilename !== filename) {
347
- await this.rotateStream(filename);
348
- await this.cleanOldFiles();
349
- } else if (this.currentFilename) {
350
- await this.checkAndRotateBySize();
351
- }
352
- if (this.currentStream) {
353
- return new Promise((resolve, reject) => {
354
- this.currentStream.write(message + "\n", "utf-8", (error) => {
355
- if (error) {
356
- process.stderr.write(`[FileTransport] Failed to write log: ${error.message}
357
- `);
358
- reject(error);
359
- } else {
360
- resolve();
361
- }
362
- });
363
- });
364
- }
365
- }
366
- /**
367
- * 스트림 교체 (날짜 변경 시)
368
- */
369
- async rotateStream(filename) {
370
- if (this.currentStream) {
371
- await this.closeStream();
372
- }
373
- const filepath = join(this.logDir, filename);
374
- this.currentStream = createWriteStream(filepath, {
375
- flags: "a",
376
- // append mode
377
- encoding: "utf-8"
378
- });
379
- this.currentFilename = filename;
380
- this.currentStream.on("error", (error) => {
381
- process.stderr.write(`[FileTransport] Stream error: ${error.message}
382
- `);
383
- this.currentStream = null;
384
- this.currentFilename = null;
385
- });
386
- }
387
- /**
388
- * 현재 스트림 닫기
389
- */
390
- async closeStream() {
391
- if (!this.currentStream) {
392
- return;
393
- }
394
- return new Promise((resolve, reject) => {
395
- this.currentStream.end((error) => {
396
- if (error) {
397
- reject(error);
398
- } else {
399
- this.currentStream = null;
400
- this.currentFilename = null;
401
- resolve();
402
- }
403
- });
404
- });
405
- }
406
- /**
407
- * 파일 크기 체크 및 크기 기반 로테이션
408
- */
409
- async checkAndRotateBySize() {
410
- if (!this.currentFilename) {
411
- return;
412
- }
413
- const filepath = join(this.logDir, this.currentFilename);
414
- if (!existsSync(filepath)) {
415
- return;
416
- }
417
- try {
418
- const stats = statSync(filepath);
419
- if (stats.size >= this.maxFileSize) {
420
- await this.rotateBySize();
421
- }
422
- } catch (error) {
423
- const errorMessage = error instanceof Error ? error.message : String(error);
424
- process.stderr.write(`[FileTransport] Failed to check file size: ${errorMessage}
425
- `);
426
- }
427
- }
428
- /**
429
- * 크기 기반 로테이션 수행
430
- * 예: 2025-01-01.log -> 2025-01-01.1.log, 2025-01-01.1.log -> 2025-01-01.2.log
431
- */
432
- async rotateBySize() {
433
- if (!this.currentFilename) {
434
- return;
435
- }
436
- await this.closeStream();
437
- const baseName = this.currentFilename.replace(/\.log$/, "");
438
- const files = readdirSync(this.logDir);
439
- const relatedFiles = files.filter((file) => file.startsWith(baseName) && file.endsWith(".log")).sort().reverse();
440
- for (const file of relatedFiles) {
441
- const match = file.match(/\.(\d+)\.log$/);
442
- if (match) {
443
- const oldNum = parseInt(match[1], 10);
444
- const newNum = oldNum + 1;
445
- const oldPath = join(this.logDir, file);
446
- const newPath2 = join(this.logDir, `${baseName}.${newNum}.log`);
447
- try {
448
- renameSync(oldPath, newPath2);
449
- } catch (error) {
450
- const errorMessage = error instanceof Error ? error.message : String(error);
451
- process.stderr.write(`[FileTransport] Failed to rotate file: ${errorMessage}
452
- `);
453
- }
454
- }
455
- }
456
- const currentPath = join(this.logDir, this.currentFilename);
457
- const newPath = join(this.logDir, `${baseName}.1.log`);
458
- try {
459
- if (existsSync(currentPath)) {
460
- renameSync(currentPath, newPath);
461
- }
462
- } catch (error) {
463
- const errorMessage = error instanceof Error ? error.message : String(error);
464
- process.stderr.write(`[FileTransport] Failed to rotate current file: ${errorMessage}
465
- `);
466
- }
467
- await this.rotateStream(this.currentFilename);
468
- }
469
- /**
470
- * 오래된 로그 파일 정리
471
- * maxFiles 개수를 초과하는 로그 파일 삭제
472
- */
473
- async cleanOldFiles() {
474
- try {
475
- if (!existsSync(this.logDir)) {
476
- return;
477
- }
478
- const files = readdirSync(this.logDir);
479
- const logFiles = files.filter((file) => file.endsWith(".log")).map((file) => {
480
- const filepath = join(this.logDir, file);
481
- const stats = statSync(filepath);
482
- return { file, mtime: stats.mtime };
483
- }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
484
- if (logFiles.length > this.maxFiles) {
485
- const filesToDelete = logFiles.slice(this.maxFiles);
486
- for (const { file } of filesToDelete) {
487
- const filepath = join(this.logDir, file);
488
- try {
489
- unlinkSync(filepath);
490
- } catch (error) {
491
- const errorMessage = error instanceof Error ? error.message : String(error);
492
- process.stderr.write(`[FileTransport] Failed to delete old file "${file}": ${errorMessage}
493
- `);
494
- }
495
- }
496
- }
497
- } catch (error) {
498
- const errorMessage = error instanceof Error ? error.message : String(error);
499
- process.stderr.write(`[FileTransport] Failed to clean old files: ${errorMessage}
500
- `);
501
- }
502
- }
503
- /**
504
- * 날짜별 로그 파일명 생성
505
- */
506
- getLogFilename(date) {
507
- const year = date.getFullYear();
508
- const month = String(date.getMonth() + 1).padStart(2, "0");
509
- const day = String(date.getDate()).padStart(2, "0");
510
- return `${year}-${month}-${day}.log`;
511
- }
512
- async close() {
513
- await this.closeStream();
514
- }
515
- };
516
- function isFileLoggingEnabled() {
517
- return process.env.LOGGER_FILE_ENABLED === "true";
518
- }
519
- function getDefaultLogLevel() {
520
- const isProduction = process.env.NODE_ENV === "production";
521
- const isDevelopment = process.env.NODE_ENV === "development";
522
- if (isDevelopment) {
523
- return "debug";
524
- }
525
- if (isProduction) {
526
- return "info";
527
- }
528
- return "warn";
529
- }
530
- function getConsoleConfig() {
531
- const isProduction = process.env.NODE_ENV === "production";
532
- return {
533
- level: "debug",
534
- enabled: true,
535
- colorize: !isProduction
536
- // Dev: colored output, Production: plain text
537
- };
538
- }
539
- function getFileConfig() {
540
- const isProduction = process.env.NODE_ENV === "production";
541
- return {
542
- level: "info",
543
- enabled: isProduction,
544
- // File logging in production only
545
- logDir: process.env.LOG_DIR || "./logs",
546
- maxFileSize: 10 * 1024 * 1024,
547
- // 10MB
548
- maxFiles: 10
549
- };
550
- }
551
- function validateDirectoryWritable(dirPath) {
552
- if (!existsSync(dirPath)) {
553
- try {
554
- mkdirSync(dirPath, { recursive: true });
555
- } catch (error) {
556
- const errorMessage = error instanceof Error ? error.message : String(error);
557
- throw new Error(`Failed to create log directory "${dirPath}": ${errorMessage}`);
558
- }
559
- }
560
- try {
561
- accessSync(dirPath, constants.W_OK);
562
- } catch {
563
- throw new Error(`Log directory "${dirPath}" is not writable. Please check permissions.`);
564
- }
565
- const testFile = join(dirPath, ".logger-write-test");
566
- try {
567
- writeFileSync(testFile, "test", "utf-8");
568
- unlinkSync(testFile);
569
- } catch (error) {
570
- const errorMessage = error instanceof Error ? error.message : String(error);
571
- throw new Error(`Cannot write to log directory "${dirPath}": ${errorMessage}`);
572
- }
573
- }
574
- function validateFileConfig() {
575
- if (!isFileLoggingEnabled()) {
576
- return;
577
- }
578
- const logDir = process.env.LOG_DIR;
579
- if (!logDir) {
580
- throw new Error(
581
- "LOG_DIR environment variable is required when LOGGER_FILE_ENABLED=true. Example: LOG_DIR=/var/log/myapp"
582
- );
583
- }
584
- validateDirectoryWritable(logDir);
585
- }
586
- function validateSlackConfig() {
587
- const webhookUrl = process.env.SLACK_WEBHOOK_URL;
588
- if (!webhookUrl) {
589
- return;
590
- }
591
- if (!webhookUrl.startsWith("https://hooks.slack.com/")) {
592
- throw new Error(
593
- `Invalid SLACK_WEBHOOK_URL: "${webhookUrl}". Slack webhook URLs must start with "https://hooks.slack.com/"`
594
- );
595
- }
596
- }
597
- function validateEmailConfig() {
598
- const smtpHost = process.env.SMTP_HOST;
599
- const smtpPort = process.env.SMTP_PORT;
600
- const emailFrom = process.env.EMAIL_FROM;
601
- const emailTo = process.env.EMAIL_TO;
602
- const hasAnyEmailConfig = smtpHost || smtpPort || emailFrom || emailTo;
603
- if (!hasAnyEmailConfig) {
604
- return;
605
- }
606
- const missingFields = [];
607
- if (!smtpHost) missingFields.push("SMTP_HOST");
608
- if (!smtpPort) missingFields.push("SMTP_PORT");
609
- if (!emailFrom) missingFields.push("EMAIL_FROM");
610
- if (!emailTo) missingFields.push("EMAIL_TO");
611
- if (missingFields.length > 0) {
612
- throw new Error(
613
- `Email transport configuration incomplete. Missing: ${missingFields.join(", ")}. Either set all required fields or remove all email configuration.`
614
- );
615
- }
616
- const port = parseInt(smtpPort, 10);
617
- if (isNaN(port) || port < 1 || port > 65535) {
618
- throw new Error(
619
- `Invalid SMTP_PORT: "${smtpPort}". Must be a number between 1 and 65535.`
620
- );
621
- }
622
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
623
- if (!emailRegex.test(emailFrom)) {
624
- throw new Error(`Invalid EMAIL_FROM format: "${emailFrom}"`);
625
- }
626
- const recipients = emailTo.split(",").map((e) => e.trim());
627
- for (const email of recipients) {
628
- if (!emailRegex.test(email)) {
629
- throw new Error(`Invalid email address in EMAIL_TO: "${email}"`);
630
- }
631
- }
632
- }
633
- function validateEnvironment() {
634
- const nodeEnv = process.env.NODE_ENV;
635
- if (!nodeEnv) {
636
- process.stderr.write(
637
- "[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\n"
638
- );
639
- }
640
- }
641
- function validateConfig() {
642
- try {
643
- validateEnvironment();
644
- validateFileConfig();
645
- validateSlackConfig();
646
- validateEmailConfig();
647
- } catch (error) {
648
- if (error instanceof Error) {
649
- throw new Error(`[Logger] Configuration validation failed: ${error.message}`);
650
- }
651
- throw error;
652
- }
653
- }
654
-
655
- // src/logger/factory.ts
656
- function initializeTransports() {
657
- const transports = [];
658
- const consoleConfig = getConsoleConfig();
659
- transports.push(new ConsoleTransport(consoleConfig));
660
- const fileConfig = getFileConfig();
661
- if (fileConfig.enabled) {
662
- transports.push(new FileTransport(fileConfig));
663
- }
664
- return transports;
665
- }
666
- function initializeLogger() {
667
- validateConfig();
668
- return new Logger({
669
- level: getDefaultLogLevel(),
670
- transports: initializeTransports()
671
- });
672
- }
673
- var logger = initializeLogger();
674
-
675
- // src/middleware/error-handler.ts
676
- var errorLogger = logger.child("error-handler");
677
19
  function ErrorHandler(options = {}) {
678
20
  const {
679
- includeStack = process.env.NODE_ENV !== "production",
21
+ includeStack = env.NODE_ENV !== "production",
680
22
  enableLogging = true
681
23
  } = options;
682
24
  return (err, c) => {
25
+ if (isSerializableError(err)) {
26
+ const { statusCode: statusCode2 } = err;
27
+ if (enableLogging) {
28
+ logError(err, {
29
+ type: err.constructor.name,
30
+ message: err.message,
31
+ statusCode: statusCode2,
32
+ path: c.req.path,
33
+ method: c.req.method
34
+ }, includeStack);
35
+ }
36
+ const serialized = err.toJSON();
37
+ if (includeStack && err.stack) {
38
+ serialized.stack = err.stack;
39
+ }
40
+ return c.json(serialized, statusCode2);
41
+ }
683
42
  const errorWithCode = err;
684
43
  const statusCode = errorWithCode.statusCode || 500;
685
- const errorType = err.name || "Error";
686
44
  if (enableLogging) {
687
- const logLevel = statusCode >= 500 ? "error" : "warn";
688
- const logData = {
689
- type: errorType,
45
+ logError(err, {
46
+ type: err.name || "Error",
690
47
  message: err.message,
691
48
  statusCode,
692
49
  path: c.req.path,
693
50
  method: c.req.method
694
- };
695
- if (errorWithCode.details) {
696
- logData.details = errorWithCode.details;
697
- }
698
- if (statusCode >= 500 && includeStack) {
699
- logData.stack = err.stack;
700
- }
701
- errorLogger[logLevel]("Error occurred", logData);
51
+ }, includeStack);
702
52
  }
703
53
  const response = {
704
- success: false,
705
- error: {
706
- message: err.message || "Internal Server Error",
707
- type: errorType,
708
- statusCode
709
- }
54
+ __type: "Error",
55
+ message: err.message || "Internal Server Error"
710
56
  };
711
- if (errorWithCode.details) {
712
- response.error.details = errorWithCode.details;
713
- }
714
- if (includeStack) {
715
- response.error.stack = err.stack;
57
+ if (includeStack && err.stack) {
58
+ response.stack = err.stack;
716
59
  }
717
60
  return c.json(response, statusCode);
718
61
  };
@@ -727,7 +70,7 @@ function generateRequestId() {
727
70
  const randomPart = randomBytes(6).toString("hex");
728
71
  return `req_${timestamp}_${randomPart}`;
729
72
  }
730
- function maskSensitiveData2(obj, sensitiveFields, seen = /* @__PURE__ */ new WeakSet()) {
73
+ function maskSensitiveData(obj, sensitiveFields, seen = /* @__PURE__ */ new WeakSet()) {
731
74
  if (!obj || typeof obj !== "object") return obj;
732
75
  if (seen.has(obj)) return "[Circular]";
733
76
  seen.add(obj);
@@ -738,24 +81,28 @@ function maskSensitiveData2(obj, sensitiveFields, seen = /* @__PURE__ */ new Wea
738
81
  if (lowerFields.some((field) => lowerKey.includes(field))) {
739
82
  masked[key] = "***MASKED***";
740
83
  } else if (typeof masked[key] === "object" && masked[key] !== null) {
741
- masked[key] = maskSensitiveData2(masked[key], sensitiveFields, seen);
84
+ masked[key] = maskSensitiveData(masked[key], sensitiveFields, seen);
742
85
  }
743
86
  }
744
87
  return masked;
745
88
  }
746
89
  function RequestLogger(config) {
747
90
  const cfg = { ...DEFAULT_CONFIG, ...config };
748
- const apiLogger = logger.child("api");
91
+ const apiLogger = logger.child("@spfn/core:api");
749
92
  return async (c, next) => {
750
93
  const path = new URL(c.req.url).pathname;
751
- if (cfg.excludePaths.includes(path)) {
94
+ const isExcluded = cfg.excludePaths.some(
95
+ (excludePath) => path === excludePath || path.startsWith(excludePath + "/")
96
+ );
97
+ if (isExcluded) {
752
98
  return next();
753
99
  }
754
100
  const requestId = generateRequestId();
755
101
  c.set("requestId", requestId);
756
102
  const method = c.req.method;
757
103
  const userAgent = c.req.header("user-agent");
758
- const ip = c.req.header("x-forwarded-for") || c.req.header("x-real-ip") || "unknown";
104
+ const forwardedFor = c.req.header("x-forwarded-for");
105
+ const ip = forwardedFor?.split(",")[0]?.trim() || c.req.header("x-real-ip") || "unknown";
759
106
  const startTime = Date.now();
760
107
  apiLogger.info("Request received", {
761
108
  requestId,
@@ -781,14 +128,13 @@ function RequestLogger(config) {
781
128
  }
782
129
  if (status >= 400) {
783
130
  try {
784
- const responseBody = await c.res.clone().json();
785
- logData.response = responseBody;
131
+ logData.response = await c.res.clone().json();
786
132
  } catch {
787
133
  }
788
134
  if (["POST", "PUT", "PATCH"].includes(method)) {
789
135
  try {
790
136
  const requestBody = await c.req.json();
791
- logData.request = maskSensitiveData2(requestBody, cfg.sensitiveFields);
137
+ logData.request = maskSensitiveData(requestBody, cfg.sensitiveFields);
792
138
  } catch {
793
139
  }
794
140
  }
@@ -808,6 +154,6 @@ function RequestLogger(config) {
808
154
  };
809
155
  }
810
156
 
811
- export { ErrorHandler, RequestLogger, maskSensitiveData2 as maskSensitiveData };
157
+ export { ErrorHandler, RequestLogger, maskSensitiveData };
812
158
  //# sourceMappingURL=index.js.map
813
159
  //# sourceMappingURL=index.js.map