@spfn/core 0.1.0-alpha.8 → 0.1.0-alpha.82

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 (61) hide show
  1. package/README.md +169 -195
  2. package/dist/auto-loader-JFaZ9gON.d.ts +80 -0
  3. package/dist/cache/index.d.ts +211 -0
  4. package/dist/cache/index.js +1013 -0
  5. package/dist/cache/index.js.map +1 -0
  6. package/dist/client/index.d.ts +131 -92
  7. package/dist/client/index.js +93 -85
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/codegen/generators/index.d.ts +19 -0
  10. package/dist/codegen/generators/index.js +1521 -0
  11. package/dist/codegen/generators/index.js.map +1 -0
  12. package/dist/codegen/index.d.ts +76 -60
  13. package/dist/codegen/index.js +1506 -735
  14. package/dist/codegen/index.js.map +1 -1
  15. package/dist/database-errors-BNNmLTJE.d.ts +86 -0
  16. package/dist/db/index.d.ts +844 -44
  17. package/dist/db/index.js +1281 -1307
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/env/index.d.ts +508 -0
  20. package/dist/env/index.js +1127 -0
  21. package/dist/env/index.js.map +1 -0
  22. package/dist/errors/index.d.ts +136 -0
  23. package/dist/errors/index.js +172 -0
  24. package/dist/errors/index.js.map +1 -0
  25. package/dist/index-DHiAqhKv.d.ts +101 -0
  26. package/dist/index.d.ts +3 -374
  27. package/dist/index.js +2424 -2178
  28. package/dist/index.js.map +1 -1
  29. package/dist/logger/index.d.ts +94 -0
  30. package/dist/logger/index.js +795 -0
  31. package/dist/logger/index.js.map +1 -0
  32. package/dist/middleware/index.d.ts +60 -0
  33. package/dist/middleware/index.js +918 -0
  34. package/dist/middleware/index.js.map +1 -0
  35. package/dist/route/index.d.ts +21 -53
  36. package/dist/route/index.js +1259 -219
  37. package/dist/route/index.js.map +1 -1
  38. package/dist/server/index.d.ts +18 -0
  39. package/dist/server/index.js +2419 -2059
  40. package/dist/server/index.js.map +1 -1
  41. package/dist/types/index.d.ts +121 -0
  42. package/dist/types/index.js +38 -0
  43. package/dist/types/index.js.map +1 -0
  44. package/dist/types-BXibIEyj.d.ts +60 -0
  45. package/package.json +67 -17
  46. package/dist/auto-loader-C44TcLmM.d.ts +0 -125
  47. package/dist/bind-pssq1NRT.d.ts +0 -34
  48. package/dist/postgres-errors-CY_Es8EJ.d.ts +0 -1703
  49. package/dist/scripts/index.d.ts +0 -24
  50. package/dist/scripts/index.js +0 -1201
  51. package/dist/scripts/index.js.map +0 -1
  52. package/dist/scripts/templates/api-index.template.txt +0 -10
  53. package/dist/scripts/templates/api-tag.template.txt +0 -11
  54. package/dist/scripts/templates/contract.template.txt +0 -87
  55. package/dist/scripts/templates/entity-type.template.txt +0 -31
  56. package/dist/scripts/templates/entity.template.txt +0 -19
  57. package/dist/scripts/templates/index.template.txt +0 -10
  58. package/dist/scripts/templates/repository.template.txt +0 -37
  59. package/dist/scripts/templates/routes-id.template.txt +0 -59
  60. package/dist/scripts/templates/routes-index.template.txt +0 -44
  61. package/dist/types-SlzTr8ZO.d.ts +0 -143
