@spfn/core 0.1.0-alpha.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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +580 -0
  3. package/dist/auto-loader-C44TcLmM.d.ts +125 -0
  4. package/dist/bind-pssq1NRT.d.ts +34 -0
  5. package/dist/client/index.d.ts +174 -0
  6. package/dist/client/index.js +179 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/codegen/index.d.ts +126 -0
  9. package/dist/codegen/index.js +970 -0
  10. package/dist/codegen/index.js.map +1 -0
  11. package/dist/db/index.d.ts +83 -0
  12. package/dist/db/index.js +2099 -0
  13. package/dist/db/index.js.map +1 -0
  14. package/dist/index.d.ts +379 -0
  15. package/dist/index.js +13042 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/postgres-errors-CY_Es8EJ.d.ts +1703 -0
  18. package/dist/route/index.d.ts +72 -0
  19. package/dist/route/index.js +442 -0
  20. package/dist/route/index.js.map +1 -0
  21. package/dist/scripts/index.d.ts +24 -0
  22. package/dist/scripts/index.js +1157 -0
  23. package/dist/scripts/index.js.map +1 -0
  24. package/dist/scripts/templates/api-index.template.txt +10 -0
  25. package/dist/scripts/templates/api-tag.template.txt +11 -0
  26. package/dist/scripts/templates/contract.template.txt +87 -0
  27. package/dist/scripts/templates/entity-type.template.txt +31 -0
  28. package/dist/scripts/templates/entity.template.txt +19 -0
  29. package/dist/scripts/templates/index.template.txt +10 -0
  30. package/dist/scripts/templates/repository.template.txt +37 -0
  31. package/dist/scripts/templates/routes-id.template.txt +59 -0
  32. package/dist/scripts/templates/routes-index.template.txt +44 -0
  33. package/dist/server/index.d.ts +303 -0
  34. package/dist/server/index.js +12923 -0
  35. package/dist/server/index.js.map +1 -0
  36. package/dist/types-SlzTr8ZO.d.ts +143 -0
  37. package/package.json +119 -0
