@fuzionx/framework 0.1.8 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/db-sync.js +99 -0
- package/cli/fx.js +3 -0
- package/cli/index.js +329 -0
- package/cli/templates/app/.env.example.tpl +14 -0
- package/cli/templates/app/.gitignore.tpl +4 -0
- package/cli/templates/app/app.js.tpl +14 -0
- package/cli/templates/app/controllers/HomeController.js +13 -0
- package/cli/templates/app/fuzionx.yaml.tpl +32 -0
- package/cli/templates/app/package.json.tpl +15 -0
- package/cli/templates/app/routes/api.js.tpl +7 -0
- package/cli/templates/app/routes/web.js.tpl +5 -0
- package/cli/templates/app/views/default/errors/404.html +15 -0
- package/cli/templates/app/views/default/errors/500.html +14 -0
- package/cli/templates/app/views/default/layouts/main.html +22 -0
- package/cli/templates/app/views/default/pages/home.html +188 -0
- package/cli/templates/make/controller.js.tpl +40 -0
- package/cli/templates/make/event.js.tpl +8 -0
- package/cli/templates/make/job.js.tpl +10 -0
- package/cli/templates/make/middleware.js.tpl +10 -0
- package/cli/templates/make/model.js.tpl +15 -0
- package/cli/templates/make/service.js.tpl +15 -0
- package/cli/templates/make/task.js.tpl +15 -0
- package/cli/templates/make/test.js.tpl +7 -0
- package/cli/templates/make/worker.js.tpl +14 -0
- package/cli/templates/make/ws.js.tpl +18 -0
- package/lib/core/Application.js +164 -5
- package/lib/core/AutoLoader.js +46 -0
- package/lib/core/Config.js +103 -0
- package/lib/core/Context.js +5 -11
- package/lib/view/View.js +31 -20
- package/package.json +4 -2
package/lib/core/Config.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* dot-notation으로 접근: config.get('app.auth.secret')
|
|
6
6
|
*
|
|
7
7
|
* .env 자동 로드 + ${VAR:default} 환경변수 치환.
|
|
8
|
+
* YAML 파일 직접 파싱 지원 (외부 의존 없이 내장 파서 사용).
|
|
8
9
|
*
|
|
9
10
|
* @see docs/framework/17-config.md
|
|
10
11
|
*/
|
|
@@ -26,6 +27,108 @@ export default class Config {
|
|
|
26
27
|
this._cache = new Map();
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* YAML 파일에서 설정 로드.
|
|
32
|
+
* configPath가 지정된 경우 Application 생성자에서 호출.
|
|
33
|
+
* bridge 섹션은 Rust에서 파싱하므로 JS 쪽은 database/app/themes 등을 로드.
|
|
34
|
+
* @param {string} configPath - 절대 경로
|
|
35
|
+
*/
|
|
36
|
+
loadYaml(configPath) {
|
|
37
|
+
if (!existsSync(configPath)) return;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
41
|
+
const parsed = Config.parseYaml(content);
|
|
42
|
+
|
|
43
|
+
// _raw에 병합 (기존 opts.config 우선)
|
|
44
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
45
|
+
if (this._raw[key] === undefined) {
|
|
46
|
+
this._raw[key] = value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 섹션 별칭 갱신
|
|
51
|
+
if (!Object.keys(this.bridge).length && parsed.bridge) this.bridge = parsed.bridge;
|
|
52
|
+
if (!Object.keys(this.database).length && parsed.database) this.database = parsed.database;
|
|
53
|
+
if (!Object.keys(this.app).length && parsed.app) this.app = parsed.app;
|
|
54
|
+
|
|
55
|
+
// 캐시 초기화
|
|
56
|
+
this._cache.clear();
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`[Config] YAML 파일 로드 실패 (${configPath}):`, err.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 간이 YAML 파서 — 외부 의존 없이 fuzionx.yaml 구조 파싱.
|
|
64
|
+
* 지원: 중첩 객체, 문자열, 숫자, boolean, 배열(인라인), 인용 문자열.
|
|
65
|
+
* @param {string} content
|
|
66
|
+
* @returns {object}
|
|
67
|
+
*/
|
|
68
|
+
static parseYaml(content) {
|
|
69
|
+
const result = {};
|
|
70
|
+
const lines = content.split('\n');
|
|
71
|
+
const stack = [{ indent: -1, obj: result }];
|
|
72
|
+
|
|
73
|
+
for (const rawLine of lines) {
|
|
74
|
+
// 주석/빈 줄 무시
|
|
75
|
+
const line = rawLine.replace(/#.*$/, '');
|
|
76
|
+
if (!line.trim()) continue;
|
|
77
|
+
|
|
78
|
+
const indent = line.search(/\S/);
|
|
79
|
+
const match = line.match(/^(\s*)([-\w.]+)\s*:\s*(.*)$/);
|
|
80
|
+
if (!match) continue;
|
|
81
|
+
|
|
82
|
+
const [, , key, rawValue] = match;
|
|
83
|
+
|
|
84
|
+
// 스택에서 현재 indent보다 깊거나 같은 레벨 제거
|
|
85
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
86
|
+
stack.pop();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parent = stack[stack.length - 1].obj;
|
|
90
|
+
const value = rawValue.trim();
|
|
91
|
+
|
|
92
|
+
if (!value) {
|
|
93
|
+
// 하위 객체 시작
|
|
94
|
+
parent[key] = {};
|
|
95
|
+
stack.push({ indent, obj: parent[key] });
|
|
96
|
+
} else {
|
|
97
|
+
// 값 파싱
|
|
98
|
+
parent[key] = Config._parseYamlValue(value);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* YAML 값 파싱 (문자열, 숫자, boolean, 배열)
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
static _parseYamlValue(raw) {
|
|
110
|
+
// 인용 문자열
|
|
111
|
+
const quoted = raw.match(/^(['"])(.*)\1$/);
|
|
112
|
+
if (quoted) return quoted[2];
|
|
113
|
+
|
|
114
|
+
// boolean
|
|
115
|
+
if (raw === 'true') return true;
|
|
116
|
+
if (raw === 'false') return false;
|
|
117
|
+
if (raw === 'null' || raw === '~') return null;
|
|
118
|
+
|
|
119
|
+
// 인라인 배열 [a, b, c]
|
|
120
|
+
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
121
|
+
return raw.slice(1, -1).split(',').map(s => Config._parseYamlValue(s.trim()));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 숫자
|
|
125
|
+
const num = Number(raw);
|
|
126
|
+
if (!isNaN(num) && raw !== '') return num;
|
|
127
|
+
|
|
128
|
+
// 기본: 문자열
|
|
129
|
+
return raw;
|
|
130
|
+
}
|
|
131
|
+
|
|
29
132
|
/**
|
|
30
133
|
* .env 파일 로드 (17-config.md)
|
|
31
134
|
* 부트 시 자동 호출. 이미 설정된 환경변수는 덮어쓰지 않음.
|
package/lib/core/Context.js
CHANGED
|
@@ -313,19 +313,13 @@ export default class Context {
|
|
|
313
313
|
...data,
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
// Bridge SSR
|
|
317
|
-
if (this.app?.
|
|
318
|
-
|
|
319
|
-
const html = this.app._bridge.ssrRenderString(view, globals, this.locale);
|
|
320
|
-
return this.html(html);
|
|
321
|
-
} catch {} // Bridge SSR 실패 시 View 폴백
|
|
316
|
+
// Bridge Tera SSR — 폴백 없음
|
|
317
|
+
if (!this.app?._view) {
|
|
318
|
+
throw new Error('View not initialized — Bridge not available');
|
|
322
319
|
}
|
|
323
320
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return this.html(html);
|
|
327
|
-
}
|
|
328
|
-
return this.send(`View '${view}' not found`);
|
|
321
|
+
const html = this.app._view.render(view, globals);
|
|
322
|
+
return this.html(html);
|
|
329
323
|
}
|
|
330
324
|
|
|
331
325
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
package/lib/view/View.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* View — 뷰 렌더링 (Bridge SSR)
|
|
2
|
+
* View — 뷰 렌더링 (Bridge Tera SSR)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Bridge의 ssrRenderFile N-API로 Tera 파일 기반 렌더링.
|
|
5
|
+
* {% extends %}, {% block %}, {% include %}, {{ t(key="...") }} 완전 지원.
|
|
6
|
+
*
|
|
7
|
+
* Bridge 없으면 반드시 에러 — 폴백 없음.
|
|
6
8
|
*
|
|
7
9
|
* @see docs/framework/03-views-templates.md
|
|
8
10
|
*/
|
|
@@ -32,10 +34,10 @@ export default class View {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
* 템플릿 렌더링 — Bridge
|
|
37
|
+
* 템플릿 렌더링 — Bridge ssrRenderFile N-API 사용
|
|
36
38
|
*
|
|
37
39
|
* 테마 경로 해석 (03-views-templates.md):
|
|
38
|
-
* 'home' →
|
|
40
|
+
* 'home' → ssrRenderFile(viewsPath/{theme}, 'pages/home.html', context, locale)
|
|
39
41
|
*
|
|
40
42
|
* @param {string} template - 'home', 'users/index' 등
|
|
41
43
|
* @param {object} [data] - 템플릿 변수
|
|
@@ -43,30 +45,39 @@ export default class View {
|
|
|
43
45
|
* @returns {string} - 렌더링된 HTML
|
|
44
46
|
*/
|
|
45
47
|
render(template, data = {}, theme) {
|
|
48
|
+
if (!this._bridge) {
|
|
49
|
+
throw new Error('Bridge not available — cannot render view');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof this._bridge.ssrRenderFile !== 'function') {
|
|
53
|
+
throw new Error('Bridge ssrRenderFile not available — rebuild with ssr feature');
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
const activeTheme = theme || data.theme || this.theme;
|
|
47
57
|
const mergedData = { ...this._globals, ...data, theme: activeTheme };
|
|
58
|
+
const locale = data.locale || 'ko';
|
|
59
|
+
|
|
60
|
+
// Tera glob 루트: views/{theme}/
|
|
61
|
+
const templateDir = join(this.viewsPath, activeTheme);
|
|
62
|
+
const contextJson = JSON.stringify(mergedData);
|
|
48
63
|
|
|
49
|
-
//
|
|
64
|
+
// 후보 이름: pages/{template}.html → {template}.html
|
|
50
65
|
const candidates = [
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
`pages/${template}.html`,
|
|
67
|
+
`${template}.html`,
|
|
53
68
|
];
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} catch {
|
|
63
|
-
// 파일 없음 → 다음 후보
|
|
70
|
+
for (const templateName of candidates) {
|
|
71
|
+
try {
|
|
72
|
+
return this._bridge.ssrRenderFile(templateDir, templateName, contextJson, locale);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// 파일 없음 → 다음 후보 (Tera init 에러는 파일 미존재)
|
|
75
|
+
if (!err.message?.includes('not found') && !err.message?.includes('init failed')) {
|
|
76
|
+
throw err; // 렌더링 에러는 그대로 전파
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
|
-
|
|
67
|
-
throw new Error(`View '${template}' not found. Searched: ${candidates.join(', ')}`);
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
throw new Error(`
|
|
81
|
+
throw new Error(`View '${template}' not found in theme '${activeTheme}'. Searched: ${candidates.join(', ')}`);
|
|
71
82
|
}
|
|
72
83
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzionx/framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Full-stack MVC framework built on @fuzionx/core — Controller, Service, Model, Middleware, DI, EventBus",
|
|
6
6
|
"main": "index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"url": "https://github.com/saytohenry/fuzionx"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@fuzionx/core": "^0.1.
|
|
37
|
+
"@fuzionx/core": "^0.1.20",
|
|
38
38
|
"better-sqlite3": "^12.8.0",
|
|
39
39
|
"knex": "^3.2.5",
|
|
40
40
|
"mongoose": "^9.3.2",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"files": [
|
|
48
48
|
"index.js",
|
|
49
|
+
"bin/",
|
|
50
|
+
"cli/",
|
|
49
51
|
"lib/",
|
|
50
52
|
"testing/"
|
|
51
53
|
]
|