target-supabase-sdk 0.1.8 → 0.2.0

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 (212) hide show
  1. package/README.md +74 -6
  2. package/dist/auth/auth.api.d.ts.map +1 -1
  3. package/dist/auth/auth.api.js +2 -1
  4. package/dist/auth/auth.api.js.map +1 -1
  5. package/dist/browser.d.ts +52 -0
  6. package/dist/browser.d.ts.map +1 -0
  7. package/dist/browser.js +45 -0
  8. package/dist/browser.js.map +1 -0
  9. package/dist/command/command.api.d.ts +43 -0
  10. package/dist/command/command.api.d.ts.map +1 -0
  11. package/dist/command/command.api.js +58 -0
  12. package/dist/command/command.api.js.map +1 -0
  13. package/dist/command/command.interface.d.ts +14 -0
  14. package/dist/command/command.interface.d.ts.map +1 -0
  15. package/dist/command/command.interface.js +9 -0
  16. package/dist/command/command.interface.js.map +1 -0
  17. package/dist/core.api.d.ts +70 -6
  18. package/dist/core.api.d.ts.map +1 -1
  19. package/dist/core.api.js +191 -13
  20. package/dist/core.api.js.map +1 -1
  21. package/dist/file/file-manager.d.ts +1 -1
  22. package/dist/file/file-manager.d.ts.map +1 -1
  23. package/dist/file/file-manager.js +1 -1
  24. package/dist/file/file-manager.js.map +1 -1
  25. package/dist/index.d.ts +2 -40
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -34
  28. package/dist/index.js.map +1 -1
  29. package/dist/link/link.interface.d.ts +1 -1
  30. package/dist/link/link.interface.d.ts.map +1 -1
  31. package/dist/node/node-log-scope.d.ts +11 -0
  32. package/dist/node/node-log-scope.d.ts.map +1 -0
  33. package/dist/node/node-log-scope.js +7 -0
  34. package/dist/node/node-log-scope.js.map +1 -0
  35. package/dist/node/node-runtime.base.d.ts +42 -0
  36. package/dist/node/node-runtime.base.d.ts.map +1 -0
  37. package/dist/node/node-runtime.base.js +397 -0
  38. package/dist/node/node-runtime.base.js.map +1 -0
  39. package/dist/node/node-task.preset.d.ts +1 -0
  40. package/dist/node/node-task.preset.d.ts.map +1 -0
  41. package/dist/node/node-task.preset.js +14 -0
  42. package/dist/node/node-task.preset.js.map +1 -0
  43. package/dist/node/node.api.d.ts +67 -31
  44. package/dist/node/node.api.d.ts.map +1 -1
  45. package/dist/node/node.api.js +65 -102
  46. package/dist/node/node.api.js.map +1 -1
  47. package/dist/node/node.interface.d.ts +7 -0
  48. package/dist/node/node.interface.d.ts.map +1 -1
  49. package/dist/node/node.interface.js +2 -0
  50. package/dist/node/node.interface.js.map +1 -1
  51. package/dist/node/node.utils.d.ts +5 -0
  52. package/dist/node/node.utils.d.ts.map +1 -0
  53. package/dist/node/node.utils.js +36 -0
  54. package/dist/node/node.utils.js.map +1 -0
  55. package/dist/node/task-node.d.ts +20 -0
  56. package/dist/node/task-node.d.ts.map +1 -0
  57. package/dist/node/task-node.js +245 -0
  58. package/dist/node/task-node.js.map +1 -0
  59. package/dist/node/trigger-node.d.ts +18 -0
  60. package/dist/node/trigger-node.d.ts.map +1 -0
  61. package/dist/node/trigger-node.js +150 -0
  62. package/dist/node/trigger-node.js.map +1 -0
  63. package/dist/node.d.ts +22 -0
  64. package/dist/node.d.ts.map +1 -0
  65. package/dist/node.js +19 -0
  66. package/dist/node.js.map +1 -0
  67. package/dist/parcel/parcel-manager.d.ts +1 -1
  68. package/dist/parcel/parcel-manager.d.ts.map +1 -1
  69. package/dist/parcel/parcel-manager.js +1 -1
  70. package/dist/parcel/parcel-manager.js.map +1 -1
  71. package/dist/repo/repo-context.utils.d.ts +24 -0
  72. package/dist/repo/repo-context.utils.d.ts.map +1 -0
  73. package/dist/repo/repo-context.utils.js +81 -0
  74. package/dist/repo/repo-context.utils.js.map +1 -0
  75. package/dist/repo/repo-manager.d.ts +42 -0
  76. package/dist/repo/repo-manager.d.ts.map +1 -0
  77. package/dist/repo/repo-manager.js +166 -0
  78. package/dist/repo/repo-manager.js.map +1 -0
  79. package/dist/repo/repo.api.d.ts +16 -0
  80. package/dist/repo/repo.api.d.ts.map +1 -0
  81. package/dist/repo/repo.api.js +40 -0
  82. package/dist/repo/repo.api.js.map +1 -0
  83. package/dist/repo/repo.interface.d.ts +12 -5
  84. package/dist/repo/repo.interface.d.ts.map +1 -1
  85. package/dist/repo/repo.interface.js +2 -2
  86. package/dist/repo/repo.interface.js.map +1 -1
  87. package/dist/repo/repo.script-loader.d.ts +19 -0
  88. package/dist/repo/repo.script-loader.d.ts.map +1 -0
  89. package/dist/repo/repo.script-loader.js +108 -0
  90. package/dist/repo/repo.script-loader.js.map +1 -0
  91. package/dist/shared/log/create-api-logger.d.ts +21 -0
  92. package/dist/shared/log/create-api-logger.d.ts.map +1 -0
  93. package/dist/shared/log/create-api-logger.js +19 -0
  94. package/dist/shared/log/create-api-logger.js.map +1 -0
  95. package/dist/shared/log/index.d.ts +10 -0
  96. package/dist/shared/log/index.d.ts.map +1 -0
  97. package/dist/shared/log/index.js +10 -0
  98. package/dist/shared/log/index.js.map +1 -0
  99. package/dist/shared/log/log-manager.d.ts +93 -0
  100. package/dist/shared/log/log-manager.d.ts.map +1 -0
  101. package/dist/shared/log/log-manager.js +237 -0
  102. package/dist/shared/log/log-manager.js.map +1 -0
  103. package/dist/shared/log/log-scope.d.ts +49 -0
  104. package/dist/shared/log/log-scope.d.ts.map +1 -0
  105. package/dist/shared/log/log-scope.js +70 -0
  106. package/dist/shared/log/log-scope.js.map +1 -0
  107. package/dist/shared/utils/config-path.utils.d.ts +38 -0
  108. package/dist/shared/utils/config-path.utils.d.ts.map +1 -0
  109. package/dist/shared/utils/config-path.utils.js +85 -0
  110. package/dist/shared/utils/config-path.utils.js.map +1 -0
  111. package/dist/shared/utils/error.utils.d.ts +11 -0
  112. package/dist/shared/utils/error.utils.d.ts.map +1 -0
  113. package/dist/shared/utils/error.utils.js +15 -0
  114. package/dist/shared/utils/error.utils.js.map +1 -0
  115. package/dist/shared/utils/id.utils.d.ts +3 -0
  116. package/dist/shared/utils/id.utils.d.ts.map +1 -0
  117. package/dist/shared/utils/id.utils.js +5 -0
  118. package/dist/shared/utils/id.utils.js.map +1 -0
  119. package/dist/supabase.js +1 -1
  120. package/dist/supabase.js.map +1 -1
  121. package/dist/task/index.d.ts +15 -0
  122. package/dist/task/index.d.ts.map +1 -0
  123. package/dist/task/index.js +14 -0
  124. package/dist/task/index.js.map +1 -0
  125. package/dist/task/local-task-registry.d.ts +37 -0
  126. package/dist/task/local-task-registry.d.ts.map +1 -0
  127. package/dist/task/local-task-registry.js +214 -0
  128. package/dist/task/local-task-registry.js.map +1 -0
  129. package/dist/task/task-manager.d.ts +64 -0
  130. package/dist/task/task-manager.d.ts.map +1 -0
  131. package/dist/task/task-manager.js +196 -0
  132. package/dist/task/task-manager.js.map +1 -0
  133. package/dist/task/task-post.api.d.ts +49 -0
  134. package/dist/task/task-post.api.d.ts.map +1 -0
  135. package/dist/task/task-post.api.js +91 -0
  136. package/dist/task/task-post.api.js.map +1 -0
  137. package/dist/task/task-repo-context.d.ts +97 -0
  138. package/dist/task/task-repo-context.d.ts.map +1 -0
  139. package/dist/task/task-repo-context.js +12 -0
  140. package/dist/task/task-repo-context.js.map +1 -0
  141. package/dist/task/task-repo-validation.d.ts +46 -0
  142. package/dist/task/task-repo-validation.d.ts.map +1 -0
  143. package/dist/task/task-repo-validation.js +96 -0
  144. package/dist/task/task-repo-validation.js.map +1 -0
  145. package/dist/task/task.api.d.ts +198 -0
  146. package/dist/task/task.api.d.ts.map +1 -1
  147. package/dist/task/task.api.js +302 -1
  148. package/dist/task/task.api.js.map +1 -1
  149. package/dist/task/task.interface.d.ts +11 -2
  150. package/dist/task/task.interface.d.ts.map +1 -1
  151. package/dist/task/task.interface.js +10 -0
  152. package/dist/task/task.interface.js.map +1 -1
  153. package/dist/trigger/trigger.api.d.ts +140 -0
  154. package/dist/trigger/trigger.api.d.ts.map +1 -0
  155. package/dist/trigger/trigger.api.js +125 -0
  156. package/dist/trigger/trigger.api.js.map +1 -0
  157. package/dist/trigger/trigger.interface.d.ts +43 -0
  158. package/dist/trigger/trigger.interface.d.ts.map +1 -0
  159. package/dist/trigger/trigger.interface.js +11 -0
  160. package/dist/trigger/trigger.interface.js.map +1 -0
  161. package/dist/trigger/trigger.utils.d.ts +6 -0
  162. package/dist/trigger/trigger.utils.d.ts.map +1 -0
  163. package/dist/trigger/trigger.utils.js +39 -0
  164. package/dist/trigger/trigger.utils.js.map +1 -0
  165. package/package.json +30 -8
  166. package/src/auth/auth.api.ts +3 -1
  167. package/src/browser.ts +100 -0
  168. package/src/command/command.api.ts +88 -0
  169. package/src/command/command.interface.ts +17 -0
  170. package/src/core.api.ts +305 -26
  171. package/src/file/file-manager.ts +1 -1
  172. package/src/index.ts +2 -64
  173. package/src/link/link.interface.ts +1 -1
  174. package/src/node/node-log-scope.ts +15 -0
  175. package/src/node/node-runtime.base.ts +450 -0
  176. package/src/node/node-task.preset.ts +13 -0
  177. package/src/node/node.api.ts +95 -117
  178. package/src/node/node.interface.ts +8 -0
  179. package/src/node/node.utils.ts +36 -0
  180. package/src/node/task-node.ts +284 -0
  181. package/src/node/trigger-node.ts +180 -0
  182. package/src/node.ts +62 -0
  183. package/src/parcel/parcel-manager.ts +1 -1
  184. package/src/repo/repo-context.utils.ts +111 -0
  185. package/src/repo/repo-manager.ts +245 -0
  186. package/src/repo/repo.api.ts +50 -0
  187. package/src/repo/repo.interface.ts +13 -5
  188. package/src/repo/repo.script-loader.ts +140 -0
  189. package/src/shared/log/create-api-logger.ts +36 -0
  190. package/src/shared/log/index.ts +23 -0
  191. package/src/shared/log/log-manager.ts +335 -0
  192. package/src/shared/log/log-scope.ts +123 -0
  193. package/src/shared/utils/config-path.utils.ts +104 -0
  194. package/src/shared/utils/error.utils.ts +15 -0
  195. package/src/shared/utils/id.utils.ts +4 -0
  196. package/src/supabase.ts +1 -1
  197. package/src/task/index.ts +48 -0
  198. package/src/task/local-task-registry.ts +282 -0
  199. package/src/task/task-manager.ts +301 -0
  200. package/src/task/task-post.api.ts +116 -0
  201. package/src/task/task-repo-context.ts +105 -0
  202. package/src/task/task-repo-validation.ts +171 -0
  203. package/src/task/task.api.ts +369 -0
  204. package/src/task/task.interface.ts +12 -2
  205. package/src/trigger/trigger.api.ts +166 -0
  206. package/src/trigger/trigger.interface.ts +50 -0
  207. package/src/trigger/trigger.utils.ts +56 -0
  208. package/dist/task/task-node-manager.d.ts +0 -1
  209. package/dist/task/task-node-manager.d.ts.map +0 -1
  210. package/dist/task/task-node-manager.js +0 -2
  211. package/dist/task/task-node-manager.js.map +0 -1
  212. package/src/task/task-node-manager.ts +0 -0