@@ -1,1201 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/postgres-js';
2
- import { migrate } from 'drizzle-orm/postgres-js/migrator';
3
- import postgres from 'postgres';
4
- import { config } from 'dotenv';
5
- import { fileURLToPath } from 'url';
6
- import { dirname, join, resolve } from 'path';
7
- import { mkdir, writeFile, readdir, stat } from 'fs/promises';
8
- import * as ts from 'typescript';
9
- import { existsSync, mkdirSync, createWriteStream, readFileSync } from 'fs';
10
- import { watch } from 'chokidar';
11
- import pino from 'pino';
12
- import chalk from 'chalk';
13
-
14
- // src/scripts/migrate.ts
15
- config({ path: ".env.local" });
16
- var __filename = fileURLToPath(import.meta.url);
17
- var __dirname = dirname(__filename);
18
- var projectRoot = join(__dirname, "../../..");
19
- var DATABASE_URL = process.env.DATABASE_URL;
20
- if (!DATABASE_URL) {
21
- console.error("\u274C DATABASE_URL environment variable is required");
22
- process.exit(1);
23
- }
24
- async function runMigrations() {
25
- console.log("\u{1F504} Starting database migration...");
26
- console.log(`\u{1F4C2} Migrations folder: ${join(projectRoot, "drizzle")}`);
27
- const migrationConnection = postgres(DATABASE_URL, { max: 1 });
28
- const db = drizzle(migrationConnection);
29
- try {
30
- console.log("\u23F3 Applying migrations...");
31
- await migrate(db, {
32
- migrationsFolder: join(projectRoot, "drizzle")
33
- });
34
- console.log("\u2705 Migration completed successfully");
35
- } catch (error) {
36
- console.error("\u274C Migration failed:", error);
37
- throw error;
38
- } finally {
39
- await migrationConnection.end();
40
- console.log("\u{1F50C} Database connection closed");
41
- }
42
- }
43
- runMigrations().then(() => {
44
- console.log("\u{1F389} All migrations applied");
45
- process.exit(0);
46
- }).catch((error) => {
47
- console.error("\u{1F4A5} Migration process failed:", error);
48
- process.exit(1);
49
- });
50
- async function scanContracts(routesDir) {
51
- const contractFiles = await scanContractFiles(routesDir);
52
- const mappings = [];
53
- for (let i = 0; i < contractFiles.length; i++) {
54
- const filePath = contractFiles[i];
55
- const exports = extractContractExports(filePath);
56
- const basePath = getBasePathFromFile(filePath, routesDir);
57
- for (let j = 0; j < exports.length; j++) {
58
- const contractExport = exports[j];
59
- const fullPath = combinePaths(basePath, contractExport.path);
60
- mappings.push({
61
- method: contractExport.method,
62
- path: fullPath,
63
- contractName: contractExport.name,
64
- contractImportPath: getImportPathFromRoutes(filePath, routesDir),
65
- routeFile: "",
66
- // Not needed anymore
67
- contractFile: filePath
68
- });
69
- }
70
- }
71
- return mappings;
72
- }
73
- async function scanContractFiles(dir, files = []) {
74
- try {
75
- const entries = await readdir(dir);
76
- for (let i = 0; i < entries.length; i++) {
77
- const entry = entries[i];
78
- const fullPath = join(dir, entry);
79
- const fileStat = await stat(fullPath);
80
- if (fileStat.isDirectory()) {
81
- await scanContractFiles(fullPath, files);
82
- } else if (entry === "contract.ts") {
83
- files.push(fullPath);
84
- }
85
- }
86
- } catch (error) {
87
- }
88
- return files;
89
- }
90
- function extractContractExports(filePath) {
91
- const sourceCode = readFileSync(filePath, "utf-8");
92
- const sourceFile = ts.createSourceFile(
93
- filePath,
94
- sourceCode,
95
- ts.ScriptTarget.Latest,
96
- true
97
- );
98
- const exports = [];
99
- function visit(node) {
100
- if (ts.isVariableStatement(node)) {
101
- const hasExport = node.modifiers?.some(
102
- (m) => m.kind === ts.SyntaxKind.ExportKeyword
103
- );
104
- if (hasExport && node.declarationList.declarations.length > 0) {
105
- const declaration = node.declarationList.declarations[0];
106
- if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && declaration.initializer && ts.isObjectLiteralExpression(declaration.initializer)) {
107
- const name = declaration.name.text;
108
- if (isContractName(name)) {
109
- const contractData = extractContractData(declaration.initializer);
110
- if (contractData.method && contractData.path) {
111
- exports.push({
112
- name,
113
- method: contractData.method,
114
- path: contractData.path
115
- });
116
- }
117
- }
118
- }
119
- }
120
- }
121
- ts.forEachChild(node, visit);
122
- }
123
- visit(sourceFile);
124
- return exports;
125
- }
126
- function extractContractData(objectLiteral) {
127
- const result = {};
128
- for (let i = 0; i < objectLiteral.properties.length; i++) {
129
- const prop = objectLiteral.properties[i];
130
- if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
131
- const propName = prop.name.text;
132
- if (propName === "method") {
133
- let value;
134
- if (ts.isStringLiteral(prop.initializer)) {
135
- value = prop.initializer.text;
136
- } else if (ts.isAsExpression(prop.initializer) && ts.isStringLiteral(prop.initializer.expression)) {
137
- value = prop.initializer.expression.text;
138
- }
139
- if (value) result.method = value;
140
- } else if (propName === "path") {
141
- let value;
142
- if (ts.isStringLiteral(prop.initializer)) {
143
- value = prop.initializer.text;
144
- } else if (ts.isAsExpression(prop.initializer) && ts.isStringLiteral(prop.initializer.expression)) {
145
- value = prop.initializer.expression.text;
146
- }
147
- if (value) result.path = value;
148
- }
149
- }
150
- }
151
- return result;
152
- }
153
- function isContractName(name) {
154
- return name.indexOf("Contract") !== -1 || name.indexOf("contract") !== -1 || name.endsWith("Schema") || name.endsWith("schema");
155
- }
156
- function getBasePathFromFile(filePath, routesDir) {
157
- let relativePath = filePath.replace(routesDir, "");
158
- if (relativePath.startsWith("/")) {
159
- relativePath = relativePath.slice(1);
160
- }
161
- relativePath = relativePath.replace("/contract.ts", "");
162
- if (relativePath === "index" || relativePath === "") {
163
- return "/";
164
- }
165
- const segments = relativePath.split("/");
166
- const transformed = [];
167
- for (let i = 0; i < segments.length; i++) {
168
- const seg = segments[i];
169
- if (seg === "index") {
170
- continue;
171
- }
172
- if (seg.startsWith("[") && seg.endsWith("]")) {
173
- transformed.push(":" + seg.slice(1, -1));
174
- } else {
175
- transformed.push(seg);
176
- }
177
- }
178
- if (transformed.length === 0) {
179
- return "/";
180
- }
181
- return "/" + transformed.join("/");
182
- }
183
- function combinePaths(basePath, contractPath) {
184
- basePath = basePath || "/";
185
- contractPath = contractPath || "/";
186
- if (basePath.endsWith("/") && basePath !== "/") {
187
- basePath = basePath.slice(0, -1);
188
- }
189
- if (contractPath.startsWith("/") && contractPath !== "/") {
190
- if (basePath === "/") {
191
- return contractPath;
192
- }
193
- return basePath + contractPath;
194
- }
195
- if (contractPath === "/") {
196
- return basePath;
197
- }
198
- return basePath + "/" + contractPath;
199
- }
200
- function getImportPathFromRoutes(filePath, routesDir) {
201
- let relativePath = filePath.replace(routesDir, "");
202
- if (relativePath.startsWith("/")) {
203
- relativePath = relativePath.slice(1);
204
- }
205
- if (relativePath.endsWith(".ts")) {
206
- relativePath = relativePath.slice(0, -3);
207
- }
208
- return "@/server/routes/" + relativePath;
209
- }
210
-
211
- // src/codegen/route-scanner.ts
212
- function groupByResource(mappings) {
213
- const grouped = {};
214
- for (let i = 0; i < mappings.length; i++) {
215
- const mapping = mappings[i];
216
- const resource = extractResourceName(mapping.path);
217
- if (!grouped[resource]) {
218
- grouped[resource] = [];
219
- }
220
- grouped[resource].push(mapping);
221
- }
222
- return grouped;
223
- }
224
- function extractResourceName(path) {
225
- const segments = path.slice(1).split("/").filter((s) => s && s !== "*");
226
- const staticSegments = [];
227
- for (let i = 0; i < segments.length; i++) {
228
- const seg = segments[i];
229
- if (!seg.startsWith(":")) {
230
- staticSegments.push(seg);
231
- }
232
- }
233
- if (staticSegments.length === 0) {
234
- return "root";
235
- }
236
- if (staticSegments.length === 1) {
237
- return staticSegments[0];
238
- }
239
- const result = [staticSegments[0]];
240
- for (let i = 1; i < staticSegments.length; i++) {
241
- const seg = staticSegments[i];
242
- result.push(seg.charAt(0).toUpperCase() + seg.slice(1));
243
- }
244
- return result.join("");
245
- }
246
- async function generateClient(mappings, options) {
247
- const startTime = Date.now();
248
- const grouped = groupByResource(mappings);
249
- const resourceNames = Object.keys(grouped);
250
- const code = generateClientCode(mappings, grouped, options);
251
- await mkdir(dirname(options.outputPath), { recursive: true });
252
- await writeFile(options.outputPath, code, "utf-8");
253
- const stats = {
254
- routesScanned: mappings.length,
255
- contractsFound: mappings.length,
256
- contractFiles: countUniqueContractFiles(mappings),
257
- resourcesGenerated: resourceNames.length,
258
- methodsGenerated: mappings.length,
259
- duration: Date.now() - startTime
260
- };
261
- return stats;
262
- }
263
- function generateClientCode(mappings, grouped, options) {
264
- let code = "";
265
- code += generateHeader();
266
- code += generateImports(mappings, options);
267
- code += generateApiObject(grouped, options);
268
- code += generateFooter();
269
- return code;
270
- }
271
- function generateHeader() {
272
- return `/**
273
- * Auto-generated API Client
274
- *
275
- * Generated by @spfn/core codegen
276
- * DO NOT EDIT MANUALLY
277
- *
278
- * @generated ${(/* @__PURE__ */ new Date()).toISOString()}
279
- */
280
-
281
- `;
282
- }
283
- function generateImports(mappings, options) {
284
- let code = "";
285
- code += `import { client } from '@spfn/core/client';
286
- `;
287
- if (options.includeTypes !== false) {
288
- code += `import type { InferContract } from '@spfn/core';
289
- `;
290
- }
291
- code += `
292
- `;
293
- const importGroups = groupContractsByImportPath(mappings);
294
- const importPaths = Object.keys(importGroups);
295
- for (let i = 0; i < importPaths.length; i++) {
296
- const importPath = importPaths[i];
297
- const contracts = importGroups[importPath];
298
- code += `import { ${contracts.join(", ")} } from '${importPath}';
299
- `;
300
- }
301
- code += `
302
- `;
303
- return code;
304
- }
305
- function groupContractsByImportPath(mappings) {
306
- const groups = {};
307
- for (let i = 0; i < mappings.length; i++) {
308
- const mapping = mappings[i];
309
- const path = mapping.contractImportPath;
310
- if (!groups[path]) {
311
- groups[path] = /* @__PURE__ */ new Set();
312
- }
313
- groups[path].add(mapping.contractName);
314
- }
315
- const result = {};
316
- const keys = Object.keys(groups);
317
- for (let i = 0; i < keys.length; i++) {
318
- const key = keys[i];
319
- result[key] = Array.from(groups[key]);
320
- }
321
- return result;
322
- }
323
- function generateApiObject(grouped, options) {
324
- let code = "";
325
- code += `/**
326
- * Type-safe API client
327
- */
328
- export const api = {
329
- `;
330
- const resourceNames = Object.keys(grouped);
331
- for (let i = 0; i < resourceNames.length; i++) {
332
- const resourceName = resourceNames[i];
333
- const routes = grouped[resourceName];
334
- code += ` ${resourceName}: {
335
- `;
336
- for (let j = 0; j < routes.length; j++) {
337
- const route = routes[j];
338
- code += generateMethodCode(route, options);
339
- }
340
- code += ` }`;
341
- if (i < resourceNames.length - 1) {
342
- code += `,`;
343
- }
344
- code += `
345
- `;
346
- }
347
- code += `} as const;
348
-
349
- `;
350
- return code;
351
- }
352
- function generateMethodCode(mapping, options) {
353
- const methodName = generateMethodName(mapping);
354
- const contractType = `typeof ${mapping.contractName}`;
355
- const hasParams = mapping.path.includes(":");
356
- const hasBody = ["POST", "PUT", "PATCH"].indexOf(mapping.method) !== -1;
357
- let code = "";
358
- if (options.includeJsDoc !== false) {
359
- code += ` /**
360
- `;
361
- code += ` * ${mapping.method} ${mapping.path}
362
- `;
363
- code += ` */
364
- `;
365
- }
366
- code += ` ${methodName}: (`;
367
- const params = [];
368
- if (hasParams) {
369
- params.push(`params: InferContract<${contractType}>['params']`);
370
- }
371
- if (hasBody) {
372
- params.push(`body: InferContract<${contractType}>['body']`);
373
- }
374
- if (params.length > 0) {
375
- code += `options: { ${params.join(", ")} }`;
376
- }
377
- code += `) => `;
378
- code += `client.call('${mapping.path}', ${mapping.contractName}, `;
379
- if (params.length > 0) {
380
- code += `options`;
381
- } else {
382
- code += `{}`;
383
- }
384
- code += `),
385
- `;
386
- return code;
387
- }
388
- function generateMethodName(mapping) {
389
- const method = mapping.method.toLowerCase();
390
- if (mapping.path === "/" || mapping.path.match(/^\/[\w-]+$/)) {
391
- if (method === "get") {
392
- return "list";
393
- }
394
- if (method === "post") {
395
- return "create";
396
- }
397
- }
398
- if (mapping.path.includes(":")) {
399
- if (method === "get") {
400
- return "getById";
401
- }
402
- if (method === "put" || method === "patch") {
403
- return "update";
404
- }
405
- if (method === "delete") {
406
- return "delete";
407
- }
408
- }
409
- return method;
410
- }
411
- function generateFooter() {
412
- return `/**
413
- * Export client instance for advanced usage
414
- *
415
- * Use this to add interceptors or customize the client:
416
- *
417
- * @example
418
- * \`\`\`ts
419
- * import { client } from './api';
420
- * import { createAuthInterceptor } from '@spfn/auth/nextjs';
421
- * import { NextJSCookieProvider } from '@spfn/auth/nextjs';
422
- *
423
- * client.use(createAuthInterceptor({
424
- * cookieProvider: new NextJSCookieProvider(),
425
- * encryptionKey: process.env.ENCRYPTION_KEY!
426
- * }));
427
- * \`\`\`
428
- */
429
- export { client };
430
- `;
431
- }
432
- function countUniqueContractFiles(mappings) {
433
- const files = /* @__PURE__ */ new Set();
434
- for (let i = 0; i < mappings.length; i++) {
435
- if (mappings[i].contractFile) {
436
- files.add(mappings[i].contractFile);
437
- }
438
- }
439
- return files.size;
440
- }
441
- var PinoAdapter = class _PinoAdapter {
442
- logger;
443
- constructor(config2) {
444
- const isProduction = process.env.NODE_ENV === "production";
445
- const isDevelopment = process.env.NODE_ENV === "development";
446
- const fileLoggingEnabled = process.env.LOGGER_FILE_ENABLED === "true";
447
- const targets = [];
448
- if (!isProduction && isDevelopment) {
449
- targets.push({
450
- target: "pino-pretty",
451
- level: "debug",
452
- options: {
453
- colorize: true,
454
- translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l",
455
- ignore: "pid,hostname"
456
- }
457
- });
458
- }
459
- if (fileLoggingEnabled && isProduction) {
460
- const logDir = process.env.LOG_DIR || "./logs";
461
- const maxFileSize = process.env.LOG_MAX_FILE_SIZE || "10M";
462
- const maxFiles = parseInt(process.env.LOG_MAX_FILES || "10", 10);
463
- targets.push({
464
- target: "pino-roll",
465
- level: "info",
466
- options: {
467
- file: `${logDir}/app.log`,
468
- frequency: "daily",
469
- size: maxFileSize,
470
- limit: { count: maxFiles },
471
- mkdir: true
472
- }
473
- });
474
- }
475
- this.logger = pino({
476
- level: config2.level,
477
- // Transport 설정 (targets가 있으면 사용, 없으면 기본 stdout)
478
- transport: targets.length > 0 ? { targets } : void 0,
479
- // 기본 필드
480
- base: config2.module ? { module: config2.module } : void 0
481
- });
482
- }
483
- child(module) {
484
- const childLogger = new _PinoAdapter({ level: this.logger.level, module });
485
- childLogger.logger = this.logger.child({ module });
486
- return childLogger;
487
- }
488
- debug(message, context) {
489
- this.logger.debug(context || {}, message);
490
- }
491
- info(message, context) {
492
- this.logger.info(context || {}, message);
493
- }
494
- warn(message, errorOrContext, context) {
495
- if (errorOrContext instanceof Error) {
496
- this.logger.warn({ err: errorOrContext, ...context }, message);
497
- } else {
498
- this.logger.warn(errorOrContext || {}, message);
499
- }
500
- }
501
- error(message, errorOrContext, context) {
502
- if (errorOrContext instanceof Error) {
503
- this.logger.error({ err: errorOrContext, ...context }, message);
504
- } else {
505
- this.logger.error(errorOrContext || {}, message);
506
- }
507
- }
508
- fatal(message, errorOrContext, context) {
509
- if (errorOrContext instanceof Error) {
510
- this.logger.fatal({ err: errorOrContext, ...context }, message);
511
- } else {
512
- this.logger.fatal(errorOrContext || {}, message);
513
- }
514
- }
515
- async close() {
516
- }
517
- };
518
-
519
- // src/logger/logger.ts
520
- var Logger = class _Logger {
521
- config;
522
- module;
523
- constructor(config2) {
524
- this.config = config2;
525
- this.module = config2.module;
526
- }
527
- /**
528
- * Get current log level
529
- */
530
- get level() {
531
- return this.config.level;
532
- }
533
- /**
534
- * Create child logger (per module)
535
- */
536
- child(module) {
537
- return new _Logger({
538
- ...this.config,
539
- module
540
- });
541
- }
542
- /**
543
- * Debug log
544
- */
545
- debug(message, context) {
546
- this.log("debug", message, void 0, context);
547
- }
548
- /**
549
- * Info log
550
- */
551
- info(message, context) {
552
- this.log("info", message, void 0, context);
553
- }
554
- warn(message, errorOrContext, context) {
555
- if (errorOrContext instanceof Error) {
556
- this.log("warn", message, errorOrContext, context);
557
- } else {
558
- this.log("warn", message, void 0, errorOrContext);
559
- }
560
- }
561
- error(message, errorOrContext, context) {
562
- if (errorOrContext instanceof Error) {
563
- this.log("error", message, errorOrContext, context);
564
- } else {
565
- this.log("error", message, void 0, errorOrContext);
566
- }
567
- }
568
- fatal(message, errorOrContext, context) {
569
- if (errorOrContext instanceof Error) {
570
- this.log("fatal", message, errorOrContext, context);
571
- } else {
572
- this.log("fatal", message, void 0, errorOrContext);
573
- }
574
- }
575
- /**
576
- * Log processing (internal)
577
- */
578
- log(level, message, error, context) {
579
- const metadata = {
580
- timestamp: /* @__PURE__ */ new Date(),
581
- level,
582
- message,
583
- module: this.module,
584
- error,
585
- context
586
- };
587
- this.processTransports(metadata);
588
- }
589
- /**
590
- * Process Transports
591
- */
592
- processTransports(metadata) {
593
- const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
594
- Promise.all(promises).catch((error) => {
595
- const errorMessage = error instanceof Error ? error.message : String(error);
596
- process.stderr.write(`[Logger] Transport error: ${errorMessage}
597
- `);
598
- });
599
- }
600
- /**
601
- * Transport log (error-safe)
602
- */
603
- async safeTransportLog(transport, metadata) {
604
- try {
605
- await transport.log(metadata);
606
- } catch (error) {
607
- const errorMessage = error instanceof Error ? error.message : String(error);
608
- process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
609
- `);
610
- }
611
- }
612
- /**
613
- * Close all Transports
614
- */
615
- async close() {
616
- const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
617
- await Promise.all(closePromises);
618
- }
619
- };
620
-
621
- // src/logger/types.ts
622
- var LOG_LEVEL_PRIORITY = {
623
- debug: 0,
624
- info: 1,
625
- warn: 2,
626
- error: 3,
627
- fatal: 4
628
- };
629
-
630
- // src/logger/formatters.ts
631
- var COLORS = {
632
- reset: "\x1B[0m",
633
- bright: "\x1B[1m",
634
- dim: "\x1B[2m",
635
- // 로그 레벨 컬러
636
- debug: "\x1B[36m",
637
- // cyan
638
- info: "\x1B[32m",
639
- // green
640
- warn: "\x1B[33m",
641
- // yellow
642
- error: "\x1B[31m",
643
- // red
644
- fatal: "\x1B[35m",
645
- // magenta
646
- // 추가 컬러
647
- gray: "\x1B[90m"
648
- };
649
- function colorizeLevel(level) {
650
- const color = COLORS[level];
651
- const levelStr = level.toUpperCase().padEnd(5);
652
- return `${color}${levelStr}${COLORS.reset}`;
653
- }
654
- function formatTimestamp(date) {
655
- return date.toISOString();
656
- }
657
- function formatTimestampHuman(date) {
658
- const year = date.getFullYear();
659
- const month = String(date.getMonth() + 1).padStart(2, "0");
660
- const day = String(date.getDate()).padStart(2, "0");
661
- const hours = String(date.getHours()).padStart(2, "0");
662
- const minutes = String(date.getMinutes()).padStart(2, "0");
663
- const seconds = String(date.getSeconds()).padStart(2, "0");
664
- const ms = String(date.getMilliseconds()).padStart(3, "0");
665
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
666
- }
667
- function formatError(error) {
668
- const lines = [];
669
- lines.push(`${error.name}: ${error.message}`);
670
- if (error.stack) {
671
- const stackLines = error.stack.split("\n").slice(1);
672
- lines.push(...stackLines);
673
- }
674
- return lines.join("\n");
675
- }
676
- function formatContext(context) {
677
- try {
678
- return JSON.stringify(context, null, 2);
679
- } catch (error) {
680
- return "[Context serialization failed]";
681
- }
682
- }
683
- function formatConsole(metadata, colorize = true) {
684
- const parts = [];
685
- const timestamp = formatTimestampHuman(metadata.timestamp);
686
- if (colorize) {
687
- parts.push(`${COLORS.gray}${timestamp}${COLORS.reset}`);
688
- } else {
689
- parts.push(timestamp);
690
- }
691
- if (colorize) {
692
- parts.push(colorizeLevel(metadata.level));
693
- } else {
694
- parts.push(metadata.level.toUpperCase().padEnd(5));
695
- }
696
- if (metadata.module) {
697
- if (colorize) {
698
- parts.push(`${COLORS.dim}[${metadata.module}]${COLORS.reset}`);
699
- } else {
700
- parts.push(`[${metadata.module}]`);
701
- }
702
- }
703
- parts.push(metadata.message);
704
- let output = parts.join(" ");
705
- if (metadata.context && Object.keys(metadata.context).length > 0) {
706
- output += "\n" + formatContext(metadata.context);
707
- }
708
- if (metadata.error) {
709
- output += "\n" + formatError(metadata.error);
710
- }
711
- return output;
712
- }
713
- function formatJSON(metadata) {
714
- const obj = {
715
- timestamp: formatTimestamp(metadata.timestamp),
716
- level: metadata.level,
717
- message: metadata.message
718
- };
719
- if (metadata.module) {
720
- obj.module = metadata.module;
721
- }
722
- if (metadata.context) {
723
- obj.context = metadata.context;
724
- }
725
- if (metadata.error) {
726
- obj.error = {
727
- name: metadata.error.name,
728
- message: metadata.error.message,
729
- stack: metadata.error.stack
730
- };
731
- }
732
- return JSON.stringify(obj);
733
- }
734
-
735
- // src/logger/transports/console.ts
736
- var ConsoleTransport = class {
737
- name = "console";
738
- level;
739
- enabled;
740
- colorize;
741
- constructor(config2) {
742
- this.level = config2.level;
743
- this.enabled = config2.enabled;
744
- this.colorize = config2.colorize ?? true;
745
- }
746
- async log(metadata) {
747
- if (!this.enabled) {
748
- return;
749
- }
750
- if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
751
- return;
752
- }
753
- const message = formatConsole(metadata, this.colorize);
754
- if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
755
- console.error(message);
756
- } else {
757
- console.log(message);
758
- }
759
- }
760
- };
761
- var FileTransport = class {
762
- name = "file";
763
- level;
764
- enabled;
765
- logDir;
766
- currentStream = null;
767
- currentFilename = null;
768
- constructor(config2) {
769
- this.level = config2.level;
770
- this.enabled = config2.enabled;
771
- this.logDir = config2.logDir;
772
- if (!existsSync(this.logDir)) {
773
- mkdirSync(this.logDir, { recursive: true });
774
- }
775
- }
776
- async log(metadata) {
777
- if (!this.enabled) {
778
- return;
779
- }
780
- if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
781
- return;
782
- }
783
- const message = formatJSON(metadata);
784
- const filename = this.getLogFilename(metadata.timestamp);
785
- if (this.currentFilename !== filename) {
786
- await this.rotateStream(filename);
787
- }
788
- if (this.currentStream) {
789
- return new Promise((resolve2, reject) => {
790
- this.currentStream.write(message + "\n", "utf-8", (error) => {
791
- if (error) {
792
- process.stderr.write(`[FileTransport] Failed to write log: ${error.message}
793
- `);
794
- reject(error);
795
- } else {
796
- resolve2();
797
- }
798
- });
799
- });
800
- }
801
- }
802
- /**
803
- * 스트림 교체 (날짜 변경 시)
804
- */
805
- async rotateStream(filename) {
806
- if (this.currentStream) {
807
- await this.closeStream();
808
- }
809
- const filepath = join(this.logDir, filename);
810
- this.currentStream = createWriteStream(filepath, {
811
- flags: "a",
812
- // append mode
813
- encoding: "utf-8"
814
- });
815
- this.currentFilename = filename;
816
- this.currentStream.on("error", (error) => {
817
- process.stderr.write(`[FileTransport] Stream error: ${error.message}
818
- `);
819
- this.currentStream = null;
820
- this.currentFilename = null;
821
- });
822
- }
823
- /**
824
- * 현재 스트림 닫기
825
- */
826
- async closeStream() {
827
- if (!this.currentStream) {
828
- return;
829
- }
830
- return new Promise((resolve2, reject) => {
831
- this.currentStream.end((error) => {
832
- if (error) {
833
- reject(error);
834
- } else {
835
- this.currentStream = null;
836
- this.currentFilename = null;
837
- resolve2();
838
- }
839
- });
840
- });
841
- }
842
- /**
843
- * 날짜별 로그 파일명 생성
844
- */
845
- getLogFilename(date) {
846
- const year = date.getFullYear();
847
- const month = String(date.getMonth() + 1).padStart(2, "0");
848
- const day = String(date.getDate()).padStart(2, "0");
849
- return `${year}-${month}-${day}.log`;
850
- }
851
- async close() {
852
- await this.closeStream();
853
- }
854
- };
855
-
856
- // src/logger/config.ts
857
- function getDefaultLogLevel() {
858
- const isProduction = process.env.NODE_ENV === "production";
859
- const isDevelopment = process.env.NODE_ENV === "development";
860
- if (isDevelopment) {
861
- return "debug";
862
- }
863
- if (isProduction) {
864
- return "info";
865
- }
866
- return "warn";
867
- }
868
- function getConsoleConfig() {
869
- const isProduction = process.env.NODE_ENV === "production";
870
- return {
871
- level: "debug",
872
- enabled: true,
873
- colorize: !isProduction
874
- // Dev: colored output, Production: plain text
875
- };
876
- }
877
- function getFileConfig() {
878
- const isProduction = process.env.NODE_ENV === "production";
879
- return {
880
- level: "info",
881
- enabled: isProduction,
882
- // File logging in production only
883
- logDir: process.env.LOG_DIR || "./logs",
884
- maxFileSize: 10 * 1024 * 1024,
885
- // 10MB
886
- maxFiles: 10
887
- };
888
- }
889
-
890
- // src/logger/adapters/custom.ts
891
- function initializeTransports() {
892
- const transports = [];
893
- const consoleConfig = getConsoleConfig();
894
- transports.push(new ConsoleTransport(consoleConfig));
895
- const fileConfig = getFileConfig();
896
- if (fileConfig.enabled) {
897
- transports.push(new FileTransport(fileConfig));
898
- }
899
- return transports;
900
- }
901
- var CustomAdapter = class _CustomAdapter {
902
- logger;
903
- constructor(config2) {
904
- this.logger = new Logger({
905
- level: config2.level,
906
- module: config2.module,
907
- transports: initializeTransports()
908
- });
909
- }
910
- child(module) {
911
- const adapter = new _CustomAdapter({ level: this.logger.level, module });
912
- adapter.logger = this.logger.child(module);
913
- return adapter;
914
- }
915
- debug(message, context) {
916
- this.logger.debug(message, context);
917
- }
918
- info(message, context) {
919
- this.logger.info(message, context);
920
- }
921
- warn(message, errorOrContext, context) {
922
- if (errorOrContext instanceof Error) {
923
- this.logger.warn(message, errorOrContext, context);
924
- } else {
925
- this.logger.warn(message, errorOrContext);
926
- }
927
- }
928
- error(message, errorOrContext, context) {
929
- if (errorOrContext instanceof Error) {
930
- this.logger.error(message, errorOrContext, context);
931
- } else {
932
- this.logger.error(message, errorOrContext);
933
- }
934
- }
935
- fatal(message, errorOrContext, context) {
936
- if (errorOrContext instanceof Error) {
937
- this.logger.fatal(message, errorOrContext, context);
938
- } else {
939
- this.logger.fatal(message, errorOrContext);
940
- }
941
- }
942
- async close() {
943
- await this.logger.close();
944
- }
945
- };
946
-
947
- // src/logger/adapter-factory.ts
948
- function createAdapter(type) {
949
- const level = getDefaultLogLevel();
950
- switch (type) {
951
- case "pino":
952
- return new PinoAdapter({ level });
953
- case "custom":
954
- return new CustomAdapter({ level });
955
- default:
956
- return new PinoAdapter({ level });
957
- }
958
- }
959
- function getAdapterType() {
960
- const adapterEnv = process.env.LOGGER_ADAPTER;
961
- if (adapterEnv === "custom" || adapterEnv === "pino") {
962
- return adapterEnv;
963
- }
964
- return "pino";
965
- }
966
- var logger = createAdapter(getAdapterType());
967
-
968
- // src/codegen/watch-generate.ts
969
- var codegenLogger = logger.child("codegen");
970
- async function generateOnce(options) {
971
- const cwd = process.cwd();
972
- const routesDir = options.routesDir ?? join(cwd, "src", "server", "routes");
973
- const outputPath = options.outputPath ?? join(cwd, "src", "lib", "api.ts");
974
- try {
975
- const contracts = await scanContracts(routesDir);
976
- if (contracts.length === 0) {
977
- if (options.debug) {
978
- codegenLogger.warn("No contracts found");
979
- }
980
- return null;
981
- }
982
- const stats = await generateClient(contracts, {
983
- outputPath,
984
- includeTypes: true,
985
- includeJsDoc: true
986
- });
987
- if (options.debug) {
988
- codegenLogger.info("Client generated", {
989
- endpoints: stats.methodsGenerated,
990
- resources: stats.resourcesGenerated,
991
- duration: stats.duration
992
- });
993
- }
994
- return stats;
995
- } catch (error) {
996
- codegenLogger.error(
997
- "Generation failed",
998
- error instanceof Error ? error : new Error(String(error))
999
- );
1000
- return null;
1001
- }
1002
- }
1003
- async function watchAndGenerate(options = {}) {
1004
- const cwd = process.cwd();
1005
- const routesDir = options.routesDir ?? join(cwd, "src", "server", "routes");
1006
- const outputPath = options.outputPath ?? join(cwd, "src", "lib", "api.ts");
1007
- const watchMode = options.watch !== false;
1008
- if (options.debug) {
1009
- codegenLogger.info("Contract Watcher Started", { routesDir, outputPath, watch: watchMode });
1010
- }
1011
- await generateOnce(options);
1012
- if (watchMode) {
1013
- let isGenerating = false;
1014
- let pendingRegeneration = false;
1015
- const watcher = watch(routesDir, {
1016
- ignored: /(^|[\/\\])\../,
1017
- // ignore dotfiles
1018
- persistent: true,
1019
- ignoreInitial: true,
1020
- awaitWriteFinish: {
1021
- stabilityThreshold: 100,
1022
- pollInterval: 50
1023
- }
1024
- });
1025
- const regenerate = async () => {
1026
- if (isGenerating) {
1027
- pendingRegeneration = true;
1028
- return;
1029
- }
1030
- isGenerating = true;
1031
- pendingRegeneration = false;
1032
- if (options.debug) {
1033
- codegenLogger.info("Contracts changed, regenerating...");
1034
- }
1035
- await generateOnce(options);
1036
- isGenerating = false;
1037
- if (pendingRegeneration) {
1038
- await regenerate();
1039
- }
1040
- };
1041
- watcher.on("add", regenerate).on("change", regenerate).on("unlink", regenerate);
1042
- process.on("SIGINT", () => {
1043
- watcher.close();
1044
- process.exit(0);
1045
- });
1046
- await new Promise(() => {
1047
- });
1048
- }
1049
- }
1050
- if (import.meta.url === `file://${process.argv[1]}`) {
1051
- watchAndGenerate({ debug: true });
1052
- }
1053
- async function generateClientCommand(options = {}) {
1054
- const contractsDir = options.contractsDir || resolve(process.cwd(), "src/server/contracts");
1055
- const outputPath = options.output || resolve(process.cwd(), "src/generated/api-client.ts");
1056
- const generationOptions = {
1057
- // Using routesDir field for backward compatibility
1058
- outputPath,
1059
- includeTypes: true,
1060
- includeJsDoc: true
1061
- };
1062
- console.log(chalk.blue("\u{1F50D} Scanning contracts..."));
1063
- console.log(chalk.gray(` Contracts directory: ${contractsDir}`));
1064
- try {
1065
- await generateClientOnce(generationOptions, contractsDir);
1066
- if (options.watch) {
1067
- console.log(chalk.blue("\n\u{1F440} Watching for changes..."));
1068
- await watchContracts(generationOptions, contractsDir);
1069
- }
1070
- } catch (error) {
1071
- console.error(chalk.red("\u274C Generation failed:"));
1072
- console.error(error);
1073
- process.exit(1);
1074
- }
1075
- }
1076
- async function generateClientOnce(options, contractsDir) {
1077
- const startTime = Date.now();
1078
- const mappings = await scanContracts(contractsDir);
1079
- if (mappings.length === 0) {
1080
- console.log(chalk.yellow("\u26A0\uFE0F No contracts found"));
1081
- console.log(chalk.gray(" Make sure your contracts include method and path"));
1082
- return;
1083
- }
1084
- console.log(chalk.green(`\u2705 Found ${mappings.length} contracts`));
1085
- const uniqueContracts = new Set(mappings.map((m) => m.contractName)).size;
1086
- const uniquePaths = new Set(mappings.map((m) => m.contractImportPath)).size;
1087
- console.log(chalk.gray(` Contracts: ${uniqueContracts}`));
1088
- console.log(chalk.gray(` Contract files: ${uniquePaths}`));
1089
- console.log(chalk.blue("\n\u{1F4DD} Generating client..."));
1090
- const stats = await generateClient(mappings, options);
1091
- const elapsed = Date.now() - startTime;
1092
- console.log(chalk.green(`
1093
- \u2728 Client generated successfully!`));
1094
- console.log(chalk.gray(` Output: ${options.outputPath}`));
1095
- console.log(chalk.gray(` Resources: ${stats.resourcesGenerated}`));
1096
- console.log(chalk.gray(` Methods: ${stats.methodsGenerated}`));
1097
- console.log(chalk.gray(` Time: ${elapsed}ms`));
1098
- }
1099
- async function watchContracts(options, contractsDir) {
1100
- const watcher = watch(contractsDir, {
1101
- ignored: /(^|[\/\\])\../,
1102
- // ignore dotfiles
1103
- persistent: true,
1104
- ignoreInitial: true
1105
- });
1106
- let isGenerating = false;
1107
- let pendingRegeneration = false;
1108
- const regenerate = async () => {
1109
- if (isGenerating) {
1110
- pendingRegeneration = true;
1111
- return;
1112
- }
1113
- isGenerating = true;
1114
- pendingRegeneration = false;
1115
- try {
1116
- console.log(chalk.blue("\n\u{1F504} Contracts changed, regenerating..."));
1117
- await generateClientOnce(options, contractsDir);
1118
- } catch (error) {
1119
- console.error(chalk.red("\u274C Regeneration failed:"));
1120
- console.error(error);
1121
- } finally {
1122
- isGenerating = false;
1123
- if (pendingRegeneration) {
1124
- await regenerate();
1125
- }
1126
- }
1127
- };
1128
- watcher.on("add", (path) => {
1129
- console.log(chalk.gray(` File added: ${path}`));
1130
- regenerate();
1131
- }).on("change", (path) => {
1132
- console.log(chalk.gray(` File changed: ${path}`));
1133
- regenerate();
1134
- }).on("unlink", (path) => {
1135
- console.log(chalk.gray(` File removed: ${path}`));
1136
- regenerate();
1137
- }).on("error", (error) => {
1138
- console.error(chalk.red("\u274C Watch error:"), error);
1139
- });
1140
- process.on("SIGINT", () => {
1141
- console.log(chalk.blue("\n\n\u{1F44B} Stopping watch mode..."));
1142
- watcher.close();
1143
- process.exit(0);
1144
- });
1145
- }
1146
- function parseArgs() {
1147
- const args = process.argv.slice(2);
1148
- const options = {};
1149
- for (let i = 0; i < args.length; i++) {
1150
- const arg = args[i];
1151
- if (arg === "--watch" || arg === "-w") {
1152
- options.watch = true;
1153
- } else if (arg === "--contracts" || arg === "-c") {
1154
- options.contractsDir = args[++i];
1155
- } else if (arg === "--output" || arg === "-o") {
1156
- options.output = args[++i];
1157
- } else if (arg === "--base-url" || arg === "-b") {
1158
- options.baseUrl = args[++i];
1159
- } else if (arg === "--help" || arg === "-h") {
1160
- printHelp();
1161
- process.exit(0);
1162
- }
1163
- }
1164
- return options;
1165
- }
1166
- function printHelp() {
1167
- console.log(`
1168
- ${chalk.bold("Generate API Client from Contracts")}
1169
-
1170
- ${chalk.bold("USAGE")}
1171
- node dist/scripts/generate-client.js [options]
1172
-
1173
- ${chalk.bold("OPTIONS")}
1174
- -c, --contracts <dir> Contracts directory (default: src/server/contracts)
1175
- -o, --output <file> Output file path (default: src/generated/api-client.ts)
1176
- -b, --base-url <url> Base URL for API client
1177
- -w, --watch Watch for changes and regenerate
1178
- -h, --help Show this help message
1179
-
1180
- ${chalk.bold("EXAMPLES")}
1181
- # Generate once
1182
- node dist/scripts/generate-client.js
1183
-
1184
- # Watch mode
1185
- node dist/scripts/generate-client.js --watch
1186
-
1187
- # Custom paths
1188
- node dist/scripts/generate-client.js --contracts ./contracts --output ./client.ts
1189
-
1190
- # With base URL
1191
- node dist/scripts/generate-client.js --base-url https://api.example.com
1192
- `);
1193
- }
1194
- if (import.meta.url === `file://${process.argv[1]}`) {
1195
- const options = parseArgs();
1196
- generateClientCommand(options);
1197
- }
1198
-
1199
- export { generateClientCommand };
1200
- //# sourceMappingURL=index.js.map
1201
- //# sourceMappingURL=index.js.map