@fuzionx/framework 0.1.27 → 0.1.29
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/index.js +66 -53
- package/cli/templates/app/database/models/User.js +9 -0
- package/cli/templates/app/fuzionx.yaml.tpl +3 -3
- package/cli/templates/app/locales/en.json +52 -0
- package/cli/templates/app/locales/ko.json +52 -0
- package/cli/templates/app/package.json.tpl +2 -1
- package/cli/templates/app/shared/events/userEvents.js +10 -0
- package/cli/templates/app/shared/jobs/CleanupJob.js +18 -0
- package/cli/templates/app/shared/jobs/EmailTask.js +17 -0
- package/cli/templates/app/shared/jobs/VideoPreviewTask.js +47 -0
- package/cli/templates/app/shared/workers/heavy.js +18 -0
- package/cli/templates/app/tester/controllers/FileController.js +288 -0
- package/cli/templates/app/tester/controllers/HomeController.js +36 -0
- package/cli/templates/app/tester/controllers/UserController.js +43 -0
- package/cli/templates/app/tester/middleware/RequestLogger.js +13 -0
- package/cli/templates/app/tester/routes/api.js +397 -0
- package/cli/templates/app/tester/routes/web.js +8 -0
- package/cli/templates/app/tester/services/UserService.js +52 -0
- package/cli/templates/app/tester/views/default/errors/404.html +15 -0
- package/cli/templates/app/tester/views/default/errors/500.html +14 -0
- package/cli/templates/app/tester/views/default/layouts/main.html +82 -0
- package/cli/templates/app/tester/views/default/pages/home.html +56 -0
- package/cli/templates/app/tester/views/default/pages/i18n.html +104 -0
- package/cli/templates/app/tester/views/default/pages/upload.html +149 -0
- package/cli/templates/app/tester/views/default/pages/websocket.html +239 -0
- package/cli/templates/app/tester/views/default/partials/footer.html +8 -0
- package/cli/templates/app/tester/views/default/partials/header.html +20 -0
- package/cli/templates/app/tester/ws/ChatHandler.js +98 -0
- package/lib/core/Application.js +1 -1
- package/lib/helpers/Logger.js +6 -6
- package/package.json +2 -2
- /package/cli/templates/app/{controllers → fuzionx/controllers}/HomeController.js +0 -0
- /package/cli/templates/app/{routes → fuzionx/routes}/api.js.tpl +0 -0
- /package/cli/templates/app/{routes → fuzionx/routes}/web.js.tpl +0 -0
- /package/cli/templates/app/{views → fuzionx/views}/default/errors/404.html +0 -0
- /package/cli/templates/app/{views → fuzionx/views}/default/errors/500.html +0 -0
- /package/cli/templates/app/{views → fuzionx/views}/default/layouts/main.html +0 -0
- /package/cli/templates/app/{views → fuzionx/views}/default/pages/home.html +0 -0
package/cli/index.js
CHANGED
|
@@ -93,7 +93,7 @@ const TYPE_SUFFIXES = {
|
|
|
93
93
|
export async function makeFile(type, name, baseDir = '.', appName) {
|
|
94
94
|
let dir;
|
|
95
95
|
if (APP_TYPE_DIRS[type]) {
|
|
96
|
-
if (!appName) throw new Error(`--app option required for make:${type} (e.g. fx make:${type} ${name} --app=
|
|
96
|
+
if (!appName) throw new Error(`--app option required for make:${type} (e.g. fx make:${type} ${name} --app=fuzionx)`);
|
|
97
97
|
dir = `app/${appName}/${APP_TYPE_DIRS[type]}`;
|
|
98
98
|
} else if (SHARED_TYPE_DIRS[type]) {
|
|
99
99
|
dir = SHARED_TYPE_DIRS[type];
|
|
@@ -127,42 +127,51 @@ const APP_FILES = [
|
|
|
127
127
|
{ tpl: 'app/package.json.tpl', dest: 'package.json' },
|
|
128
128
|
{ tpl: 'app/fuzionx.yaml.tpl', dest: 'fuzionx.yaml' },
|
|
129
129
|
{ tpl: 'app/app.js.tpl', dest: 'app.js' },
|
|
130
|
-
{ tpl: 'app/routes/web.js.tpl', dest: 'app/backend/routes/web.js' },
|
|
131
|
-
{ tpl: 'app/routes/api.js.tpl', dest: 'app/backend/routes/api.js' },
|
|
132
130
|
{ tpl: 'app/.env.example.tpl', dest: '.env.example' },
|
|
133
131
|
{ tpl: 'app/.env.example.tpl', dest: '.env' },
|
|
134
132
|
{ tpl: 'app/.gitignore.tpl', dest: '.gitignore' },
|
|
135
133
|
];
|
|
136
134
|
|
|
137
|
-
/**
|
|
135
|
+
/** fuzionx 앱 템플릿 (.tpl 파일은 치환, 그 외는 직접 복사) */
|
|
136
|
+
const FUZIONX_TPL_FILES = [
|
|
137
|
+
{ tpl: 'app/fuzionx/routes/web.js.tpl', dest: 'app/fuzionx/routes/web.js' },
|
|
138
|
+
{ tpl: 'app/fuzionx/routes/api.js.tpl', dest: 'app/fuzionx/routes/api.js' },
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
/** 디렉토리 재귀 복사 (템플릿 → 프로젝트) */
|
|
142
|
+
async function copyDirRecursive(src, dst) {
|
|
143
|
+
await fs.mkdir(dst, { recursive: true });
|
|
144
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const srcPath = path.join(src, entry.name);
|
|
147
|
+
const dstPath = path.join(dst, entry.name);
|
|
148
|
+
if (entry.isDirectory()) {
|
|
149
|
+
await copyDirRecursive(srcPath, dstPath);
|
|
150
|
+
} else {
|
|
151
|
+
await fs.copyFile(srcPath, dstPath);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 스캐폴딩 디렉토리 (빈 디렉토리 + .gitkeep) */
|
|
138
157
|
const APP_DIRS = [
|
|
139
|
-
//
|
|
140
|
-
'app/
|
|
141
|
-
'app/
|
|
142
|
-
'app/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'app/backend/views/default/layouts',
|
|
146
|
-
'app/backend/views/default/errors',
|
|
147
|
-
'app/backend/ws',
|
|
148
|
-
// 공유
|
|
149
|
-
'database/models',
|
|
158
|
+
// fuzionx 앱 (개발자 작업 영역)
|
|
159
|
+
'app/fuzionx/services',
|
|
160
|
+
'app/fuzionx/middleware',
|
|
161
|
+
'app/fuzionx/ws',
|
|
162
|
+
// tester 앱 (빈 폴더가 아닌 것은 copyDirRecursive로 처리)
|
|
163
|
+
// 인프라
|
|
150
164
|
'database/migrations',
|
|
151
165
|
'database/seeds',
|
|
152
|
-
'shared/events',
|
|
153
|
-
'shared/jobs',
|
|
154
|
-
'shared/workers',
|
|
155
|
-
// 인프라
|
|
156
166
|
'storage/logs',
|
|
157
167
|
'storage/uploads',
|
|
158
168
|
'public/css',
|
|
159
169
|
'public/js',
|
|
160
|
-
'locales',
|
|
161
170
|
'tests',
|
|
162
171
|
];
|
|
163
172
|
|
|
164
173
|
/**
|
|
165
|
-
* fx new <name> — 앱 스캐폴딩
|
|
174
|
+
* fx new <name> — 앱 스캐폴딩 (fuzionx + tester 2-app 구조)
|
|
166
175
|
* @param {string} name - 프로젝트 이름
|
|
167
176
|
* @param {string} [targetDir] - 기본: ./<name>
|
|
168
177
|
*/
|
|
@@ -173,17 +182,17 @@ export async function createApp(name, targetDir) {
|
|
|
173
182
|
dbName: name.replace(/-/g, '_'),
|
|
174
183
|
};
|
|
175
184
|
|
|
176
|
-
// 디렉토리 생성
|
|
185
|
+
// 빈 디렉토리 생성 (.gitkeep 포함)
|
|
177
186
|
for (const d of APP_DIRS) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const files = await fs.readdir(
|
|
187
|
+
const fullDir = path.join(dir, d);
|
|
188
|
+
await fs.mkdir(fullDir, { recursive: true });
|
|
189
|
+
const files = await fs.readdir(fullDir).catch(() => []);
|
|
181
190
|
if (files.length === 0) {
|
|
182
|
-
await fs.writeFile(path.join(
|
|
191
|
+
await fs.writeFile(path.join(fullDir, '.gitkeep'), '');
|
|
183
192
|
}
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
// 템플릿 파일 생성
|
|
195
|
+
// 루트 템플릿 파일 생성 (.tpl 치환)
|
|
187
196
|
for (const { tpl, dest } of APP_FILES) {
|
|
188
197
|
const content = await loadTemplate(tpl, vars);
|
|
189
198
|
const fullPath = path.join(dir, dest);
|
|
@@ -191,36 +200,40 @@ export async function createApp(name, targetDir) {
|
|
|
191
200
|
await fs.writeFile(fullPath, content);
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
//
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const viewsDst = path.join(dir, 'app/backend/views/default');
|
|
203
|
+
// fuzionx 앱 — .tpl 파일 (치환 필요)
|
|
204
|
+
for (const { tpl, dest } of FUZIONX_TPL_FILES) {
|
|
205
|
+
const content = await loadTemplate(tpl, vars);
|
|
206
|
+
const fullPath = path.join(dir, dest);
|
|
207
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
208
|
+
await fs.writeFile(fullPath, content);
|
|
209
|
+
}
|
|
202
210
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
path.join(
|
|
211
|
+
// fuzionx 앱 — 직접 복사 (controllers, views)
|
|
212
|
+
const fuzionxSrc = path.join(TPL_DIR, 'app/fuzionx');
|
|
213
|
+
await copyDirRecursive(
|
|
214
|
+
path.join(fuzionxSrc, 'controllers'),
|
|
215
|
+
path.join(dir, 'app/fuzionx/controllers'),
|
|
207
216
|
);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
path.join(viewsSrc, 'pages/home.html'),
|
|
212
|
-
path.join(viewsDst, 'pages/home.html'),
|
|
217
|
+
await copyDirRecursive(
|
|
218
|
+
path.join(fuzionxSrc, 'views'),
|
|
219
|
+
path.join(dir, 'app/fuzionx/views'),
|
|
213
220
|
);
|
|
214
221
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
// tester 앱 — 전체 복사
|
|
223
|
+
const testerSrc = path.join(TPL_DIR, 'app/tester');
|
|
224
|
+
await copyDirRecursive(testerSrc, path.join(dir, 'app/tester'));
|
|
225
|
+
|
|
226
|
+
// shared — 전체 복사
|
|
227
|
+
const sharedSrc = path.join(TPL_DIR, 'app/shared');
|
|
228
|
+
await copyDirRecursive(sharedSrc, path.join(dir, 'shared'));
|
|
229
|
+
|
|
230
|
+
// database/models — 복사
|
|
231
|
+
const dbSrc = path.join(TPL_DIR, 'app/database');
|
|
232
|
+
await copyDirRecursive(dbSrc, path.join(dir, 'database'));
|
|
233
|
+
|
|
234
|
+
// locales — 복사
|
|
235
|
+
const localesSrc = path.join(TPL_DIR, 'app/locales');
|
|
236
|
+
await copyDirRecursive(localesSrc, path.join(dir, 'locales'));
|
|
224
237
|
|
|
225
238
|
return dir;
|
|
226
239
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MariaModel } from '@fuzionx/framework';
|
|
2
|
+
|
|
3
|
+
export default class User extends MariaModel {
|
|
4
|
+
static table = 'users';
|
|
5
|
+
static connection = 'main';
|
|
6
|
+
static softDelete = true;
|
|
7
|
+
static hidden = ['password'];
|
|
8
|
+
static fillable = ['name', 'email', 'password', 'role'];
|
|
9
|
+
}
|
|
@@ -196,7 +196,7 @@ app:
|
|
|
196
196
|
# prefix: "myapp:queue"
|
|
197
197
|
|
|
198
198
|
# ── Multi-App (도메인 → 앱 라우팅) ────────────────
|
|
199
|
+
# tester를 실행하려면 fuzionx를 주석 처리하고 tester 주석을 해제하세요.
|
|
199
200
|
apps:
|
|
200
|
-
"
|
|
201
|
-
# "
|
|
202
|
-
# "example.com": frontend
|
|
201
|
+
"127.0.0.1:49080": fuzionx
|
|
202
|
+
# "127.0.0.1:49080": tester
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "FuzionX Test App",
|
|
4
|
+
"welcome": "Welcome to FuzionX",
|
|
5
|
+
"description": "Full-stack framework testing environment"
|
|
6
|
+
},
|
|
7
|
+
"nav": {
|
|
8
|
+
"home": "Home",
|
|
9
|
+
"dashboard": "Dashboard",
|
|
10
|
+
"users": "Users",
|
|
11
|
+
"upload": "Upload",
|
|
12
|
+
"websocket": "WebSocket",
|
|
13
|
+
"docs": "API Docs",
|
|
14
|
+
"settings": "Settings"
|
|
15
|
+
},
|
|
16
|
+
"user": {
|
|
17
|
+
"name": "Name",
|
|
18
|
+
"email": "Email",
|
|
19
|
+
"role": "Role",
|
|
20
|
+
"create": "Create User",
|
|
21
|
+
"edit": "Edit",
|
|
22
|
+
"delete": "Delete",
|
|
23
|
+
"list": "User List",
|
|
24
|
+
"created": "User has been created",
|
|
25
|
+
"deleted": "User has been deleted",
|
|
26
|
+
"not_found": "User not found"
|
|
27
|
+
},
|
|
28
|
+
"auth": {
|
|
29
|
+
"login": "Login",
|
|
30
|
+
"logout": "Logout",
|
|
31
|
+
"login_required": "Login required",
|
|
32
|
+
"invalid_credentials": "Invalid credentials"
|
|
33
|
+
},
|
|
34
|
+
"common": {
|
|
35
|
+
"save": "Save",
|
|
36
|
+
"cancel": "Cancel",
|
|
37
|
+
"confirm": "Confirm",
|
|
38
|
+
"search": "Search",
|
|
39
|
+
"loading": "Loading...",
|
|
40
|
+
"error": "An error occurred",
|
|
41
|
+
"success": "Success",
|
|
42
|
+
"greeting": "Hello, {name}!",
|
|
43
|
+
"items_count": "{count} items total"
|
|
44
|
+
},
|
|
45
|
+
"upload": {
|
|
46
|
+
"select_file": "Select a file",
|
|
47
|
+
"drag_drop": "Drag & drop or click to select",
|
|
48
|
+
"uploading": "Uploading...",
|
|
49
|
+
"complete": "Upload complete",
|
|
50
|
+
"error": "Upload failed"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "FuzionX 테스트 앱",
|
|
4
|
+
"welcome": "FuzionX에 오신 것을 환영합니다",
|
|
5
|
+
"description": "풀스택 프레임워크 테스트 환경입니다"
|
|
6
|
+
},
|
|
7
|
+
"nav": {
|
|
8
|
+
"home": "홈",
|
|
9
|
+
"dashboard": "대시보드",
|
|
10
|
+
"users": "사용자",
|
|
11
|
+
"upload": "업로드",
|
|
12
|
+
"websocket": "웹소켓",
|
|
13
|
+
"docs": "API 문서",
|
|
14
|
+
"settings": "설정"
|
|
15
|
+
},
|
|
16
|
+
"user": {
|
|
17
|
+
"name": "이름",
|
|
18
|
+
"email": "이메일",
|
|
19
|
+
"role": "역할",
|
|
20
|
+
"create": "사용자 생성",
|
|
21
|
+
"edit": "수정",
|
|
22
|
+
"delete": "삭제",
|
|
23
|
+
"list": "사용자 목록",
|
|
24
|
+
"created": "사용자가 생성되었습니다",
|
|
25
|
+
"deleted": "사용자가 삭제되었습니다",
|
|
26
|
+
"not_found": "사용자를 찾을 수 없습니다"
|
|
27
|
+
},
|
|
28
|
+
"auth": {
|
|
29
|
+
"login": "로그인",
|
|
30
|
+
"logout": "로그아웃",
|
|
31
|
+
"login_required": "로그인이 필요합니다",
|
|
32
|
+
"invalid_credentials": "잘못된 인증 정보입니다"
|
|
33
|
+
},
|
|
34
|
+
"common": {
|
|
35
|
+
"save": "저장",
|
|
36
|
+
"cancel": "취소",
|
|
37
|
+
"confirm": "확인",
|
|
38
|
+
"search": "검색",
|
|
39
|
+
"loading": "로딩중...",
|
|
40
|
+
"error": "오류가 발생했습니다",
|
|
41
|
+
"success": "성공",
|
|
42
|
+
"greeting": "{name}님, 안녕하세요!",
|
|
43
|
+
"items_count": "총 {count}개 항목"
|
|
44
|
+
},
|
|
45
|
+
"upload": {
|
|
46
|
+
"select_file": "파일을 선택하세요",
|
|
47
|
+
"drag_drop": "파일을 드래그하거나 클릭하세요",
|
|
48
|
+
"uploading": "업로드 중...",
|
|
49
|
+
"complete": "업로드 완료",
|
|
50
|
+
"error": "업로드 실패"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 이벤트 리스너 등록 */
|
|
2
|
+
export default (app) => {
|
|
3
|
+
app.on('user:created', (data) => {
|
|
4
|
+
console.log(`[Event] user:created → id=${data.id}, name=${data.name}`);
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
app.on('user:deleted', (data) => {
|
|
8
|
+
console.log(`[Event] user:deleted → id=${data.id}`);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Job } from '@fuzionx/framework';
|
|
2
|
+
|
|
3
|
+
/** 매 5분마다 만료 세션 정리 (스케줄러 테스트) */
|
|
4
|
+
export default class CleanupJob extends Job {
|
|
5
|
+
static schedule = 'every:5m';
|
|
6
|
+
static timeout = 10000;
|
|
7
|
+
static enabled = true;
|
|
8
|
+
|
|
9
|
+
async handle() {
|
|
10
|
+
const cleaned = Math.floor(Math.random() * 10);
|
|
11
|
+
console.log(`[CleanupJob] ${cleaned}개 만료 세션 정리 완료`);
|
|
12
|
+
return { cleaned };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async onError(error) {
|
|
16
|
+
console.error('[CleanupJob] 실패:', error.message);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Task } from '@fuzionx/framework';
|
|
2
|
+
|
|
3
|
+
/** 이메일 전송 비동기 Task (큐 테스트) */
|
|
4
|
+
export default class EmailTask extends Task {
|
|
5
|
+
static queue = 'default';
|
|
6
|
+
static retries = 3;
|
|
7
|
+
static retryDelay = 1000;
|
|
8
|
+
|
|
9
|
+
async handle(data) {
|
|
10
|
+
console.log(`[EmailTask] 이메일 전송: to=${data.to}, subject=${data.subject}`);
|
|
11
|
+
return { sent: true, to: data.to };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async failed(data, error) {
|
|
15
|
+
console.error(`[EmailTask] 최종 실패: to=${data.to}, error=${error.message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Task } from '@fuzionx/framework';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 비디오 미리보기 생성 Task (큐 비동기 처리)
|
|
6
|
+
*
|
|
7
|
+
* 다중 썸네일 추출 + 스프라이트 시트 합성을 백그라운드에서 실행.
|
|
8
|
+
* 대용량 비디오에서 수백 장 추출 시 수 분 소요될 수 있으므로 큐로 위임.
|
|
9
|
+
*/
|
|
10
|
+
export default class VideoPreviewTask extends Task {
|
|
11
|
+
static queue = 'media';
|
|
12
|
+
static retries = 1;
|
|
13
|
+
static retryDelay = 3000;
|
|
14
|
+
static timeout = 600000; // 10분
|
|
15
|
+
|
|
16
|
+
async handle(data) {
|
|
17
|
+
const { filePath, outputDir, sheetPath, interval, width, cols } = data;
|
|
18
|
+
const app = this.app;
|
|
19
|
+
|
|
20
|
+
console.log(`[VideoPreview] 시작: interval=${interval}s, width=${width}px, file=${path.basename(filePath)}`);
|
|
21
|
+
|
|
22
|
+
// 1. 간격 기반 다중 썸네일 추출
|
|
23
|
+
const thumbs = app.media.videoThumbnails(filePath, outputDir, interval, width);
|
|
24
|
+
console.log(`[VideoPreview] 썸네일 ${thumbs.length}장 추출 완료`);
|
|
25
|
+
|
|
26
|
+
// 2. 스프라이트 시트 합성
|
|
27
|
+
if (thumbs.length > 0) {
|
|
28
|
+
app.media.videoPreviewSheet(thumbs, sheetPath, cols, Math.floor(width / 2));
|
|
29
|
+
console.log(`[VideoPreview] 스프라이트 시트 생성: ${sheetPath}`);
|
|
30
|
+
|
|
31
|
+
// 3. 워터마크 적용 (config에 경로가 있으면)
|
|
32
|
+
const wmPath = app.config.get('bridge.upload.watermark');
|
|
33
|
+
if (wmPath) {
|
|
34
|
+
const wmOpacity = app.config.get('bridge.upload.watermark_opacity', 50);
|
|
35
|
+
app.media.applyWatermark(sheetPath, wmPath, wmOpacity, 'jpeg', 90);
|
|
36
|
+
console.log(`[VideoPreview] 워터마크 적용 완료`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`[VideoPreview] 완료: ${thumbs.length}장, sheet=${sheetPath}`);
|
|
41
|
+
return { count: thumbs.length, sheetPath };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async failed(data, error) {
|
|
45
|
+
console.error(`[VideoPreview] 최종 실패: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** CPU-intensive 워커 스크립트 */
|
|
2
|
+
import { parentPort, workerData } from 'worker_threads';
|
|
3
|
+
|
|
4
|
+
function heavyCompute(n) {
|
|
5
|
+
let result = 0;
|
|
6
|
+
for (let i = 0; i < n; i++) {
|
|
7
|
+
result += Math.sqrt(i) * Math.sin(i);
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const result = heavyCompute(workerData?.iterations || 1_000_000);
|
|
13
|
+
|
|
14
|
+
parentPort?.postMessage({
|
|
15
|
+
result,
|
|
16
|
+
iterations: workerData?.iterations || 1_000_000,
|
|
17
|
+
pid: process.pid,
|
|
18
|
+
});
|