@fuzionx/framework 0.1.39 → 0.1.41

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 (80) hide show
  1. package/README.md +501 -501
  2. package/bin/fx.js +12 -12
  3. package/cli/db-sync.js +99 -99
  4. package/cli/index.js +493 -493
  5. package/cli/templates/make/app/controllers/HomeController.js +14 -14
  6. package/cli/templates/make/app/routes/api.js +7 -7
  7. package/cli/templates/make/app/routes/web.js +5 -5
  8. package/cli/templates/make/app/views/default/errors/404.html +11 -11
  9. package/cli/templates/make/app/views/default/errors/500.html +14 -14
  10. package/cli/templates/make/app/views/default/layouts/main.html +22 -22
  11. package/cli/templates/make/app/views/default/pages/home.html +11 -11
  12. package/cli/templates/make/controller.js.tpl +40 -40
  13. package/cli/templates/make/event.js.tpl +8 -8
  14. package/cli/templates/make/job.js.tpl +10 -10
  15. package/cli/templates/make/middleware.js.tpl +10 -10
  16. package/cli/templates/make/model.js.tpl +15 -15
  17. package/cli/templates/make/service.js.tpl +15 -15
  18. package/cli/templates/make/task.js.tpl +15 -15
  19. package/cli/templates/make/test.js.tpl +7 -7
  20. package/cli/templates/make/worker.js.tpl +14 -14
  21. package/cli/templates/make/ws.js.tpl +18 -18
  22. package/index.js +67 -67
  23. package/lib/core/AppError.js +46 -46
  24. package/lib/core/Application.js +1006 -1006
  25. package/lib/core/AutoLoader.js +226 -226
  26. package/lib/core/Base.js +64 -64
  27. package/lib/core/Config.js +228 -228
  28. package/lib/core/Context.js +484 -484
  29. package/lib/database/ConnectionManager.js +208 -208
  30. package/lib/database/MariaModel.js +29 -29
  31. package/lib/database/Model.js +247 -247
  32. package/lib/database/ModelRegistry.js +72 -72
  33. package/lib/database/MongoModel.js +232 -232
  34. package/lib/database/Pagination.js +37 -37
  35. package/lib/database/PostgreModel.js +29 -29
  36. package/lib/database/QueryBuilder.js +172 -172
  37. package/lib/database/SQLiteModel.js +27 -27
  38. package/lib/database/SqlModel.js +257 -257
  39. package/lib/database/SqlQueryBuilder.js +332 -332
  40. package/lib/helpers/CryptoHelper.js +48 -48
  41. package/lib/helpers/FileHelper.js +61 -61
  42. package/lib/helpers/HashHelper.js +39 -39
  43. package/lib/helpers/I18nHelper.js +174 -174
  44. package/lib/helpers/Logger.js +108 -108
  45. package/lib/helpers/MediaHelper.js +84 -84
  46. package/lib/http/Controller.js +34 -34
  47. package/lib/http/ErrorHandler.js +136 -136
  48. package/lib/http/Middleware.js +43 -43
  49. package/lib/http/Router.js +109 -109
  50. package/lib/http/Validation.js +125 -125
  51. package/lib/middleware/apiAuth.js +79 -79
  52. package/lib/middleware/auth.js +42 -42
  53. package/lib/middleware/bodyParser.js +19 -19
  54. package/lib/middleware/cors.js +47 -47
  55. package/lib/middleware/csrf.js +32 -32
  56. package/lib/middleware/index.js +13 -13
  57. package/lib/middleware/session.js +27 -27
  58. package/lib/middleware/theme.js +20 -20
  59. package/lib/realtime/RoomManager.js +85 -85
  60. package/lib/realtime/WsHandler.js +107 -107
  61. package/lib/schedule/Job.js +38 -38
  62. package/lib/schedule/Queue.js +103 -103
  63. package/lib/schedule/Scheduler.js +171 -171
  64. package/lib/schedule/Task.js +39 -39
  65. package/lib/schedule/WorkerPool.js +225 -225
  66. package/lib/services/EventBus.js +94 -94
  67. package/lib/services/Service.js +261 -261
  68. package/lib/services/Storage.js +112 -112
  69. package/lib/utilities/ArrUtil.js +112 -112
  70. package/lib/utilities/DateUtil.js +98 -98
  71. package/lib/utilities/FunctionUtil.js +119 -119
  72. package/lib/utilities/NumUtil.js +75 -75
  73. package/lib/utilities/ObjectUtil.js +170 -170
  74. package/lib/utilities/PaginationUtil.js +81 -81
  75. package/lib/utilities/StrUtil.js +105 -105
  76. package/lib/utilities/index.js +18 -18
  77. package/lib/view/OpenAPI.js +231 -231
  78. package/lib/view/View.js +83 -83
  79. package/package.json +2 -2
  80. package/testing/index.js +232 -232