@@ -0,0 +1,450 @@
1
+ import { MAX_POLL_TARGET_LIST_SIZE } from "../core.api";
2
+ import { getPollCommandList } from "../command/command.api";
3
+ import { CommandType } from "../command/command.interface";
4
+ import {
5
+ applyScopePatch,
6
+ createChildScope,
7
+ createRootScope,
8
+ logManager,
9
+ type LoggerWithScope,
10
+ type LogScope,
11
+ } from "../shared/log";
12
+ import { scopeForNodeLoop } from "./node-log-scope";
13
+ import { getErrorMessage } from "../shared/utils/error.utils";
14
+ import { patchChangeNodeStatus, patchNodeHeartBeat, patchStopNode, postRegisterNode } from "./node.api";
15
+ import { NodeLoopContext, NodeStatus } from "./node.interface";
16
+ import { formatHeartbeat, getRandomInterval } from "./node.utils";
17
+
18
+ /**
19
+ * Shared node process runtime: bootstrap, main-loop frame, heartbeat, commands, shutdown.
20
+ * Subclasses implement {@link onBeforeRegisterNode} and {@link runLoopSteps}.
21
+ */
22
+ abstract class BaseNodeRuntime {
23
+ protected static readonly HEARTBEAT_FAILURE_THRESHOLD = 3;
24
+
25
+ protected readonly runtimeModule: string;
26
+
27
+ private _localNodeId: string | null = null;
28
+ private _isNodeIdLocked = false;
29
+
30
+ private isRunning: boolean;
31
+ private loopCount: number;
32
+ private consecutiveHeartbeatFailures = 0;
33
+ private readonly startupTraceId: string;
34
+ private readonly startupScope: LogScope;
35
+ private shutdownPromise: Promise<void> | null = null;
36
+ private startPromise: Promise<void> | null = null;
37
+
38
+ get localNodeId(): string | null {
39
+ return this._localNodeId;
40
+ }
41
+
42
+ protected set localNodeId(value: string) {
43
+ if (this._isNodeIdLocked) {
44
+ throw new Error("Node ID cannot be modified after initial assignment");
45
+ }
46
+ if (typeof value !== "string" || value.trim() === "") {
47
+ throw new Error("Invalid node ID");
48
+ }
49
+
50
+ this._localNodeId = value;
51
+ this._isNodeIdLocked = true;
52
+ }
53
+
54
+ protected constructor(runtimeModule: string) {
55
+ this.runtimeModule = runtimeModule;
56
+ this.isRunning = true;
57
+ this.loopCount = 0;
58
+ this.startupTraceId = logManager.generateTraceId();
59
+ this.startupScope = createRootScope({ module: runtimeModule, traceId: this.startupTraceId });
60
+ }
61
+
62
+ /** Hook: task registration, trigger config load, etc. Throw to abort bootstrap. */
63
+ protected abstract onBeforeRegisterNode(logger: LoggerWithScope): Promise<void>;
64
+
65
+ /** Hook: one iteration body after commands + heartbeat gate. */
66
+ protected abstract runLoopSteps(ctx: NodeLoopContext, heartbeatOk: boolean): Promise<void>;
67
+
68
+ private getLogNodeId(): string {
69
+ return this.localNodeId ?? "bootstrap";
70
+ }
71
+
72
+ protected isShuttingDown(): boolean {
73
+ return this.shutdownPromise != null;
74
+ }
75
+
76
+ protected requestShutdown(trigger: string): void {
77
+ void this.shutdown({ trigger });
78
+ }
79
+
80
+ private registerProcessLifecycle(logger: LoggerWithScope): void {
81
+ process.on("SIGTERM", () => {
82
+ logger.warn("收到 SIGTERM 信號,準備關閉節點", { topic: "process" });
83
+ this.requestShutdown("SIGTERM");
84
+ });
85
+ process.on("SIGINT", () => {
86
+ logger.warn("收到 SIGINT 信號,準備關閉節點", { topic: "process" });
87
+ this.requestShutdown("SIGINT");
88
+ });
89
+ process.on("uncaughtException", (error) => {
90
+ if (this.shutdownPromise != null) {
91
+ logger.critical("shutdown 進行中發生未捕獲異常", {
92
+ topic: "process",
93
+ data: {
94
+ name: error.name,
95
+ message: error.message,
96
+ stack: error.stack,
97
+ },
98
+ });
99
+ process.exit(1);
100
+ return;
101
+ }
102
+ logger.error("未捕獲的異常,準備關閉節點", {
103
+ topic: "process",
104
+ data: {
105
+ name: error.name,
106
+ message: error.message,
107
+ stack: error.stack,
108
+ },
109
+ });
110
+ this.requestShutdown("uncaughtException");
111
+ });
112
+ process.on("unhandledRejection", (reason) => {
113
+ if (this.shutdownPromise != null) {
114
+ logger.error("shutdown 進行中發生未處理的 Promise 拒絕", {
115
+ topic: "process",
116
+ data: {
117
+ reason: getErrorMessage(reason),
118
+ stack: reason instanceof Error ? reason.stack : undefined,
119
+ },
120
+ });
121
+ return;
122
+ }
123
+ logger.error("未處理的 Promise 拒絕,準備關閉節點", {
124
+ topic: "process",
125
+ data: {
126
+ reason: getErrorMessage(reason),
127
+ stack: reason instanceof Error ? reason.stack : undefined,
128
+ },
129
+ });
130
+ this.requestShutdown("unhandledRejection");
131
+ });
132
+ }
133
+
134
+ async start(): Promise<void> {
135
+ if (this.startPromise != null) {
136
+ return this.startPromise;
137
+ }
138
+ this.startPromise = this.runStart();
139
+ return this.startPromise;
140
+ }
141
+
142
+ private async runStart(): Promise<void> {
143
+ const { logger } = logManager.withScope(
144
+ applyScopePatch(this.startupScope, { labels: { nodeId: this.getLogNodeId() } })
145
+ );
146
+
147
+ logger.info("節點進程啟動中", {
148
+ topic: "node",
149
+ data: {
150
+ pid: process.pid,
151
+ env: process.env.NODE_ENV ?? "development",
152
+ runtime: this.runtimeModule,
153
+ },
154
+ });
155
+ this.registerProcessLifecycle(logger);
156
+
157
+ try {
158
+ await this.onBeforeRegisterNode(logger);
159
+ } catch (error) {
160
+ logger.error("節點啟動前置步驟失敗", {
161
+ topic: "node",
162
+ data: { error: getErrorMessage(error) },
163
+ });
164
+ this.requestShutdown("bootstrap:pre-register");
165
+ return;
166
+ }
167
+
168
+ logger.info("開始向 Supabase 註冊節點", { topic: "node" });
169
+ try {
170
+ const { data, error } = await postRegisterNode({ traceId: this.startupTraceId });
171
+ if (data == null || error) {
172
+ throw error ?? new Error("postRegisterNode 失敗");
173
+ }
174
+ this.localNodeId = data.id;
175
+ logger.resetScope({ labels: { nodeId: data.id } });
176
+ logger.success("節點註冊成功", {
177
+ topic: "node",
178
+ data: {
179
+ nodeId: data.id,
180
+ details: data.details,
181
+ },
182
+ });
183
+ } catch (error) {
184
+ logger.error("節點註冊失敗", {
185
+ topic: "node",
186
+ data: { error: getErrorMessage(error) },
187
+ });
188
+ this.requestShutdown("bootstrap:register-node");
189
+ return;
190
+ }
191
+
192
+ const nodeId = this.localNodeId!;
193
+ logger.info("節點準備進入主循環", { topic: "node" });
194
+ const { error: enterBusyError } = await patchChangeNodeStatus({
195
+ nodeId,
196
+ status: NodeStatus.BUSY,
197
+ fromStatus: NodeStatus.READY,
198
+ traceId: this.startupTraceId,
199
+ });
200
+ if (enterBusyError) {
201
+ logger.error("節點進入主循環失敗,無法更新節點狀態為 BUSY", {
202
+ topic: "node",
203
+ data: { error: enterBusyError.message },
204
+ });
205
+ this.requestShutdown("bootstrap:enter-busy");
206
+ return;
207
+ }
208
+ logger.info("節點狀態已設為 BUSY,進入主循環", { topic: "node", data: { nodeId } });
209
+
210
+ while (this.isRunning) {
211
+ this.loopCount++;
212
+ const loopTraceId = logManager.generateTraceId();
213
+ const loopScope = createChildScope({
214
+ module: `${this.runtimeModule}-loop-iteration`,
215
+ parent: applyScopePatch(this.startupScope, { labels: { nodeId } }),
216
+ traceId: loopTraceId,
217
+ });
218
+ const loopCtx: NodeLoopContext = { loopTraceId, nodeId };
219
+ const { logger: iterLogger } = logManager.withScope(loopScope);
220
+
221
+ iterLogger.info(`主循環第 ${this.loopCount} 輪開始`, {
222
+ topic: "node",
223
+ data: { nodeId, loopCount: this.loopCount },
224
+ });
225
+ try {
226
+ await this.batchCommand(loopCtx);
227
+ if (this.isShuttingDown()) {
228
+ iterLogger.info("節點關閉中,跳過本輪剩餘步驟", { topic: "node" });
229
+ break;
230
+ }
231
+ const heartbeatOk = await this.heartbeat(loopCtx);
232
+ if (this.isShuttingDown()) {
233
+ iterLogger.info("節點關閉中,跳過本輪剩餘步驟", { topic: "node" });
234
+ break;
235
+ }
236
+ await this.runLoopSteps(loopCtx, heartbeatOk);
237
+ } catch (error) {
238
+ iterLogger.critical(`主循環第 ${this.loopCount} 輪遇到未知錯誤`, {
239
+ topic: "node",
240
+ data: { error: getErrorMessage(error) },
241
+ });
242
+ }
243
+ if (this.isShuttingDown()) {
244
+ iterLogger.debug("節點關閉中,跳過等待間隔", { topic: "node" });
245
+ break;
246
+ }
247
+ const interval = getRandomInterval();
248
+ iterLogger.debug(`主循環第 ${this.loopCount} 輪完成,等待下一輪`, {
249
+ topic: "node",
250
+ data: { intervalMs: interval },
251
+ });
252
+ await new Promise((resolve) => setTimeout(resolve, interval));
253
+ }
254
+ }
255
+
256
+ async shutdown(options?: { trigger?: string }): Promise<void> {
257
+ if (this.shutdownPromise != null) {
258
+ const { logger } = logManager.withScope(
259
+ applyScopePatch(this.startupScope, { labels: { nodeId: this.getLogNodeId() } })
260
+ );
261
+ logger.debug("shutdown 已在進行,跳過重複調用", {
262
+ topic: "node",
263
+ data: { trigger: options?.trigger ?? "unknown" },
264
+ });
265
+ return this.shutdownPromise;
266
+ }
267
+
268
+ this.isRunning = false;
269
+ this.shutdownPromise = this.runShutdown(options?.trigger).catch((error) => {
270
+ const { logger } = logManager.withScope(
271
+ applyScopePatch(this.startupScope, { labels: { nodeId: this.getLogNodeId() } })
272
+ );
273
+ logger.error("shutdown 流程異常", {
274
+ topic: "node",
275
+ data: {
276
+ trigger: options?.trigger ?? "explicit",
277
+ error: getErrorMessage(error),
278
+ },
279
+ });
280
+ process.exit(1);
281
+ });
282
+ return this.shutdownPromise;
283
+ }
284
+
285
+ private async runShutdown(trigger?: string): Promise<void> {
286
+ const { logger } = logManager.withScope(
287
+ applyScopePatch(this.startupScope, { labels: { nodeId: this.getLogNodeId() } })
288
+ );
289
+
290
+ logger.info("正在關閉節點進程", {
291
+ topic: "node",
292
+ data: { trigger: trigger ?? "explicit" },
293
+ });
294
+
295
+ if (this.localNodeId == null) {
296
+ logger.info("本地節點未註冊,直接退出", { topic: "node" });
297
+ process.exit(0);
298
+ return;
299
+ }
300
+
301
+ logger.info("正在登出節點", { topic: "node", data: { nodeId: this.localNodeId } });
302
+ try {
303
+ const { error } = await patchStopNode({
304
+ nodeId: this.localNodeId,
305
+ traceId: this.startupTraceId,
306
+ });
307
+ if (error) {
308
+ throw new Error(`patchStopNode 失敗: ${error.message}`);
309
+ }
310
+ logger.success("節點已登出", { topic: "node" });
311
+ } catch (error) {
312
+ logger.critical("登出節點失敗", {
313
+ topic: "node",
314
+ data: { error: getErrorMessage(error) },
315
+ });
316
+ } finally {
317
+ logger.info("節點進程退出", { topic: "node" });
318
+ process.exit(0);
319
+ }
320
+ }
321
+
322
+ async heartbeat(ctx: NodeLoopContext): Promise<boolean> {
323
+ const { loopTraceId, nodeId } = ctx;
324
+ const { logger } = logManager.withScope(
325
+ scopeForNodeLoop({
326
+ module: "heartbeat",
327
+ traceId: loopTraceId,
328
+ nodeId,
329
+ traceParentId: this.startupTraceId,
330
+ })
331
+ );
332
+
333
+ logger.info("開始發送心跳", { topic: "node" });
334
+ let error: { message: string } | undefined;
335
+ let lastHeartBeat: number | null | undefined;
336
+ try {
337
+ const response = await patchNodeHeartBeat({ nodeId, traceId: loopTraceId });
338
+ error = response.error;
339
+ lastHeartBeat = response.data;
340
+ } catch (caught) {
341
+ error = { message: getErrorMessage(caught) };
342
+ lastHeartBeat = null;
343
+ }
344
+ if (error || lastHeartBeat == null) {
345
+ this.consecutiveHeartbeatFailures++;
346
+ const failureCount = this.consecutiveHeartbeatFailures;
347
+ const logData = {
348
+ error: getErrorMessage(error),
349
+ consecutiveFailures: failureCount,
350
+ threshold: BaseNodeRuntime.HEARTBEAT_FAILURE_THRESHOLD,
351
+ };
352
+ if (failureCount >= BaseNodeRuntime.HEARTBEAT_FAILURE_THRESHOLD) {
353
+ logger.critical("連續心跳失敗達上限,準備關閉節點", {
354
+ topic: "node",
355
+ data: logData,
356
+ });
357
+ this.requestShutdown("heartbeat:consecutive-failures");
358
+ } else {
359
+ logger.warn("心跳更新失敗", { topic: "node", data: logData });
360
+ }
361
+ return false;
362
+ }
363
+
364
+ this.consecutiveHeartbeatFailures = 0;
365
+ logger.success("心跳更新成功", {
366
+ topic: "node",
367
+ data: { lastHeartBeat: formatHeartbeat(lastHeartBeat) },
368
+ });
369
+ return true;
370
+ }
371
+
372
+ async batchCommand(ctx: NodeLoopContext): Promise<void> {
373
+ const { loopTraceId, nodeId } = ctx;
374
+ const { logger } = logManager.withScope(
375
+ scopeForNodeLoop({
376
+ module: "batchCommand",
377
+ traceId: loopTraceId,
378
+ nodeId,
379
+ traceParentId: this.startupTraceId,
380
+ })
381
+ );
382
+
383
+ logger.debug("開始檢查控制指令", { topic: "node" });
384
+
385
+ const { data: commandList = [], error: pollError } = await getPollCommandList({
386
+ nodeId,
387
+ traceId: loopTraceId,
388
+ size: MAX_POLL_TARGET_LIST_SIZE,
389
+ });
390
+ if (pollError) {
391
+ logger.warn("獲取指令失敗", {
392
+ topic: "node",
393
+ data: { error: pollError.message },
394
+ });
395
+ return;
396
+ }
397
+ if (commandList.length === 0) {
398
+ logger.info("獲取指令數量為 0", { topic: "node" });
399
+ return;
400
+ }
401
+
402
+ logger.info("獲取到控制指令", {
403
+ topic: "node",
404
+ data: { count: commandList.length },
405
+ });
406
+
407
+ for (const command of commandList) {
408
+ const cmd = command.name;
409
+ logger.info("執行控制指令", {
410
+ topic: "node",
411
+ data: { commandId: command.id, cmd },
412
+ });
413
+ try {
414
+ await this.executeCommand(cmd);
415
+ logger.success("控制指令執行成功", {
416
+ topic: "node",
417
+ data: { commandId: command.id, cmd },
418
+ });
419
+ } catch (error) {
420
+ logger.warn("控制指令執行失敗", {
421
+ topic: "node",
422
+ data: {
423
+ commandId: command.id,
424
+ cmd,
425
+ error: getErrorMessage(error),
426
+ },
427
+ });
428
+ }
429
+ }
430
+
431
+ logger.info("控制指令批次處理完成", {
432
+ topic: "node",
433
+ data: { totalDequeued: commandList.length },
434
+ });
435
+ }
436
+
437
+ async executeCommand(cmd: CommandType): Promise<void> {
438
+ switch (cmd) {
439
+ case CommandType.STOP_NODE:
440
+ this.requestShutdown("cmd:STOP_NODE");
441
+ return;
442
+ default: {
443
+ const unknownCmd: never = cmd;
444
+ throw new Error(`無法解析的cmd: ${unknownCmd}`);
445
+ }
446
+ }
447
+ }
448
+ }
449
+
450
+ export { BaseNodeRuntime };
@@ -0,0 +1,13 @@
1
+ // export const generateControllerTaskPreset = () => {
2
+ // return {
3
+ // category: CategoryTask.TASK_CONTROLLER,
4
+ // taskTypeFilterList: CONTROLLER_TASK_TYPE_ALLOW_LIST,
5
+ // };
6
+ // };
7
+
8
+ // export const getWorkerTaskPreset = () => {
9
+ // return {
10
+ // category: CategoryTask.TASK,
11
+ // taskTypeFilterList: WORKER_TASK_TYPE_ALLOW_LIST,
12
+ // };
13
+ // };