@fuzionx/framework 0.1.43 → 0.1.44

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 +100 -100
  4. package/cli/index.js +494 -494
  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 +227 -227
  26. package/lib/core/Base.js +64 -64
  27. package/lib/core/Config.js +331 -331
  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,32 +1,32 @@
1
- /**
2
- * csrf — CSRF 보호 미들웨어
3
- *
4
- * GET/HEAD/OPTIONS 는 통과, 나머지는 토큰 검증.
5
- *
6
- * @see docs/framework/12-middleware.md
7
- *
8
- * @param {object} [opts]
9
- * @param {string} [opts.headerName='x-csrf-token']
10
- * @param {string} [opts.sessionKey='_csrfToken']
11
- */
12
- export function csrf(opts = {}) {
13
- const headerName = opts.headerName || 'x-csrf-token';
14
- const sessionKey = opts.sessionKey || '_csrfToken';
15
-
16
- return async (ctx, next) => {
17
- if (['GET', 'HEAD', 'OPTIONS'].includes(ctx.method)) {
18
- await next();
19
- return;
20
- }
21
-
22
- const token = ctx.get(headerName);
23
- const expected = ctx._rawSession?.[sessionKey];
24
-
25
- if (!token || !expected || token !== expected) {
26
- ctx.status(403).json({ error: { message: 'CSRF token mismatch', status: 403 } });
27
- return;
28
- }
29
-
30
- await next();
31
- };
32
- }
1
+ /**
2
+ * csrf — CSRF 보호 미들웨어
3
+ *
4
+ * GET/HEAD/OPTIONS 는 통과, 나머지는 토큰 검증.
5
+ *
6
+ * @see docs/framework/12-middleware.md
7
+ *
8
+ * @param {object} [opts]
9
+ * @param {string} [opts.headerName='x-csrf-token']
10
+ * @param {string} [opts.sessionKey='_csrfToken']
11
+ */
12
+ export function csrf(opts = {}) {
13
+ const headerName = opts.headerName || 'x-csrf-token';
14
+ const sessionKey = opts.sessionKey || '_csrfToken';
15
+
16
+ return async (ctx, next) => {
17
+ if (['GET', 'HEAD', 'OPTIONS'].includes(ctx.method)) {
18
+ await next();
19
+ return;
20
+ }
21
+
22
+ const token = ctx.get(headerName);
23
+ const expected = ctx._rawSession?.[sessionKey];
24
+
25
+ if (!token || !expected || token !== expected) {
26
+ ctx.status(403).json({ error: { message: 'CSRF token mismatch', status: 403 } });
27
+ return;
28
+ }
29
+
30
+ await next();
31
+ };
32
+ }
@@ -1,13 +1,13 @@
1
- /**
2
- * 내장 미들웨어 — 배럴 re-export
3
- *
4
- * @see docs/framework/12-middleware.md
5
- * @see docs/framework/14-authentication.md
6
- */
7
- export { bodyParser } from './bodyParser.js';
8
- export { cors } from './cors.js';
9
- export { auth } from './auth.js';
10
- export { apiAuth } from './apiAuth.js';
11
- export { csrf } from './csrf.js';
12
- export { session } from './session.js';
13
- export { theme } from './theme.js';
1
+ /**
2
+ * 내장 미들웨어 — 배럴 re-export
3
+ *
4
+ * @see docs/framework/12-middleware.md
5
+ * @see docs/framework/14-authentication.md
6
+ */
7
+ export { bodyParser } from './bodyParser.js';
8
+ export { cors } from './cors.js';
9
+ export { auth } from './auth.js';
10
+ export { apiAuth } from './apiAuth.js';
11
+ export { csrf } from './csrf.js';
12
+ export { session } from './session.js';
13
+ export { theme } from './theme.js';
@@ -1,27 +1,27 @@
1
- /**
2
- * session — 세션 로드/저장 미들웨어
3
- *
4
- * Bridge sessionGet/sessionSet 사용.
5
- *
6
- * @see docs/framework/12-middleware.md — "session: 세션 로드/저장"
7
- */
8
- export function session(opts = {}) {
9
- return async (ctx, next) => {
10
- // Session은 Context 생성 시 rawReq.session에서 이미 로드됨
11
- // Bridge가 rawReq에 session 데이터를 포함하여 전달
12
-
13
- await next();
14
-
15
- // 요청 완료 후 — 소비된 flash 데이터 정리
16
- const bridge = ctx.app?._bridge;
17
- const sessionId = ctx._sessionId;
18
- if (sessionId && bridge?.sessionSet && ctx._rawSession) {
19
- if (ctx._rawSession._flash && Object.keys(ctx._rawSession._flash).length === 0) {
20
- delete ctx._rawSession._flash;
21
- }
22
- try {
23
- bridge.sessionSet(sessionId, ctx._rawSession);
24
- } catch {}
25
- }
26
- };
27
- }
1
+ /**
2
+ * session — 세션 로드/저장 미들웨어
3
+ *
4
+ * Bridge sessionGet/sessionSet 사용.
5
+ *
6
+ * @see docs/framework/12-middleware.md — "session: 세션 로드/저장"
7
+ */
8
+ export function session(opts = {}) {
9
+ return async (ctx, next) => {
10
+ // Session은 Context 생성 시 rawReq.session에서 이미 로드됨
11
+ // Bridge가 rawReq에 session 데이터를 포함하여 전달
12
+
13
+ await next();
14
+
15
+ // 요청 완료 후 — 소비된 flash 데이터 정리
16
+ const bridge = ctx.app?._bridge;
17
+ const sessionId = ctx._sessionId;
18
+ if (sessionId && bridge?.sessionSet && ctx._rawSession) {
19
+ if (ctx._rawSession._flash && Object.keys(ctx._rawSession._flash).length === 0) {
20
+ delete ctx._rawSession._flash;
21
+ }
22
+ try {
23
+ bridge.sessionSet(sessionId, ctx._rawSession);
24
+ } catch {}
25
+ }
26
+ };
27
+ }
@@ -1,20 +1,20 @@
1
- /**
2
- * theme — 도메인 → 테마 매핑 미들웨어
3
- *
4
- * @see docs/framework/03-views-templates.md
5
- * @see docs/framework/12-middleware.md — "theme: 도메인 → 테마 매핑"
6
- *
7
- * @param {object} [opts]
8
- * @param {string} [opts.default='default'] - 기본 테마
9
- * @param {object} [opts.mapping] - { 'domain.com': 'theme1' }
10
- */
11
- export function theme(opts = {}) {
12
- const defaultTheme = opts.default || 'default';
13
-
14
- return async (ctx, next) => {
15
- const configDefault = ctx.app?.config?.get('app.themes.default') || defaultTheme;
16
- ctx.theme = configDefault;
17
-
18
- await next();
19
- };
20
- }
1
+ /**
2
+ * theme — 도메인 → 테마 매핑 미들웨어
3
+ *
4
+ * @see docs/framework/03-views-templates.md
5
+ * @see docs/framework/12-middleware.md — "theme: 도메인 → 테마 매핑"
6
+ *
7
+ * @param {object} [opts]
8
+ * @param {string} [opts.default='default'] - 기본 테마
9
+ * @param {object} [opts.mapping] - { 'domain.com': 'theme1' }
10
+ */
11
+ export function theme(opts = {}) {
12
+ const defaultTheme = opts.default || 'default';
13
+
14
+ return async (ctx, next) => {
15
+ const configDefault = ctx.app?.config?.get('app.themes.default') || defaultTheme;
16
+ ctx.theme = configDefault;
17
+
18
+ await next();
19
+ };
20
+ }
@@ -1,85 +1,85 @@
1
- /**
2
- * RoomManager — 전체 룸 상태 관리 (Primary 프로세스)
3
- *
4
- * @see docs/framework/class-design.mm.md (RoomManager)
5
- */
6
- export default class RoomManager {
7
- constructor() {
8
- /** @type {Map<string, Set<string>>} roomName → Set<socketId> */
9
- this._rooms = new Map();
10
- }
11
-
12
- /**
13
- * 룸에 참가
14
- * @param {string} room
15
- * @param {string} socketId
16
- */
17
- join(room, socketId) {
18
- if (!this._rooms.has(room)) {
19
- this._rooms.set(room, new Set());
20
- }
21
- this._rooms.get(room).add(socketId);
22
- }
23
-
24
- /**
25
- * 룸에서 퇴장
26
- * @param {string} room
27
- * @param {string} socketId
28
- */
29
- leave(room, socketId) {
30
- const members = this._rooms.get(room);
31
- if (!members) return;
32
- members.delete(socketId);
33
- if (members.size === 0) {
34
- this._rooms.delete(room);
35
- }
36
- }
37
-
38
- /**
39
- * 소켓이 속한 모든 룸에서 퇴장
40
- * @param {string} socketId
41
- */
42
- leaveAll(socketId) {
43
- for (const [room, members] of this._rooms) {
44
- members.delete(socketId);
45
- if (members.size === 0) {
46
- this._rooms.delete(room);
47
- }
48
- }
49
- }
50
-
51
- /**
52
- * 룸 멤버 조회
53
- * @param {string} room
54
- * @returns {string[]}
55
- */
56
- members(room) {
57
- const members = this._rooms.get(room);
58
- return members ? [...members] : [];
59
- }
60
-
61
- /**
62
- * 룸 존재 여부
63
- * @param {string} room
64
- * @returns {boolean}
65
- */
66
- has(room) {
67
- return this._rooms.has(room) && this._rooms.get(room).size > 0;
68
- }
69
-
70
- /**
71
- * 전체 룸 목록
72
- * @returns {string[]}
73
- */
74
- rooms() {
75
- return [...this._rooms.keys()];
76
- }
77
-
78
- /**
79
- * 룸 수
80
- * @returns {number}
81
- */
82
- get size() {
83
- return this._rooms.size;
84
- }
85
- }
1
+ /**
2
+ * RoomManager — 전체 룸 상태 관리 (Primary 프로세스)
3
+ *
4
+ * @see docs/framework/class-design.mm.md (RoomManager)
5
+ */
6
+ export default class RoomManager {
7
+ constructor() {
8
+ /** @type {Map<string, Set<string>>} roomName → Set<socketId> */
9
+ this._rooms = new Map();
10
+ }
11
+
12
+ /**
13
+ * 룸에 참가
14
+ * @param {string} room
15
+ * @param {string} socketId
16
+ */
17
+ join(room, socketId) {
18
+ if (!this._rooms.has(room)) {
19
+ this._rooms.set(room, new Set());
20
+ }
21
+ this._rooms.get(room).add(socketId);
22
+ }
23
+
24
+ /**
25
+ * 룸에서 퇴장
26
+ * @param {string} room
27
+ * @param {string} socketId
28
+ */
29
+ leave(room, socketId) {
30
+ const members = this._rooms.get(room);
31
+ if (!members) return;
32
+ members.delete(socketId);
33
+ if (members.size === 0) {
34
+ this._rooms.delete(room);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 소켓이 속한 모든 룸에서 퇴장
40
+ * @param {string} socketId
41
+ */
42
+ leaveAll(socketId) {
43
+ for (const [room, members] of this._rooms) {
44
+ members.delete(socketId);
45
+ if (members.size === 0) {
46
+ this._rooms.delete(room);
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 룸 멤버 조회
53
+ * @param {string} room
54
+ * @returns {string[]}
55
+ */
56
+ members(room) {
57
+ const members = this._rooms.get(room);
58
+ return members ? [...members] : [];
59
+ }
60
+
61
+ /**
62
+ * 룸 존재 여부
63
+ * @param {string} room
64
+ * @returns {boolean}
65
+ */
66
+ has(room) {
67
+ return this._rooms.has(room) && this._rooms.get(room).size > 0;
68
+ }
69
+
70
+ /**
71
+ * 전체 룸 목록
72
+ * @returns {string[]}
73
+ */
74
+ rooms() {
75
+ return [...this._rooms.keys()];
76
+ }
77
+
78
+ /**
79
+ * 룸 수
80
+ * @returns {number}
81
+ */
82
+ get size() {
83
+ return this._rooms.size;
84
+ }
85
+ }
@@ -1,107 +1,107 @@
1
- /**
2
- * WsHandler — WebSocket 네임스페이스 핸들러
3
- *
4
- * @see docs/framework/11-websocket.md
5
- * @see docs/framework/class-design.mm.md (WsHandler + EventBuilder)
6
- */
7
- import Base from '../core/Base.js';
8
-
9
- /**
10
- * EventBuilder — static events(e) DSL에서 사용
11
- *
12
- * @example
13
- * static events(e) {
14
- * e.on('chat', this.handleChat);
15
- * e.on('typing', this.handleTyping, ['rateLimit:10']);
16
- * }
17
- */
18
- export class EventBuilder {
19
- constructor() {
20
- this._events = [];
21
- }
22
-
23
- /**
24
- * 이벤트 핸들러 등록
25
- * @param {string} type - 이벤트 타입
26
- * @param {Function|object} handler - 핸들러 메서드 레퍼런스
27
- * @param {string[]} [middleware] - 이벤트별 미들웨어
28
- */
29
- on(type, handler, middleware = []) {
30
- this._events.push({ type, handler, middleware });
31
- }
32
- }
33
-
34
- export default class WsHandler extends Base {
35
- /** @type {string} 네임스페이스 경로 (e.g. '/chat') */
36
- static namespace = '/';
37
-
38
- /** @type {string[]} 핸드셰이크 미들웨어 */
39
- static middleware = [];
40
-
41
- /**
42
- * 이벤트 라우팅 선언 (서브클래스에서 오버라이드)
43
- * @param {EventBuilder} e
44
- */
45
- static events(e) {
46
- // 서브클래스에서:
47
- // e.on('chat', this.handleChat);
48
- // e.on('typing', this.handleTyping, ['rateLimit:10']);
49
- }
50
-
51
- /**
52
- * 이벤트 라우팅 맵 빌드
53
- * @returns {Map<string, {handler, middleware}>}
54
- */
55
- static buildEventMap() {
56
- const builder = new EventBuilder();
57
- this.events(builder);
58
- const map = new Map();
59
- for (const { type, handler, middleware } of builder._events) {
60
- map.set(type, { handler, middleware });
61
- }
62
- return map;
63
- }
64
-
65
- // ── 라이프사이클 훅 ──
66
-
67
- /**
68
- * 연결 시 (서브클래스에서 오버라이드)
69
- * @param {object} socket
70
- */
71
- async onConnect(socket) {}
72
-
73
- /**
74
- * 이벤트 핸들러 (서브클래스에서 오버라이드)
75
- * @param {object} socket
76
- * @param {string} event
77
- * @param {*} data
78
- */
79
- async onEvent(socket, event, data) {}
80
-
81
- /**
82
- * 연결 해제 시 (서브클래스에서 오버라이드)
83
- * @param {object} socket
84
- * @param {number} code
85
- * @param {string} reason
86
- */
87
- async onDisconnect(socket, code, reason) {}
88
-
89
- /**
90
- * 미등록 이벤트 수신 시 (서브클래스에서 오버라이드)
91
- * @param {object} socket
92
- * @param {string} type
93
- * @param {*} data
94
- */
95
- async onUnknownEvent(socket, type, data) {
96
- // 기본: 무시 (서브클래스에서 오버라이드 가능)
97
- }
98
-
99
- /**
100
- * 에러 시 (서브클래스에서 오버라이드)
101
- * @param {object} socket
102
- * @param {Error} error
103
- */
104
- async onError(socket, error) {
105
- this.logger?.error(`WS error in ${this.constructor.namespace}:`, error);
106
- }
107
- }
1
+ /**
2
+ * WsHandler — WebSocket 네임스페이스 핸들러
3
+ *
4
+ * @see docs/framework/11-websocket.md
5
+ * @see docs/framework/class-design.mm.md (WsHandler + EventBuilder)
6
+ */
7
+ import Base from '../core/Base.js';
8
+
9
+ /**
10
+ * EventBuilder — static events(e) DSL에서 사용
11
+ *
12
+ * @example
13
+ * static events(e) {
14
+ * e.on('chat', this.handleChat);
15
+ * e.on('typing', this.handleTyping, ['rateLimit:10']);
16
+ * }
17
+ */
18
+ export class EventBuilder {
19
+ constructor() {
20
+ this._events = [];
21
+ }
22
+
23
+ /**
24
+ * 이벤트 핸들러 등록
25
+ * @param {string} type - 이벤트 타입
26
+ * @param {Function|object} handler - 핸들러 메서드 레퍼런스
27
+ * @param {string[]} [middleware] - 이벤트별 미들웨어
28
+ */
29
+ on(type, handler, middleware = []) {
30
+ this._events.push({ type, handler, middleware });
31
+ }
32
+ }
33
+
34
+ export default class WsHandler extends Base {
35
+ /** @type {string} 네임스페이스 경로 (e.g. '/chat') */
36
+ static namespace = '/';
37
+
38
+ /** @type {string[]} 핸드셰이크 미들웨어 */
39
+ static middleware = [];
40
+
41
+ /**
42
+ * 이벤트 라우팅 선언 (서브클래스에서 오버라이드)
43
+ * @param {EventBuilder} e
44
+ */
45
+ static events(e) {
46
+ // 서브클래스에서:
47
+ // e.on('chat', this.handleChat);
48
+ // e.on('typing', this.handleTyping, ['rateLimit:10']);
49
+ }
50
+
51
+ /**
52
+ * 이벤트 라우팅 맵 빌드
53
+ * @returns {Map<string, {handler, middleware}>}
54
+ */
55
+ static buildEventMap() {
56
+ const builder = new EventBuilder();
57
+ this.events(builder);
58
+ const map = new Map();
59
+ for (const { type, handler, middleware } of builder._events) {
60
+ map.set(type, { handler, middleware });
61
+ }
62
+ return map;
63
+ }
64
+
65
+ // ── 라이프사이클 훅 ──
66
+
67
+ /**
68
+ * 연결 시 (서브클래스에서 오버라이드)
69
+ * @param {object} socket
70
+ */
71
+ async onConnect(socket) {}
72
+
73
+ /**
74
+ * 이벤트 핸들러 (서브클래스에서 오버라이드)
75
+ * @param {object} socket
76
+ * @param {string} event
77
+ * @param {*} data
78
+ */
79
+ async onEvent(socket, event, data) {}
80
+
81
+ /**
82
+ * 연결 해제 시 (서브클래스에서 오버라이드)
83
+ * @param {object} socket
84
+ * @param {number} code
85
+ * @param {string} reason
86
+ */
87
+ async onDisconnect(socket, code, reason) {}
88
+
89
+ /**
90
+ * 미등록 이벤트 수신 시 (서브클래스에서 오버라이드)
91
+ * @param {object} socket
92
+ * @param {string} type
93
+ * @param {*} data
94
+ */
95
+ async onUnknownEvent(socket, type, data) {
96
+ // 기본: 무시 (서브클래스에서 오버라이드 가능)
97
+ }
98
+
99
+ /**
100
+ * 에러 시 (서브클래스에서 오버라이드)
101
+ * @param {object} socket
102
+ * @param {Error} error
103
+ */
104
+ async onError(socket, error) {
105
+ this.logger?.error(`WS error in ${this.constructor.namespace}:`, error);
106
+ }
107
+ }
@@ -1,38 +1,38 @@
1
- /**
2
- * Job — 정기 실행 작업 (cron 스케줄)
3
- *
4
- * @see docs/framework/10-scheduler-queue.md
5
- * @see docs/framework/class-design.mm.md (Job)
6
- */
7
- import Base from '../core/Base.js';
8
-
9
- export default class Job extends Base {
10
- /** @type {string} cron 표현식 또는 간편 표현 (every:5m, daily:02:00) */
11
- static schedule = '';
12
-
13
- /** @type {number} 타임아웃 (ms), 기본 30초 */
14
- static timeout = 30000;
15
-
16
- /** @type {boolean} false면 스킵 */
17
- static enabled = true;
18
-
19
- /**
20
- * Job 실행 (서브클래스에서 오버라이드)
21
- *
22
- * 메인 스레드에서 실행되므로 this.db, this.service() 등 접근 가능.
23
- * CPU-intensive 작업은 this.worker.run() 또는 this.worker.exec()으로 위임.
24
- *
25
- * @returns {Promise<void>}
26
- */
27
- async handle() {
28
- throw new Error(`${this.constructor.name}.handle() must be implemented`);
29
- }
30
-
31
- /**
32
- * Job 실패 시 호출 (서브클래스에서 오버라이드)
33
- * @param {Error} error
34
- */
35
- async onError(error) {
36
- this.logger.error(`Job ${this.constructor.name} failed:`, error);
37
- }
38
- }
1
+ /**
2
+ * Job — 정기 실행 작업 (cron 스케줄)
3
+ *
4
+ * @see docs/framework/10-scheduler-queue.md
5
+ * @see docs/framework/class-design.mm.md (Job)
6
+ */
7
+ import Base from '../core/Base.js';
8
+
9
+ export default class Job extends Base {
10
+ /** @type {string} cron 표현식 또는 간편 표현 (every:5m, daily:02:00) */
11
+ static schedule = '';
12
+
13
+ /** @type {number} 타임아웃 (ms), 기본 30초 */
14
+ static timeout = 30000;
15
+
16
+ /** @type {boolean} false면 스킵 */
17
+ static enabled = true;
18
+
19
+ /**
20
+ * Job 실행 (서브클래스에서 오버라이드)
21
+ *
22
+ * 메인 스레드에서 실행되므로 this.db, this.service() 등 접근 가능.
23
+ * CPU-intensive 작업은 this.worker.run() 또는 this.worker.exec()으로 위임.
24
+ *
25
+ * @returns {Promise<void>}
26
+ */
27
+ async handle() {
28
+ throw new Error(`${this.constructor.name}.handle() must be implemented`);
29
+ }
30
+
31
+ /**
32
+ * Job 실패 시 호출 (서브클래스에서 오버라이드)
33
+ * @param {Error} error
34
+ */
35
+ async onError(error) {
36
+ this.logger.error(`Job ${this.constructor.name} failed:`, error);
37
+ }
38
+ }