@@ -0,0 +1,970 @@
1
+ import { mkdir, writeFile, readdir, stat } from 'fs/promises';
2
+ import { join, dirname } from 'path';
3
+ import * as ts from 'typescript';
4
+ import { readFileSync, existsSync, mkdirSync, createWriteStream } from 'fs';
5
+ import pino from 'pino';
6
+
7
+ // src/codegen/contract-scanner.ts
8
+ async function scanContracts(routesDir) {
9
+ const contractFiles = await scanContractFiles(routesDir);
10
+ const mappings = [];
11
+ for (let i = 0; i < contractFiles.length; i++) {
12
+ const filePath = contractFiles[i];
13
+ const exports = extractContractExports(filePath);
14
+ const basePath = getBasePathFromFile(filePath, routesDir);
15
+ for (let j = 0; j < exports.length; j++) {
16
+ const contractExport = exports[j];
17
+ const fullPath = combinePaths(basePath, contractExport.path);
18
+ mappings.push({
19
+ method: contractExport.method,
20
+ path: fullPath,
21
+ contractName: contractExport.name,
22
+ contractImportPath: getImportPathFromRoutes(filePath, routesDir),
23
+ routeFile: "",
24
+ // Not needed anymore
25
+ contractFile: filePath
26
+ });
27
+ }
28
+ }
29
+ return mappings;
30
+ }
31
+ async function scanContractFiles(dir, files = []) {
32
+ try {
33
+ const entries = await readdir(dir);
34
+ for (let i = 0; i < entries.length; i++) {
35
+ const entry = entries[i];
36
+ const fullPath = join(dir, entry);
37
+ const fileStat = await stat(fullPath);
38
+ if (fileStat.isDirectory()) {
39
+ await scanContractFiles(fullPath, files);
40
+ } else if (entry === "contract.ts") {
41
+ files.push(fullPath);
42
+ }
43
+ }
44
+ } catch (error) {
45
+ }
46
+ return files;
47
+ }
48
+ function extractContractExports(filePath) {
49
+ const sourceCode = readFileSync(filePath, "utf-8");
50
+ const sourceFile = ts.createSourceFile(
51
+ filePath,
52
+ sourceCode,
53
+ ts.ScriptTarget.Latest,
54
+ true
55
+ );
56
+ const exports = [];
57
+ function visit(node) {
58
+ if (ts.isVariableStatement(node)) {
59
+ const hasExport = node.modifiers?.some(
60
+ (m) => m.kind === ts.SyntaxKind.ExportKeyword
61
+ );
62
+ if (hasExport && node.declarationList.declarations.length > 0) {
63
+ const declaration = node.declarationList.declarations[0];
64
+ if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
65
+ const name = declaration.name.text;
66
+ if (isContractName(name)) {
67
+ const contractData = extractContractData(declaration.initializer);
68
+ if (contractData.method && contractData.path) {
69
+ exports.push({
70
+ name,
71
+ method: contractData.method,
72
+ path: contractData.path
73
+ });
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ts.forEachChild(node, visit);
80
+ }
81
+ visit(sourceFile);
82
+ return exports;
83
+ }
84
+ function extractContractData(objectLiteral) {
85
+ const result = {};
86
+ for (let i = 0; i < objectLiteral.properties.length; i++) {
87
+ const prop = objectLiteral.properties[i];
88
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
89
+ const propName = prop.name.text;
90
+ if (propName === "method") {
91
+ let value;
92
+ if (ts.isStringLiteral(prop.initializer)) {
93
+ value = prop.initializer.text;
94
+ } else if (ts.isAsExpression(prop.initializer) && ts.isStringLiteral(prop.initializer.expression)) {
95
+ value = prop.initializer.expression.text;
96
+ }
97
+ if (value) result.method = value;
98
+ } else if (propName === "path") {
99
+ let value;
100
+ if (ts.isStringLiteral(prop.initializer)) {
101
+ value = prop.initializer.text;
102
+ } else if (ts.isAsExpression(prop.initializer) && ts.isStringLiteral(prop.initializer.expression)) {
103
+ value = prop.initializer.expression.text;
104
+ }
105
+ if (value) result.path = value;
106
+ }
107
+ }
108
+ }
109
+ return result;
110
+ }
111
+ function isContractName(name) {
112
+ return name.indexOf("Contract") !== -1 || name.indexOf("contract") !== -1 || name.endsWith("Schema") || name.endsWith("schema");
113
+ }
114
+ function getBasePathFromFile(filePath, routesDir) {
115
+ let relativePath = filePath.replace(routesDir, "");
116
+ if (relativePath.startsWith("/")) {
117
+ relativePath = relativePath.slice(1);
118
+ }
119
+ relativePath = relativePath.replace("/contract.ts", "");
120
+ if (relativePath === "index" || relativePath === "") {
121
+ return "/";
122
+ }
123
+ const segments = relativePath.split("/");
124
+ const transformed = [];
125
+ for (let i = 0; i < segments.length; i++) {
126
+ const seg = segments[i];
127
+ if (seg === "index") {
128
+ continue;
129
+ }
130
+ if (seg.startsWith("[") && seg.endsWith("]")) {
131
+ transformed.push(":" + seg.slice(1, -1));
132
+ } else {
133
+ transformed.push(seg);
134
+ }
135
+ }
136
+ if (transformed.length === 0) {
137
+ return "/";
138
+ }
139
+ return "/" + transformed.join("/");
140
+ }
141
+ function combinePaths(basePath, contractPath) {
142
+ basePath = basePath || "/";
143
+ contractPath = contractPath || "/";
144
+ if (basePath.endsWith("/") && basePath !== "/") {
145
+ basePath = basePath.slice(0, -1);
146
+ }
147
+ if (contractPath.startsWith("/") && contractPath !== "/") {
148
+ if (basePath === "/") {
149
+ return contractPath;
150
+ }
151
+ return basePath + contractPath;
152
+ }
153
+ if (contractPath === "/") {
154
+ return basePath;
155
+ }
156
+ return basePath + "/" + contractPath;
157
+ }
158
+ function getImportPathFromRoutes(filePath, routesDir) {
159
+ let relativePath = filePath.replace(routesDir, "");
160
+ if (relativePath.startsWith("/")) {
161
+ relativePath = relativePath.slice(1);
162
+ }
163
+ if (relativePath.endsWith(".ts")) {
164
+ relativePath = relativePath.slice(0, -3);
165
+ }
166
+ return "@/server/routes/" + relativePath;
167
+ }
168
+
169
+ // src/codegen/route-scanner.ts
170
+ function groupByResource(mappings) {
171
+ const grouped = {};
172
+ for (let i = 0; i < mappings.length; i++) {
173
+ const mapping = mappings[i];
174
+ const resource = extractResourceName(mapping.path);
175
+ if (!grouped[resource]) {
176
+ grouped[resource] = [];
177
+ }
178
+ grouped[resource].push(mapping);
179
+ }
180
+ return grouped;
181
+ }
182
+ function extractResourceName(path) {
183
+ const segments = path.slice(1).split("/").filter((s) => s && s !== "*");
184
+ const staticSegments = [];
185
+ for (let i = 0; i < segments.length; i++) {
186
+ const seg = segments[i];
187
+ if (!seg.startsWith(":")) {
188
+ staticSegments.push(seg);
189
+ }
190
+ }
191
+ if (staticSegments.length === 0) {
192
+ return "root";
193
+ }
194
+ if (staticSegments.length === 1) {
195
+ return staticSegments[0];
196
+ }
197
+ const result = [staticSegments[0]];
198
+ for (let i = 1; i < staticSegments.length; i++) {
199
+ const seg = staticSegments[i];
200
+ result.push(seg.charAt(0).toUpperCase() + seg.slice(1));
201
+ }
202
+ return result.join("");
203
+ }
204
+ async function generateClient(mappings, options) {
205
+ const startTime = Date.now();
206
+ const grouped = groupByResource(mappings);
207
+ const resourceNames = Object.keys(grouped);
208
+ const code = generateClientCode(mappings, grouped, options);
209
+ await mkdir(dirname(options.outputPath), { recursive: true });
210
+ await writeFile(options.outputPath, code, "utf-8");
211
+ const stats = {
212
+ routesScanned: mappings.length,
213
+ contractsFound: mappings.length,
214
+ contractFiles: countUniqueContractFiles(mappings),
215
+ resourcesGenerated: resourceNames.length,
216
+ methodsGenerated: mappings.length,
217
+ duration: Date.now() - startTime
218
+ };
219
+ return stats;
220
+ }
221
+ function generateClientCode(mappings, grouped, options) {
222
+ let code = "";
223
+ code += generateHeader();
224
+ code += generateImports(mappings, options);
225
+ code += generateApiObject(grouped, options);
226
+ code += generateFooter();
227
+ return code;
228
+ }
229
+ function generateHeader() {
230
+ return `/**
231
+ * Auto-generated API Client
232
+ *
233
+ * Generated by @spfn/core codegen
234
+ * DO NOT EDIT MANUALLY
235
+ *
236
+ * @generated ${(/* @__PURE__ */ new Date()).toISOString()}
237
+ */
238
+
239
+ `;
240
+ }
241
+ function generateImports(mappings, options) {
242
+ let code = "";
243
+ code += `import { client } from '@spfn/core/client';
244
+ `;
245
+ if (options.includeTypes !== false) {
246
+ code += `import type { InferContract } from '@spfn/core';
247
+ `;
248
+ }
249
+ code += `
250
+ `;
251
+ const importGroups = groupContractsByImportPath(mappings);
252
+ const importPaths = Object.keys(importGroups);
253
+ for (let i = 0; i < importPaths.length; i++) {
254
+ const importPath = importPaths[i];
255
+ const contracts = importGroups[importPath];
256
+ code += `import { ${contracts.join(", ")} } from '${importPath}';
257
+ `;
258
+ }
259
+ code += `
260
+ `;
261
+ return code;
262
+ }
263
+ function groupContractsByImportPath(mappings) {
264
+ const groups = {};
265
+ for (let i = 0; i < mappings.length; i++) {
266
+ const mapping = mappings[i];
267
+ const path = mapping.contractImportPath;
268
+ if (!groups[path]) {
269
+ groups[path] = /* @__PURE__ */ new Set();
270
+ }
271
+ groups[path].add(mapping.contractName);
272
+ }
273
+ const result = {};
274
+ const keys = Object.keys(groups);
275
+ for (let i = 0; i < keys.length; i++) {
276
+ const key = keys[i];
277
+ result[key] = Array.from(groups[key]);
278
+ }
279
+ return result;
280
+ }
281
+ function generateApiObject(grouped, options) {
282
+ let code = "";
283
+ code += `/**
284
+ * Type-safe API client
285
+ */
286
+ export const api = {
287
+ `;
288
+ const resourceNames = Object.keys(grouped);
289
+ for (let i = 0; i < resourceNames.length; i++) {
290
+ const resourceName = resourceNames[i];
291
+ const routes = grouped[resourceName];
292
+ code += ` ${resourceName}: {
293
+ `;
294
+ for (let j = 0; j < routes.length; j++) {
295
+ const route = routes[j];
296
+ code += generateMethodCode(route, options);
297
+ }
298
+ code += ` }`;
299
+ if (i < resourceNames.length - 1) {
300
+ code += `,`;
301
+ }
302
+ code += `
303
+ `;
304
+ }
305
+ code += `} as const;
306
+
307
+ `;
308
+ return code;
309
+ }
310
+ function generateMethodCode(mapping, options) {
311
+ const methodName = generateMethodName(mapping);
312
+ const contractType = `typeof ${mapping.contractName}`;
313
+ const hasParams = mapping.path.includes(":");
314
+ const hasBody = ["POST", "PUT", "PATCH"].indexOf(mapping.method) !== -1;
315
+ let code = "";
316
+ if (options.includeJsDoc !== false) {
317
+ code += ` /**
318
+ `;
319
+ code += ` * ${mapping.method} ${mapping.path}
320
+ `;
321
+ code += ` */
322
+ `;
323
+ }
324
+ code += ` ${methodName}: (`;
325
+ const params = [];
326
+ if (hasParams) {
327
+ params.push(`params: InferContract<${contractType}>['params']`);
328
+ }
329
+ if (hasBody) {
330
+ params.push(`body: InferContract<${contractType}>['body']`);
331
+ }
332
+ if (params.length > 0) {
333
+ code += `options: { ${params.join(", ")} }`;
334
+ }
335
+ code += `) => `;
336
+ code += `client.call('${mapping.path}', ${mapping.contractName}, `;
337
+ if (params.length > 0) {
338
+ code += `options`;
339
+ } else {
340
+ code += `{}`;
341
+ }
342
+ code += `),
343
+ `;
344
+ return code;
345
+ }
346
+ function generateMethodName(mapping) {
347
+ const method = mapping.method.toLowerCase();
348
+ if (mapping.path === "/" || mapping.path.match(/^\/[\w-]+$/)) {
349
+ if (method === "get") {
350
+ return "list";
351
+ }
352
+ if (method === "post") {
353
+ return "create";
354
+ }
355
+ }
356
+ if (mapping.path.includes(":")) {
357
+ if (method === "get") {
358
+ return "getById";
359
+ }
360
+ if (method === "put" || method === "patch") {
361
+ return "update";
362
+ }
363
+ if (method === "delete") {
364
+ return "delete";
365
+ }
366
+ }
367
+ return method;
368
+ }
369
+ function generateFooter() {
370
+ return `/**
371
+ * Export client instance for advanced usage
372
+ *
373
+ * Use this to add interceptors or customize the client:
374
+ *
375
+ * @example
376
+ * \`\`\`ts
377
+ * import { client } from './api';
378
+ * import { createAuthInterceptor } from '@spfn/auth/nextjs';
379
+ * import { NextJSCookieProvider } from '@spfn/auth/nextjs';
380
+ *
381
+ * client.use(createAuthInterceptor({
382
+ * cookieProvider: new NextJSCookieProvider(),
383
+ * encryptionKey: process.env.ENCRYPTION_KEY!
384
+ * }));
385
+ * \`\`\`
386
+ */
387
+ export { client };
388
+ `;
389
+ }
390
+ function countUniqueContractFiles(mappings) {
391
+ const files = /* @__PURE__ */ new Set();
392
+ for (let i = 0; i < mappings.length; i++) {
393
+ if (mappings[i].contractFile) {
394
+ files.add(mappings[i].contractFile);
395
+ }
396
+ }
397
+ return files.size;
398
+ }
399
+ var PinoAdapter = class _PinoAdapter {
400
+ logger;
401
+ constructor(config) {
402
+ const isProduction = process.env.NODE_ENV === "production";
403
+ const isDevelopment = process.env.NODE_ENV === "development";
404
+ const fileLoggingEnabled = process.env.LOGGER_FILE_ENABLED === "true";
405
+ const targets = [];
406
+ if (!isProduction && isDevelopment) {
407
+ targets.push({
408
+ target: "pino-pretty",
409
+ level: "debug",
410
+ options: {
411
+ colorize: true,
412
+ translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l",
413
+ ignore: "pid,hostname"
414
+ }
415
+ });
416
+ }
417
+ if (fileLoggingEnabled && isProduction) {
418
+ const logDir = process.env.LOG_DIR || "./logs";
419
+ const maxFileSize = process.env.LOG_MAX_FILE_SIZE || "10M";
420
+ const maxFiles = parseInt(process.env.LOG_MAX_FILES || "10", 10);
421
+ targets.push({
422
+ target: "pino-roll",
423
+ level: "info",
424
+ options: {
425
+ file: `${logDir}/app.log`,
426
+ frequency: "daily",
427
+ size: maxFileSize,
428
+ limit: { count: maxFiles },
429
+ mkdir: true
430
+ }
431
+ });
432
+ }
433
+ this.logger = pino({
434
+ level: config.level,
435
+ // Transport 설정 (targets가 있으면 사용, 없으면 기본 stdout)
436
+ transport: targets.length > 0 ? { targets } : void 0,
437
+ // 기본 필드
438
+ base: config.module ? { module: config.module } : void 0
439
+ });
440
+ }
441
+ child(module) {
442
+ const childLogger = new _PinoAdapter({ level: this.logger.level, module });
443
+ childLogger.logger = this.logger.child({ module });
444
+ return childLogger;
445
+ }
446
+ debug(message, context) {
447
+ this.logger.debug(context || {}, message);
448
+ }
449
+ info(message, context) {
450
+ this.logger.info(context || {}, message);
451
+ }
452
+ warn(message, errorOrContext, context) {
453
+ if (errorOrContext instanceof Error) {
454
+ this.logger.warn({ err: errorOrContext, ...context }, message);
455
+ } else {
456
+ this.logger.warn(errorOrContext || {}, message);
457
+ }
458
+ }
459
+ error(message, errorOrContext, context) {
460
+ if (errorOrContext instanceof Error) {
461
+ this.logger.error({ err: errorOrContext, ...context }, message);
462
+ } else {
463
+ this.logger.error(errorOrContext || {}, message);
464
+ }
465
+ }
466
+ fatal(message, errorOrContext, context) {
467
+ if (errorOrContext instanceof Error) {
468
+ this.logger.fatal({ err: errorOrContext, ...context }, message);
469
+ } else {
470
+ this.logger.fatal(errorOrContext || {}, message);
471
+ }
472
+ }
473
+ async close() {
474
+ }
475
+ };
476
+
477
+ // src/logger/logger.ts
478
+ var Logger = class _Logger {
479
+ config;
480
+ module;
481
+ constructor(config) {
482
+ this.config = config;
483
+ this.module = config.module;
484
+ }
485
+ /**
486
+ * Get current log level
487
+ */
488
+ get level() {
489
+ return this.config.level;
490
+ }
491
+ /**
492
+ * Create child logger (per module)
493
+ */
494
+ child(module) {
495
+ return new _Logger({
496
+ ...this.config,
497
+ module
498
+ });
499
+ }
500
+ /**
501
+ * Debug log
502
+ */
503
+ debug(message, context) {
504
+ this.log("debug", message, void 0, context);
505
+ }
506
+ /**
507
+ * Info log
508
+ */
509
+ info(message, context) {
510
+ this.log("info", message, void 0, context);
511
+ }
512
+ warn(message, errorOrContext, context) {
513
+ if (errorOrContext instanceof Error) {
514
+ this.log("warn", message, errorOrContext, context);
515
+ } else {
516
+ this.log("warn", message, void 0, errorOrContext);
517
+ }
518
+ }
519
+ error(message, errorOrContext, context) {
520
+ if (errorOrContext instanceof Error) {
521
+ this.log("error", message, errorOrContext, context);
522
+ } else {
523
+ this.log("error", message, void 0, errorOrContext);
524
+ }
525
+ }
526
+ fatal(message, errorOrContext, context) {
527
+ if (errorOrContext instanceof Error) {
528
+ this.log("fatal", message, errorOrContext, context);
529
+ } else {
530
+ this.log("fatal", message, void 0, errorOrContext);
531
+ }
532
+ }
533
+ /**
534
+ * Log processing (internal)
535
+ */
536
+ log(level, message, error, context) {
537
+ const metadata = {
538
+ timestamp: /* @__PURE__ */ new Date(),
539
+ level,
540
+ message,
541
+ module: this.module,
542
+ error,
543
+ context
544
+ };
545
+ this.processTransports(metadata);
546
+ }
547
+ /**
548
+ * Process Transports
549
+ */
550
+ processTransports(metadata) {
551
+ const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
552
+ Promise.all(promises).catch((error) => {
553
+ const errorMessage = error instanceof Error ? error.message : String(error);
554
+ process.stderr.write(`[Logger] Transport error: ${errorMessage}
555
+ `);
556
+ });
557
+ }
558
+ /**
559
+ * Transport log (error-safe)
560
+ */
561
+ async safeTransportLog(transport, metadata) {
562
+ try {
563
+ await transport.log(metadata);
564
+ } catch (error) {
565
+ const errorMessage = error instanceof Error ? error.message : String(error);
566
+ process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
567
+ `);
568
+ }
569
+ }
570
+ /**
571
+ * Close all Transports
572
+ */
573
+ async close() {
574
+ const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
575
+ await Promise.all(closePromises);
576
+ }
577
+ };
578
+
579
+ // src/logger/types.ts
580
+ var LOG_LEVEL_PRIORITY = {
581
+ debug: 0,
582
+ info: 1,
583
+ warn: 2,
584
+ error: 3,
585
+ fatal: 4
586
+ };
587
+
588
+ // src/logger/formatters.ts
589
+ var COLORS = {
590
+ reset: "\x1B[0m",
591
+ bright: "\x1B[1m",
592
+ dim: "\x1B[2m",
593
+ // 로그 레벨 컬러
594
+ debug: "\x1B[36m",
595
+ // cyan
596
+ info: "\x1B[32m",
597
+ // green
598
+ warn: "\x1B[33m",
599
+ // yellow
600
+ error: "\x1B[31m",
601
+ // red
602
+ fatal: "\x1B[35m",
603
+ // magenta
604
+ // 추가 컬러
605
+ gray: "\x1B[90m"
606
+ };
607
+ function colorizeLevel(level) {
608
+ const color = COLORS[level];
609
+ const levelStr = level.toUpperCase().padEnd(5);
610
+ return `${color}${levelStr}${COLORS.reset}`;
611
+ }
612
+ function formatTimestamp(date) {
613
+ return date.toISOString();
614
+ }
615
+ function formatTimestampHuman(date) {
616
+ const year = date.getFullYear();
617
+ const month = String(date.getMonth() + 1).padStart(2, "0");
618
+ const day = String(date.getDate()).padStart(2, "0");
619
+ const hours = String(date.getHours()).padStart(2, "0");
620
+ const minutes = String(date.getMinutes()).padStart(2, "0");
621
+ const seconds = String(date.getSeconds()).padStart(2, "0");
622
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
623
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
624
+ }
625
+ function formatError(error) {
626
+ const lines = [];
627
+ lines.push(`${error.name}: ${error.message}`);
628
+ if (error.stack) {
629
+ const stackLines = error.stack.split("\n").slice(1);
630
+ lines.push(...stackLines);
631
+ }
632
+ return lines.join("\n");
633
+ }
634
+ function formatContext(context) {
635
+ try {
636
+ return JSON.stringify(context, null, 2);
637
+ } catch (error) {
638
+ return "[Context serialization failed]";
639
+ }
640
+ }
641
+ function formatConsole(metadata, colorize = true) {
642
+ const parts = [];
643
+ const timestamp = formatTimestampHuman(metadata.timestamp);
644
+ if (colorize) {
645
+ parts.push(`${COLORS.gray}${timestamp}${COLORS.reset}`);
646
+ } else {
647
+ parts.push(timestamp);
648
+ }
649
+ if (colorize) {
650
+ parts.push(colorizeLevel(metadata.level));
651
+ } else {
652
+ parts.push(metadata.level.toUpperCase().padEnd(5));
653
+ }
654
+ if (metadata.module) {
655
+ if (colorize) {
656
+ parts.push(`${COLORS.dim}[${metadata.module}]${COLORS.reset}`);
657
+ } else {
658
+ parts.push(`[${metadata.module}]`);
659
+ }
660
+ }
661
+ parts.push(metadata.message);
662
+ let output = parts.join(" ");
663
+ if (metadata.context && Object.keys(metadata.context).length > 0) {
664
+ output += "\n" + formatContext(metadata.context);
665
+ }
666
+ if (metadata.error) {
667
+ output += "\n" + formatError(metadata.error);
668
+ }
669
+ return output;
670
+ }
671
+ function formatJSON(metadata) {
672
+ const obj = {
673
+ timestamp: formatTimestamp(metadata.timestamp),
674
+ level: metadata.level,
675
+ message: metadata.message
676
+ };
677
+ if (metadata.module) {
678
+ obj.module = metadata.module;
679
+ }
680
+ if (metadata.context) {
681
+ obj.context = metadata.context;
682
+ }
683
+ if (metadata.error) {
684
+ obj.error = {
685
+ name: metadata.error.name,
686
+ message: metadata.error.message,
687
+ stack: metadata.error.stack
688
+ };
689
+ }
690
+ return JSON.stringify(obj);
691
+ }
692
+
693
+ // src/logger/transports/console.ts
694
+ var ConsoleTransport = class {
695
+ name = "console";
696
+ level;
697
+ enabled;
698
+ colorize;
699
+ constructor(config) {
700
+ this.level = config.level;
701
+ this.enabled = config.enabled;
702
+ this.colorize = config.colorize ?? true;
703
+ }
704
+ async log(metadata) {
705
+ if (!this.enabled) {
706
+ return;
707
+ }
708
+ if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
709
+ return;
710
+ }
711
+ const message = formatConsole(metadata, this.colorize);
712
+ if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
713
+ console.error(message);
714
+ } else {
715
+ console.log(message);
716
+ }
717
+ }
718
+ };
719
+ var FileTransport = class {
720
+ name = "file";
721
+ level;
722
+ enabled;
723
+ logDir;
724
+ currentStream = null;
725
+ currentFilename = null;
726
+ constructor(config) {
727
+ this.level = config.level;
728
+ this.enabled = config.enabled;
729
+ this.logDir = config.logDir;
730
+ if (!existsSync(this.logDir)) {
731
+ mkdirSync(this.logDir, { recursive: true });
732
+ }
733
+ }
734
+ async log(metadata) {
735
+ if (!this.enabled) {
736
+ return;
737
+ }
738
+ if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
739
+ return;
740
+ }
741
+ const message = formatJSON(metadata);
742
+ const filename = this.getLogFilename(metadata.timestamp);
743
+ if (this.currentFilename !== filename) {
744
+ await this.rotateStream(filename);
745
+ }
746
+ if (this.currentStream) {
747
+ return new Promise((resolve, reject) => {
748
+ this.currentStream.write(message + "\n", "utf-8", (error) => {
749
+ if (error) {
750
+ process.stderr.write(`[FileTransport] Failed to write log: ${error.message}
751
+ `);
752
+ reject(error);
753
+ } else {
754
+ resolve();
755
+ }
756
+ });
757
+ });
758
+ }
759
+ }
760
+ /**
761
+ * 스트림 교체 (날짜 변경 시)
762
+ */
763
+ async rotateStream(filename) {
764
+ if (this.currentStream) {
765
+ await this.closeStream();
766
+ }
767
+ const filepath = join(this.logDir, filename);
768
+ this.currentStream = createWriteStream(filepath, {
769
+ flags: "a",
770
+ // append mode
771
+ encoding: "utf-8"
772
+ });
773
+ this.currentFilename = filename;
774
+ this.currentStream.on("error", (error) => {
775
+ process.stderr.write(`[FileTransport] Stream error: ${error.message}
776
+ `);
777
+ this.currentStream = null;
778
+ this.currentFilename = null;
779
+ });
780
+ }
781
+ /**
782
+ * 현재 스트림 닫기
783
+ */
784
+ async closeStream() {
785
+ if (!this.currentStream) {
786
+ return;
787
+ }
788
+ return new Promise((resolve, reject) => {
789
+ this.currentStream.end((error) => {
790
+ if (error) {
791
+ reject(error);
792
+ } else {
793
+ this.currentStream = null;
794
+ this.currentFilename = null;
795
+ resolve();
796
+ }
797
+ });
798
+ });
799
+ }
800
+ /**
801
+ * 날짜별 로그 파일명 생성
802
+ */
803
+ getLogFilename(date) {
804
+ const year = date.getFullYear();
805
+ const month = String(date.getMonth() + 1).padStart(2, "0");
806
+ const day = String(date.getDate()).padStart(2, "0");
807
+ return `${year}-${month}-${day}.log`;
808
+ }
809
+ async close() {
810
+ await this.closeStream();
811
+ }
812
+ };
813
+
814
+ // src/logger/config.ts
815
+ function getDefaultLogLevel() {
816
+ const isProduction = process.env.NODE_ENV === "production";
817
+ const isDevelopment = process.env.NODE_ENV === "development";
818
+ if (isDevelopment) {
819
+ return "debug";
820
+ }
821
+ if (isProduction) {
822
+ return "info";
823
+ }
824
+ return "warn";
825
+ }
826
+ function getConsoleConfig() {
827
+ const isProduction = process.env.NODE_ENV === "production";
828
+ return {
829
+ level: "debug",
830
+ enabled: true,
831
+ colorize: !isProduction
832
+ // Dev: colored output, Production: plain text
833
+ };
834
+ }
835
+ function getFileConfig() {
836
+ const isProduction = process.env.NODE_ENV === "production";
837
+ return {
838
+ level: "info",
839
+ enabled: isProduction,
840
+ // File logging in production only
841
+ logDir: process.env.LOG_DIR || "./logs",
842
+ maxFileSize: 10 * 1024 * 1024,
843
+ // 10MB
844
+ maxFiles: 10
845
+ };
846
+ }
847
+
848
+ // src/logger/adapters/custom.ts
849
+ function initializeTransports() {
850
+ const transports = [];
851
+ const consoleConfig = getConsoleConfig();
852
+ transports.push(new ConsoleTransport(consoleConfig));
853
+ const fileConfig = getFileConfig();
854
+ if (fileConfig.enabled) {
855
+ transports.push(new FileTransport(fileConfig));
856
+ }
857
+ return transports;
858
+ }
859
+ var CustomAdapter = class _CustomAdapter {
860
+ logger;
861
+ constructor(config) {
862
+ this.logger = new Logger({
863
+ level: config.level,
864
+ module: config.module,
865
+ transports: initializeTransports()
866
+ });
867
+ }
868
+ child(module) {
869
+ const adapter = new _CustomAdapter({ level: this.logger.level, module });
870
+ adapter.logger = this.logger.child(module);
871
+ return adapter;
872
+ }
873
+ debug(message, context) {
874
+ this.logger.debug(message, context);
875
+ }
876
+ info(message, context) {
877
+ this.logger.info(message, context);
878
+ }
879
+ warn(message, errorOrContext, context) {
880
+ if (errorOrContext instanceof Error) {
881
+ this.logger.warn(message, errorOrContext, context);
882
+ } else {
883
+ this.logger.warn(message, errorOrContext);
884
+ }
885
+ }
886
+ error(message, errorOrContext, context) {
887
+ if (errorOrContext instanceof Error) {
888
+ this.logger.error(message, errorOrContext, context);
889
+ } else {
890
+ this.logger.error(message, errorOrContext);
891
+ }
892
+ }
893
+ fatal(message, errorOrContext, context) {
894
+ if (errorOrContext instanceof Error) {
895
+ this.logger.fatal(message, errorOrContext, context);
896
+ } else {
897
+ this.logger.fatal(message, errorOrContext);
898
+ }
899
+ }
900
+ async close() {
901
+ await this.logger.close();
902
+ }
903
+ };
904
+
905
+ // src/logger/adapter-factory.ts
906
+ function createAdapter(type) {
907
+ const level = getDefaultLogLevel();
908
+ switch (type) {
909
+ case "pino":
910
+ return new PinoAdapter({ level });
911
+ case "custom":
912
+ return new CustomAdapter({ level });
913
+ default:
914
+ return new PinoAdapter({ level });
915
+ }
916
+ }
917
+ function getAdapterType() {
918
+ const adapterEnv = process.env.LOGGER_ADAPTER;
919
+ if (adapterEnv === "custom" || adapterEnv === "pino") {
920
+ return adapterEnv;
921
+ }
922
+ return "pino";
923
+ }
924
+ var logger = createAdapter(getAdapterType());
925
+
926
+ // src/codegen/watch-generate.ts
927
+ var codegenLogger = logger.child("codegen");
928
+ async function watchAndGenerate(options = {}) {
929
+ const cwd = process.cwd();
930
+ const routesDir = options.routesDir ?? join(cwd, "src", "server", "routes");
931
+ const outputPath = options.outputPath ?? join(cwd, "src", "lib", "api", "client.ts");
932
+ const debug = options.debug ?? false;
933
+ if (debug) {
934
+ codegenLogger.info("Contract Watcher Started", { routesDir, outputPath });
935
+ }
936
+ try {
937
+ const contracts = await scanContracts(routesDir);
938
+ if (contracts.length === 0) {
939
+ if (debug) {
940
+ codegenLogger.warn("No contracts found");
941
+ }
942
+ return;
943
+ }
944
+ const stats = await generateClient(contracts, {
945
+ outputPath,
946
+ includeTypes: true,
947
+ includeJsDoc: true
948
+ });
949
+ if (debug) {
950
+ codegenLogger.info("Client generated", {
951
+ endpoints: stats.methodsGenerated,
952
+ resources: stats.resourcesGenerated,
953
+ duration: stats.duration
954
+ });
955
+ }
956
+ } catch (error) {
957
+ codegenLogger.error(
958
+ "Generation failed",
959
+ error instanceof Error ? error : new Error(String(error))
960
+ );
961
+ throw error;
962
+ }
963
+ }
964
+ if (import.meta.url === `file://${process.argv[1]}`) {
965
+ watchAndGenerate({ debug: true });
966
+ }
967
+
968
+ export { generateClient, groupByResource, scanContracts, watchAndGenerate };
969
+ //# sourceMappingURL=index.js.map
970
+ //# sourceMappingURL=index.js.map