@@ -1,228 +1,228 @@
1
- /**
2
- * Config — 통합 설정 접근
3
- *
4
- * fuzionx.yaml의 3개 섹션(bridge, database, app)을 통합 관리.
5
- * dot-notation으로 접근: config.get('app.auth.secret')
6
- *
7
- * .env 자동 로드 + ${VAR:default} 환경변수 치환.
8
- * YAML 파일 직접 파싱 지원 (외부 의존 없이 내장 파서 사용).
9
- *
10
- * @see docs/framework/17-config.md
11
- */
12
- import { readFileSync, existsSync } from 'node:fs';
13
- import path from 'node:path';
14
-
15
- export default class Config {
16
- /**
17
- * @param {object} raw - 파싱된 YAML 객체 전체
18
- * raw.bridge — Bridge(Rust)가 파싱한 bridge: 섹션
19
- * raw.database — JS가 파싱한 database: 섹션
20
- * raw.app — JS가 파싱한 app: 섹션
21
- */
22
- constructor(raw = {}) {
23
- this.bridge = raw.bridge || {};
24
- this.database = raw.database || {};
25
- this.app = raw.app || {};
26
- this._raw = raw;
27
- this._cache = new Map();
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 raw = Config.parseYaml(content);
42
- const parsed = Config.resolveEnvVars(raw);
43
-
44
- // _raw에 병합 (기존 opts.config 우선)
45
- for (const [key, value] of Object.entries(parsed)) {
46
- if (this._raw[key] === undefined) {
47
- this._raw[key] = value;
48
- }
49
- }
50
-
51
- // 섹션 별칭 갱신
52
- if (!Object.keys(this.bridge).length && parsed.bridge) this.bridge = parsed.bridge;
53
- if (!Object.keys(this.database).length && parsed.database) this.database = parsed.database;
54
- if (!Object.keys(this.app).length && parsed.app) this.app = parsed.app;
55
-
56
- // 캐시 초기화
57
- this._cache.clear();
58
- } catch (err) {
59
- console.error(`[Config] YAML 파일 로드 실패 (${configPath}):`, err.message);
60
- }
61
- }
62
-
63
- /**
64
- * 간이 YAML 파서 — 외부 의존 없이 fuzionx.yaml 구조 파싱.
65
- * 지원: 중첩 객체, 문자열, 숫자, boolean, 배열(인라인), 인용 문자열.
66
- * @param {string} content
67
- * @returns {object}
68
- */
69
- static parseYaml(content) {
70
- const result = {};
71
- const lines = content.split('\n');
72
- const stack = [{ indent: -1, obj: result }];
73
-
74
- for (const rawLine of lines) {
75
- // 주석/빈 줄 무시
76
- const line = rawLine.replace(/#.*$/, '');
77
- if (!line.trim()) continue;
78
-
79
- const indent = line.search(/\S/);
80
- // 키: unquoted ([-\w.]+) 또는 quoted ("..." / '...')
81
- const match = line.match(/^(\s*)(?:(["'])([^"']+)\2|([-\w.]+))\s*:\s*(.*)$/);
82
- if (!match) continue;
83
-
84
- const key = match[3] || match[4]; // quoted key (그룹3) 또는 unquoted key (그룹4)
85
- const rawValue = match[5];
86
-
87
- // 스택에서 현재 indent보다 깊거나 같은 레벨 제거
88
- while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
89
- stack.pop();
90
- }
91
-
92
- const parent = stack[stack.length - 1].obj;
93
- const value = rawValue.trim();
94
-
95
- if (!value) {
96
- // 하위 객체 시작
97
- parent[key] = {};
98
- stack.push({ indent, obj: parent[key] });
99
- } else {
100
- // 값 파싱
101
- parent[key] = Config._parseYamlValue(value);
102
- }
103
- }
104
-
105
- return result;
106
- }
107
-
108
- /**
109
- * YAML 값 파싱 (문자열, 숫자, boolean, 배열)
110
- * @private
111
- */
112
- static _parseYamlValue(raw) {
113
- // 인용 문자열
114
- const quoted = raw.match(/^(['"])(.*)\1$/);
115
- if (quoted) return quoted[2];
116
-
117
- // boolean
118
- if (raw === 'true') return true;
119
- if (raw === 'false') return false;
120
- if (raw === 'null' || raw === '~') return null;
121
-
122
- // 인라인 배열 [a, b, c]
123
- if (raw.startsWith('[') && raw.endsWith(']')) {
124
- return raw.slice(1, -1).split(',').map(s => Config._parseYamlValue(s.trim()));
125
- }
126
-
127
- // 숫자
128
- const num = Number(raw);
129
- if (!isNaN(num) && raw !== '') return num;
130
-
131
- // 기본: 문자열
132
- return raw;
133
- }
134
-
135
- /**
136
- * .env 파일 로드 (17-config.md)
137
- * 부트 시 자동 호출. 이미 설정된 환경변수는 덮어쓰지 않음.
138
- * @param {string} [baseDir='.']
139
- */
140
- loadEnv(baseDir = '.') {
141
- const envPath = path.resolve(baseDir, '.env');
142
- if (!existsSync(envPath)) return;
143
-
144
- try {
145
- const content = readFileSync(envPath, 'utf-8');
146
- for (const line of content.split('\n')) {
147
- const trimmed = line.trim();
148
- if (!trimmed || trimmed.startsWith('#')) continue;
149
- const eqIdx = trimmed.indexOf('=');
150
- if (eqIdx === -1) continue;
151
- const key = trimmed.slice(0, eqIdx).trim();
152
- let value = trimmed.slice(eqIdx + 1).trim();
153
- // .env 인용부호 제거 ("value" → value, 'value' → value)
154
- value = value.replace(/^(['"])(.*)\1$/, '$2');
155
- // 시스템 환경변수 우선 (17-config.md)
156
- if (!(key in process.env)) {
157
- process.env[key] = value;
158
- }
159
- }
160
- } catch {} // .env 읽기 실패 시 무시
161
- }
162
-
163
- /**
164
- * YAML 객체의 ${VAR} / ${VAR:default} 패턴 치환 (17-config.md)
165
- * @param {object} obj
166
- * @returns {object}
167
- */
168
- static resolveEnvVars(obj) {
169
- if (typeof obj === 'string') {
170
- return obj.replace(/\$\{(\w+)(?::([^}]*))?\}/g, (_, varName, defaultVal) => {
171
- const envVal = process.env[varName];
172
- if (envVal !== undefined) return envVal;
173
- if (defaultVal !== undefined) return defaultVal;
174
- throw new Error(`Required environment variable '${varName}' is not set`);
175
- });
176
- }
177
- if (Array.isArray(obj)) return obj.map(item => Config.resolveEnvVars(item));
178
- if (obj !== null && typeof obj === 'object') {
179
- const result = {};
180
- for (const [key, value] of Object.entries(obj)) {
181
- result[key] = Config.resolveEnvVars(value);
182
- }
183
- return result;
184
- }
185
- return obj;
186
- }
187
-
188
- /**
189
- * dot-notation으로 설정값 접근
190
- * @param {string} path - 'app.auth.secret', 'database.main.host' 등
191
- * @param {*} [defaultValue] - 키가 없을 때 반환값
192
- * @returns {*}
193
- */
194
- get(path, defaultValue = undefined) {
195
- if (this._cache.has(path)) return this._cache.get(path);
196
-
197
- const parts = path.split('.');
198
- let current = this._raw;
199
-
200
- for (const part of parts) {
201
- if (current == null || typeof current !== 'object') {
202
- return defaultValue;
203
- }
204
- current = current[part];
205
- }
206
-
207
- if (current === undefined) return defaultValue;
208
- this._cache.set(path, current);
209
- return current;
210
- }
211
-
212
- /**
213
- * 설정값 존재 여부
214
- * @param {string} path
215
- * @returns {boolean}
216
- */
217
- has(path) {
218
- return this.get(path) !== undefined;
219
- }
220
-
221
- /**
222
- * 전체 설정 반환 (읽기 전용)
223
- * @returns {object}
224
- */
225
- all() {
226
- return this._raw;
227
- }
228
- }
1
+ /**
2
+ * Config — 통합 설정 접근
3
+ *
4
+ * fuzionx.yaml의 3개 섹션(bridge, database, app)을 통합 관리.
5
+ * dot-notation으로 접근: config.get('app.auth.secret')
6
+ *
7
+ * .env 자동 로드 + ${VAR:default} 환경변수 치환.
8
+ * YAML 파일 직접 파싱 지원 (외부 의존 없이 내장 파서 사용).
9
+ *
10
+ * @see docs/framework/17-config.md
11
+ */
12
+ import { readFileSync, existsSync } from 'node:fs';
13
+ import path from 'node:path';
14
+
15
+ export default class Config {
16
+ /**
17
+ * @param {object} raw - 파싱된 YAML 객체 전체
18
+ * raw.bridge — Bridge(Rust)가 파싱한 bridge: 섹션
19
+ * raw.database — JS가 파싱한 database: 섹션
20
+ * raw.app — JS가 파싱한 app: 섹션
21
+ */
22
+ constructor(raw = {}) {
23
+ this.bridge = raw.bridge || {};
24
+ this.database = raw.database || {};
25
+ this.app = raw.app || {};
26
+ this._raw = raw;
27
+ this._cache = new Map();
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 raw = Config.parseYaml(content);
42
+ const parsed = Config.resolveEnvVars(raw);
43
+
44
+ // _raw에 병합 (기존 opts.config 우선)
45
+ for (const [key, value] of Object.entries(parsed)) {
46
+ if (this._raw[key] === undefined) {
47
+ this._raw[key] = value;
48
+ }
49
+ }
50
+
51
+ // 섹션 별칭 갱신
52
+ if (!Object.keys(this.bridge).length && parsed.bridge) this.bridge = parsed.bridge;
53
+ if (!Object.keys(this.database).length && parsed.database) this.database = parsed.database;
54
+ if (!Object.keys(this.app).length && parsed.app) this.app = parsed.app;
55
+
56
+ // 캐시 초기화
57
+ this._cache.clear();
58
+ } catch (err) {
59
+ console.error(`[Config] YAML 파일 로드 실패 (${configPath}):`, err.message);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 간이 YAML 파서 — 외부 의존 없이 fuzionx.yaml 구조 파싱.
65
+ * 지원: 중첩 객체, 문자열, 숫자, boolean, 배열(인라인), 인용 문자열.
66
+ * @param {string} content
67
+ * @returns {object}
68
+ */
69
+ static parseYaml(content) {
70
+ const result = {};
71
+ const lines = content.split('\n');
72
+ const stack = [{ indent: -1, obj: result }];
73
+
74
+ for (const rawLine of lines) {
75
+ // 주석/빈 줄 무시
76
+ const line = rawLine.replace(/#.*$/, '');
77
+ if (!line.trim()) continue;
78
+
79
+ const indent = line.search(/\S/);
80
+ // 키: unquoted ([-\w.]+) 또는 quoted ("..." / '...')
81
+ const match = line.match(/^(\s*)(?:(["'])([^"']+)\2|([-\w.]+))\s*:\s*(.*)$/);
82
+ if (!match) continue;
83
+
84
+ const key = match[3] || match[4]; // quoted key (그룹3) 또는 unquoted key (그룹4)
85
+ const rawValue = match[5];
86
+
87
+ // 스택에서 현재 indent보다 깊거나 같은 레벨 제거
88
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
89
+ stack.pop();
90
+ }
91
+
92
+ const parent = stack[stack.length - 1].obj;
93
+ const value = rawValue.trim();
94
+
95
+ if (!value) {
96
+ // 하위 객체 시작
97
+ parent[key] = {};
98
+ stack.push({ indent, obj: parent[key] });
99
+ } else {
100
+ // 값 파싱
101
+ parent[key] = Config._parseYamlValue(value);
102
+ }
103
+ }
104
+
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * YAML 값 파싱 (문자열, 숫자, boolean, 배열)
110
+ * @private
111
+ */
112
+ static _parseYamlValue(raw) {
113
+ // 인용 문자열
114
+ const quoted = raw.match(/^(['"])(.*)\1$/);
115
+ if (quoted) return quoted[2];
116
+
117
+ // boolean
118
+ if (raw === 'true') return true;
119
+ if (raw === 'false') return false;
120
+ if (raw === 'null' || raw === '~') return null;
121
+
122
+ // 인라인 배열 [a, b, c]
123
+ if (raw.startsWith('[') && raw.endsWith(']')) {
124
+ return raw.slice(1, -1).split(',').map(s => Config._parseYamlValue(s.trim()));
125
+ }
126
+
127
+ // 숫자
128
+ const num = Number(raw);
129
+ if (!isNaN(num) && raw !== '') return num;
130
+
131
+ // 기본: 문자열
132
+ return raw;
133
+ }
134
+
135
+ /**
136
+ * .env 파일 로드 (17-config.md)
137
+ * 부트 시 자동 호출. 이미 설정된 환경변수는 덮어쓰지 않음.
138
+ * @param {string} [baseDir='.']
139
+ */
140
+ loadEnv(baseDir = '.') {
141
+ const envPath = path.resolve(baseDir, '.env');
142
+ if (!existsSync(envPath)) return;
143
+
144
+ try {
145
+ const content = readFileSync(envPath, 'utf-8');
146
+ for (const line of content.split('\n')) {
147
+ const trimmed = line.trim();
148
+ if (!trimmed || trimmed.startsWith('#')) continue;
149
+ const eqIdx = trimmed.indexOf('=');
150
+ if (eqIdx === -1) continue;
151
+ const key = trimmed.slice(0, eqIdx).trim();
152
+ let value = trimmed.slice(eqIdx + 1).trim();
153
+ // .env 인용부호 제거 ("value" → value, 'value' → value)
154
+ value = value.replace(/^(['"])(.*)\1$/, '$2');
155
+ // 시스템 환경변수 우선 (17-config.md)
156
+ if (!(key in process.env)) {
157
+ process.env[key] = value;
158
+ }
159
+ }
160
+ } catch {} // .env 읽기 실패 시 무시
161
+ }
162
+
163
+ /**
164
+ * YAML 객체의 ${VAR} / ${VAR:default} 패턴 치환 (17-config.md)
165
+ * @param {object} obj
166
+ * @returns {object}
167
+ */
168
+ static resolveEnvVars(obj) {
169
+ if (typeof obj === 'string') {
170
+ return obj.replace(/\$\{(\w+)(?::([^}]*))?\}/g, (_, varName, defaultVal) => {
171
+ const envVal = process.env[varName];
172
+ if (envVal !== undefined) return envVal;
173
+ if (defaultVal !== undefined) return defaultVal;
174
+ throw new Error(`Required environment variable '${varName}' is not set`);
175
+ });
176
+ }
177
+ if (Array.isArray(obj)) return obj.map(item => Config.resolveEnvVars(item));
178
+ if (obj !== null && typeof obj === 'object') {
179
+ const result = {};
180
+ for (const [key, value] of Object.entries(obj)) {
181
+ result[key] = Config.resolveEnvVars(value);
182
+ }
183
+ return result;
184
+ }
185
+ return obj;
186
+ }
187
+
188
+ /**
189
+ * dot-notation으로 설정값 접근
190
+ * @param {string} path - 'app.auth.secret', 'database.main.host' 등
191
+ * @param {*} [defaultValue] - 키가 없을 때 반환값
192
+ * @returns {*}
193
+ */
194
+ get(path, defaultValue = undefined) {
195
+ if (this._cache.has(path)) return this._cache.get(path);
196
+
197
+ const parts = path.split('.');
198
+ let current = this._raw;
199
+
200
+ for (const part of parts) {
201
+ if (current == null || typeof current !== 'object') {
202
+ return defaultValue;
203
+ }
204
+ current = current[part];
205
+ }
206
+
207
+ if (current === undefined) return defaultValue;
208
+ this._cache.set(path, current);
209
+ return current;
210
+ }
211
+
212
+ /**
213
+ * 설정값 존재 여부
214
+ * @param {string} path
215
+ * @returns {boolean}
216
+ */
217
+ has(path) {
218
+ return this.get(path) !== undefined;
219
+ }
220
+
221
+ /**
222
+ * 전체 설정 반환 (읽기 전용)
223
+ * @returns {object}
224
+ */
225
+ all() {
226
+ return this._raw;
227
+ }
228
+ }