@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,103 +1,103 @@
1
- /**
2
- * Queue — Task 큐 관리 (Memory/Redis)
3
- *
4
- * AutoLoader가 shared/jobs/ 에서 Task 클래스를 자동 스캔·등록.
5
- * handle(data)은 메인 스레드에서 실행되므로 this.db, this.service() 등
6
- * 앱 컨텍스트에 접근 가능합니다.
7
- *
8
- * CPU-intensive 작업은 handle() 내에서 this.worker.run()으로 위임.
9
- *
10
- * @see docs/framework/10-scheduler-queue.md
11
- * @see lib/schedule/WorkerPool.js
12
- */
13
- export default class Queue {
14
- /**
15
- * @param {import('../core/Application.js').default} app
16
- * @param {object} [opts]
17
- * @param {string} [opts.driver='memory'] - 'memory' | 'redis'
18
- */
19
- constructor(app, opts = {}) {
20
- this.app = app;
21
- this.driver = opts.driver || 'memory';
22
- this._tasks = new Map(); // name → TaskClass
23
- this._queue = [];
24
- this._processing = false;
25
- }
26
-
27
- /**
28
- * Task 클래스 등록
29
- * @param {string} name
30
- * @param {typeof import('./Task.js').default} TaskClass
31
- */
32
- register(name, TaskClass) {
33
- this._tasks.set(name, TaskClass);
34
- }
35
-
36
- /**
37
- * Task 디스패치 (큐에 추가)
38
- * @param {string|Function} taskOrName
39
- * @param {object} data
40
- * @param {object} [opts]
41
- */
42
- dispatch(taskOrName, data, opts = {}) {
43
- const name = typeof taskOrName === 'string' ? taskOrName : taskOrName.name;
44
- this._queue.push({ name, data, opts, retries: 0 });
45
-
46
- // auto-process (비동기)
47
- if (!this._processing) {
48
- this._process();
49
- }
50
- }
51
-
52
- /**
53
- * 큐 처리 루프
54
- * @private
55
- */
56
- async _process() {
57
- this._processing = true;
58
-
59
- while (this._queue.length > 0) {
60
- const job = this._queue.shift();
61
- const TaskClass = this._tasks.get(job.name);
62
-
63
- if (!TaskClass) {
64
- this.app?.logger?.error?.(`[Queue] Task '${job.name}' not registered`);
65
- continue;
66
- }
67
-
68
- const timeout = TaskClass.timeout || 30000;
69
- const task = new TaskClass(this.app);
70
-
71
- try {
72
- let timer;
73
- await Promise.race([
74
- task.handle(job.data).finally(() => clearTimeout(timer)),
75
- new Promise((_, reject) => {
76
- timer = setTimeout(() => reject(new Error(`Task timeout (${timeout}ms)`)), timeout);
77
- }),
78
- ]);
79
- } catch (err) {
80
- job.retries++;
81
- if (job.retries < (TaskClass.retries || 3)) {
82
- const delay = TaskClass.retryDelay || 1000;
83
- setTimeout(() => {
84
- this._queue.push(job);
85
- if (!this._processing) this._process();
86
- }, delay);
87
- } else {
88
- try { await task.failed(job.data, err); } catch {}
89
- }
90
- }
91
- }
92
-
93
- this._processing = false;
94
- }
95
-
96
- /**
97
- * 남은 큐 수
98
- * @returns {number}
99
- */
100
- get pending() {
101
- return this._queue.length;
102
- }
103
- }
1
+ /**
2
+ * Queue — Task 큐 관리 (Memory/Redis)
3
+ *
4
+ * AutoLoader가 shared/jobs/ 에서 Task 클래스를 자동 스캔·등록.
5
+ * handle(data)은 메인 스레드에서 실행되므로 this.db, this.service() 등
6
+ * 앱 컨텍스트에 접근 가능합니다.
7
+ *
8
+ * CPU-intensive 작업은 handle() 내에서 this.worker.run()으로 위임.
9
+ *
10
+ * @see docs/framework/10-scheduler-queue.md
11
+ * @see lib/schedule/WorkerPool.js
12
+ */
13
+ export default class Queue {
14
+ /**
15
+ * @param {import('../core/Application.js').default} app
16
+ * @param {object} [opts]
17
+ * @param {string} [opts.driver='memory'] - 'memory' | 'redis'
18
+ */
19
+ constructor(app, opts = {}) {
20
+ this.app = app;
21
+ this.driver = opts.driver || 'memory';
22
+ this._tasks = new Map(); // name → TaskClass
23
+ this._queue = [];
24
+ this._processing = false;
25
+ }
26
+
27
+ /**
28
+ * Task 클래스 등록
29
+ * @param {string} name
30
+ * @param {typeof import('./Task.js').default} TaskClass
31
+ */
32
+ register(name, TaskClass) {
33
+ this._tasks.set(name, TaskClass);
34
+ }
35
+
36
+ /**
37
+ * Task 디스패치 (큐에 추가)
38
+ * @param {string|Function} taskOrName
39
+ * @param {object} data
40
+ * @param {object} [opts]
41
+ */
42
+ dispatch(taskOrName, data, opts = {}) {
43
+ const name = typeof taskOrName === 'string' ? taskOrName : taskOrName.name;
44
+ this._queue.push({ name, data, opts, retries: 0 });
45
+
46
+ // auto-process (비동기)
47
+ if (!this._processing) {
48
+ this._process();
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 큐 처리 루프
54
+ * @private
55
+ */
56
+ async _process() {
57
+ this._processing = true;
58
+
59
+ while (this._queue.length > 0) {
60
+ const job = this._queue.shift();
61
+ const TaskClass = this._tasks.get(job.name);
62
+
63
+ if (!TaskClass) {
64
+ this.app?.logger?.error?.(`[Queue] Task '${job.name}' not registered`);
65
+ continue;
66
+ }
67
+
68
+ const timeout = TaskClass.timeout || 30000;
69
+ const task = new TaskClass(this.app);
70
+
71
+ try {
72
+ let timer;
73
+ await Promise.race([
74
+ task.handle(job.data).finally(() => clearTimeout(timer)),
75
+ new Promise((_, reject) => {
76
+ timer = setTimeout(() => reject(new Error(`Task timeout (${timeout}ms)`)), timeout);
77
+ }),
78
+ ]);
79
+ } catch (err) {
80
+ job.retries++;
81
+ if (job.retries < (TaskClass.retries || 3)) {
82
+ const delay = TaskClass.retryDelay || 1000;
83
+ setTimeout(() => {
84
+ this._queue.push(job);
85
+ if (!this._processing) this._process();
86
+ }, delay);
87
+ } else {
88
+ try { await task.failed(job.data, err); } catch {}
89
+ }
90
+ }
91
+ }
92
+
93
+ this._processing = false;
94
+ }
95
+
96
+ /**
97
+ * 남은 큐 수
98
+ * @returns {number}
99
+ */
100
+ get pending() {
101
+ return this._queue.length;
102
+ }
103
+ }
@@ -1,171 +1,171 @@
1
- /**
2
- * Scheduler — 정기 실행 작업 관리 (cron 스케줄)
3
- *
4
- * AutoLoader가 shared/jobs/ 에서 Job 클래스를 자동 스캔·등록.
5
- * handle()은 메인 스레드에서 실행되므로 this.db, this.service() 등
6
- * 앱 컨텍스트에 접근 가능합니다.
7
- *
8
- * CPU-intensive 작업은 handle() 내에서 this.worker.run()으로 위임.
9
- *
10
- * @see docs/framework/10-scheduler-queue.md
11
- * @see lib/schedule/WorkerPool.js
12
- */
13
- export default class Scheduler {
14
- /**
15
- * @param {import('../core/Application.js').default} app
16
- */
17
- constructor(app) {
18
- this.app = app;
19
- /** @type {Array<{JobClass: typeof import('./Job.js').default}>} */
20
- this._jobs = [];
21
- /** @type {Array<NodeJS.Timeout>} */
22
- this._timers = [];
23
- }
24
-
25
- /**
26
- * Job 클래스 등록 (AutoLoader에서 호출)
27
- * @param {typeof import('./Job.js').default} JobClass
28
- */
29
- register(JobClass) {
30
- if (!JobClass.schedule) return;
31
- if (JobClass.enabled === false) return;
32
- this._jobs.push({ JobClass });
33
- }
34
-
35
- /**
36
- * 등록된 모든 Job 시작
37
- */
38
- start() {
39
- for (const { JobClass } of this._jobs) {
40
- const interval = this._parseSchedule(JobClass.schedule);
41
- if (!interval) {
42
- this.app?.logger?.warn?.(`[Scheduler] Invalid schedule: ${JobClass.name} → '${JobClass.schedule}'`);
43
- continue;
44
- }
45
-
46
- const timeout = JobClass.timeout || 30000;
47
-
48
- const runJob = async () => {
49
- const job = new JobClass(this.app);
50
- try {
51
- let timer;
52
- await Promise.race([
53
- job.handle().finally(() => clearTimeout(timer)),
54
- new Promise((_, reject) => {
55
- timer = setTimeout(() => reject(new Error(`Job timeout (${timeout}ms)`)), timeout);
56
- }),
57
- ]);
58
- } catch (err) {
59
- if (typeof job.onError === 'function') {
60
- try { await job.onError(err); } catch {}
61
- } else {
62
- this.app?.logger?.error?.(`[Scheduler] ${JobClass.name} failed: ${err.message}`);
63
- }
64
- }
65
- };
66
-
67
- // daily/weekly → 첫 실행까지 지연 계산
68
- const initialDelay = this._calcInitialDelay(JobClass.schedule);
69
- if (initialDelay > 0) {
70
- const delayed = setTimeout(() => {
71
- runJob();
72
- const timer = setInterval(runJob, interval);
73
- this._timers.push(timer);
74
- }, initialDelay);
75
- this._timers.push(delayed);
76
- } else {
77
- const timer = setInterval(runJob, interval);
78
- this._timers.push(timer);
79
- // 즉시 첫 실행
80
- runJob();
81
- }
82
-
83
- this.app?.logger?.info?.(
84
- `[Scheduler] ${JobClass.name} registered (${JobClass.schedule}, interval=${interval}ms)`
85
- );
86
- }
87
- }
88
-
89
- /**
90
- * 모든 타이머 정지
91
- */
92
- stop() {
93
- for (const timer of this._timers) {
94
- clearInterval(timer);
95
- clearTimeout(timer);
96
- }
97
- this._timers = [];
98
- }
99
-
100
- // ── Schedule 파싱 ──
101
-
102
- /**
103
- * 간편 스케줄 → ms 변환
104
- *
105
- * 지원 포맷:
106
- * 'every:5s' → 5000ms
107
- * 'every:5m' → 300000ms
108
- * 'every:1h' → 3600000ms
109
- * 'daily:HH:MM' → 24h (첫 실행은 _calcInitialDelay)
110
- * 'weekly:DAY:HH:MM' → 7d (첫 실행은 _calcInitialDelay)
111
- */
112
- _parseSchedule(schedule) {
113
- if (!schedule) return null;
114
-
115
- // every:Ns/Nm/Nh
116
- const everyMatch = schedule.match(/^every:(\d+)(s|m|h)$/i);
117
- if (everyMatch) {
118
- const val = parseInt(everyMatch[1], 10);
119
- const unit = everyMatch[2].toLowerCase();
120
- if (unit === 's') return val * 1000;
121
- if (unit === 'm') return val * 60000;
122
- if (unit === 'h') return val * 3600000;
123
- }
124
-
125
- // daily:HH:MM → 24시간 간격
126
- if (/^daily:\d{2}:\d{2}$/.test(schedule)) {
127
- return 24 * 60 * 60 * 1000;
128
- }
129
-
130
- // weekly:DAY:HH:MM → 7일 간격
131
- if (/^weekly:\w+:\d{2}:\d{2}$/.test(schedule)) {
132
- return 7 * 24 * 60 * 60 * 1000;
133
- }
134
-
135
- return null;
136
- }
137
-
138
- /**
139
- * daily/weekly 스케줄의 첫 실행 지연시간 계산
140
- */
141
- _calcInitialDelay(schedule) {
142
- const now = new Date();
143
-
144
- // daily:HH:MM
145
- const dailyMatch = schedule.match(/^daily:(\d{2}):(\d{2})$/);
146
- if (dailyMatch) {
147
- const target = new Date(now);
148
- target.setHours(parseInt(dailyMatch[1], 10), parseInt(dailyMatch[2], 10), 0, 0);
149
- if (target <= now) target.setDate(target.getDate() + 1);
150
- return target - now;
151
- }
152
-
153
- // weekly:DAY:HH:MM
154
- const weeklyMatch = schedule.match(/^weekly:(\w+):(\d{2}):(\d{2})$/);
155
- if (weeklyMatch) {
156
- const days = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
157
- const targetDay = days[weeklyMatch[1].toLowerCase()];
158
- if (targetDay === undefined) return 0;
159
-
160
- const target = new Date(now);
161
- target.setHours(parseInt(weeklyMatch[2], 10), parseInt(weeklyMatch[3], 10), 0, 0);
162
-
163
- let diff = targetDay - now.getDay();
164
- if (diff < 0 || (diff === 0 && target <= now)) diff += 7;
165
- target.setDate(target.getDate() + diff);
166
- return target - now;
167
- }
168
-
169
- return 0; // every:* → 즉시 시작
170
- }
171
- }
1
+ /**
2
+ * Scheduler — 정기 실행 작업 관리 (cron 스케줄)
3
+ *
4
+ * AutoLoader가 shared/jobs/ 에서 Job 클래스를 자동 스캔·등록.
5
+ * handle()은 메인 스레드에서 실행되므로 this.db, this.service() 등
6
+ * 앱 컨텍스트에 접근 가능합니다.
7
+ *
8
+ * CPU-intensive 작업은 handle() 내에서 this.worker.run()으로 위임.
9
+ *
10
+ * @see docs/framework/10-scheduler-queue.md
11
+ * @see lib/schedule/WorkerPool.js
12
+ */
13
+ export default class Scheduler {
14
+ /**
15
+ * @param {import('../core/Application.js').default} app
16
+ */
17
+ constructor(app) {
18
+ this.app = app;
19
+ /** @type {Array<{JobClass: typeof import('./Job.js').default}>} */
20
+ this._jobs = [];
21
+ /** @type {Array<NodeJS.Timeout>} */
22
+ this._timers = [];
23
+ }
24
+
25
+ /**
26
+ * Job 클래스 등록 (AutoLoader에서 호출)
27
+ * @param {typeof import('./Job.js').default} JobClass
28
+ */
29
+ register(JobClass) {
30
+ if (!JobClass.schedule) return;
31
+ if (JobClass.enabled === false) return;
32
+ this._jobs.push({ JobClass });
33
+ }
34
+
35
+ /**
36
+ * 등록된 모든 Job 시작
37
+ */
38
+ start() {
39
+ for (const { JobClass } of this._jobs) {
40
+ const interval = this._parseSchedule(JobClass.schedule);
41
+ if (!interval) {
42
+ this.app?.logger?.warn?.(`[Scheduler] Invalid schedule: ${JobClass.name} → '${JobClass.schedule}'`);
43
+ continue;
44
+ }
45
+
46
+ const timeout = JobClass.timeout || 30000;
47
+
48
+ const runJob = async () => {
49
+ const job = new JobClass(this.app);
50
+ try {
51
+ let timer;
52
+ await Promise.race([
53
+ job.handle().finally(() => clearTimeout(timer)),
54
+ new Promise((_, reject) => {
55
+ timer = setTimeout(() => reject(new Error(`Job timeout (${timeout}ms)`)), timeout);
56
+ }),
57
+ ]);
58
+ } catch (err) {
59
+ if (typeof job.onError === 'function') {
60
+ try { await job.onError(err); } catch {}
61
+ } else {
62
+ this.app?.logger?.error?.(`[Scheduler] ${JobClass.name} failed: ${err.message}`);
63
+ }
64
+ }
65
+ };
66
+
67
+ // daily/weekly → 첫 실행까지 지연 계산
68
+ const initialDelay = this._calcInitialDelay(JobClass.schedule);
69
+ if (initialDelay > 0) {
70
+ const delayed = setTimeout(() => {
71
+ runJob();
72
+ const timer = setInterval(runJob, interval);
73
+ this._timers.push(timer);
74
+ }, initialDelay);
75
+ this._timers.push(delayed);
76
+ } else {
77
+ const timer = setInterval(runJob, interval);
78
+ this._timers.push(timer);
79
+ // 즉시 첫 실행
80
+ runJob();
81
+ }
82
+
83
+ this.app?.logger?.info?.(
84
+ `[Scheduler] ${JobClass.name} registered (${JobClass.schedule}, interval=${interval}ms)`
85
+ );
86
+ }
87
+ }
88
+
89
+ /**
90
+ * 모든 타이머 정지
91
+ */
92
+ stop() {
93
+ for (const timer of this._timers) {
94
+ clearInterval(timer);
95
+ clearTimeout(timer);
96
+ }
97
+ this._timers = [];
98
+ }
99
+
100
+ // ── Schedule 파싱 ──
101
+
102
+ /**
103
+ * 간편 스케줄 → ms 변환
104
+ *
105
+ * 지원 포맷:
106
+ * 'every:5s' → 5000ms
107
+ * 'every:5m' → 300000ms
108
+ * 'every:1h' → 3600000ms
109
+ * 'daily:HH:MM' → 24h (첫 실행은 _calcInitialDelay)
110
+ * 'weekly:DAY:HH:MM' → 7d (첫 실행은 _calcInitialDelay)
111
+ */
112
+ _parseSchedule(schedule) {
113
+ if (!schedule) return null;
114
+
115
+ // every:Ns/Nm/Nh
116
+ const everyMatch = schedule.match(/^every:(\d+)(s|m|h)$/i);
117
+ if (everyMatch) {
118
+ const val = parseInt(everyMatch[1], 10);
119
+ const unit = everyMatch[2].toLowerCase();
120
+ if (unit === 's') return val * 1000;
121
+ if (unit === 'm') return val * 60000;
122
+ if (unit === 'h') return val * 3600000;
123
+ }
124
+
125
+ // daily:HH:MM → 24시간 간격
126
+ if (/^daily:\d{2}:\d{2}$/.test(schedule)) {
127
+ return 24 * 60 * 60 * 1000;
128
+ }
129
+
130
+ // weekly:DAY:HH:MM → 7일 간격
131
+ if (/^weekly:\w+:\d{2}:\d{2}$/.test(schedule)) {
132
+ return 7 * 24 * 60 * 60 * 1000;
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * daily/weekly 스케줄의 첫 실행 지연시간 계산
140
+ */
141
+ _calcInitialDelay(schedule) {
142
+ const now = new Date();
143
+
144
+ // daily:HH:MM
145
+ const dailyMatch = schedule.match(/^daily:(\d{2}):(\d{2})$/);
146
+ if (dailyMatch) {
147
+ const target = new Date(now);
148
+ target.setHours(parseInt(dailyMatch[1], 10), parseInt(dailyMatch[2], 10), 0, 0);
149
+ if (target <= now) target.setDate(target.getDate() + 1);
150
+ return target - now;
151
+ }
152
+
153
+ // weekly:DAY:HH:MM
154
+ const weeklyMatch = schedule.match(/^weekly:(\w+):(\d{2}):(\d{2})$/);
155
+ if (weeklyMatch) {
156
+ const days = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
157
+ const targetDay = days[weeklyMatch[1].toLowerCase()];
158
+ if (targetDay === undefined) return 0;
159
+
160
+ const target = new Date(now);
161
+ target.setHours(parseInt(weeklyMatch[2], 10), parseInt(weeklyMatch[3], 10), 0, 0);
162
+
163
+ let diff = targetDay - now.getDay();
164
+ if (diff < 0 || (diff === 0 && target <= now)) diff += 7;
165
+ target.setDate(target.getDate() + diff);
166
+ return target - now;
167
+ }
168
+
169
+ return 0; // every:* → 즉시 시작
170
+ }
171
+ }
@@ -1,39 +1,39 @@
1
- /**
2
- * Task — 비동기 위임 작업 (큐)
3
- *
4
- * @see docs/framework/10-scheduler-queue.md
5
- * @see docs/framework/class-design.mm.md (Task)
6
- */
7
- import Job from './Job.js';
8
-
9
- export default class Task extends Job {
10
- /** @type {string} 큐 이름 */
11
- static queue = 'default';
12
-
13
- /** @type {number} 최대 재시도 횟수 */
14
- static retries = 3;
15
-
16
- /** @type {number} 재시도 딜레이 (ms) */
17
- static retryDelay = 5000;
18
-
19
- /** @type {number} 타임아웃 (ms) */
20
- static timeout = 30000;
21
-
22
- /**
23
- * Task 실행 (서브클래스에서 오버라이드)
24
- * @param {object} data - dispatch() 시 전달된 데이터
25
- * @returns {Promise<void>}
26
- */
27
- async handle(data) {
28
- throw new Error(`${this.constructor.name}.handle() must be implemented`);
29
- }
30
-
31
- /**
32
- * 최종 실패 시 호출 (모든 재시도 소진)
33
- * @param {object} data
34
- * @param {Error} error
35
- */
36
- async failed(data, error) {
37
- this.logger.error(`Task ${this.constructor.name} failed permanently:`, error);
38
- }
39
- }
1
+ /**
2
+ * Task — 비동기 위임 작업 (큐)
3
+ *
4
+ * @see docs/framework/10-scheduler-queue.md
5
+ * @see docs/framework/class-design.mm.md (Task)
6
+ */
7
+ import Job from './Job.js';
8
+
9
+ export default class Task extends Job {
10
+ /** @type {string} 큐 이름 */
11
+ static queue = 'default';
12
+
13
+ /** @type {number} 최대 재시도 횟수 */
14
+ static retries = 3;
15
+
16
+ /** @type {number} 재시도 딜레이 (ms) */
17
+ static retryDelay = 5000;
18
+
19
+ /** @type {number} 타임아웃 (ms) */
20
+ static timeout = 30000;
21
+
22
+ /**
23
+ * Task 실행 (서브클래스에서 오버라이드)
24
+ * @param {object} data - dispatch() 시 전달된 데이터
25
+ * @returns {Promise<void>}
26
+ */
27
+ async handle(data) {
28
+ throw new Error(`${this.constructor.name}.handle() must be implemented`);
29
+ }
30
+
31
+ /**
32
+ * 최종 실패 시 호출 (모든 재시도 소진)
33
+ * @param {object} data
34
+ * @param {Error} error
35
+ */
36
+ async failed(data, error) {
37
+ this.logger.error(`Task ${this.constructor.name} failed permanently:`, error);
38
+ }
39
+ }