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.
- package/README.md +74 -6
- package/dist/auth/auth.api.d.ts.map +1 -1
- package/dist/auth/auth.api.js +2 -1
- package/dist/auth/auth.api.js.map +1 -1
- package/dist/browser.d.ts +52 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +45 -0
- package/dist/browser.js.map +1 -0
- package/dist/command/command.api.d.ts +43 -0
- package/dist/command/command.api.d.ts.map +1 -0
- package/dist/command/command.api.js +58 -0
- package/dist/command/command.api.js.map +1 -0
- package/dist/command/command.interface.d.ts +14 -0
- package/dist/command/command.interface.d.ts.map +1 -0
- package/dist/command/command.interface.js +9 -0
- package/dist/command/command.interface.js.map +1 -0
- package/dist/core.api.d.ts +70 -6
- package/dist/core.api.d.ts.map +1 -1
- package/dist/core.api.js +191 -13
- package/dist/core.api.js.map +1 -1
- package/dist/file/file-manager.d.ts +1 -1
- package/dist/file/file-manager.d.ts.map +1 -1
- package/dist/file/file-manager.js +1 -1
- package/dist/file/file-manager.js.map +1 -1
- package/dist/index.d.ts +2 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -34
- package/dist/index.js.map +1 -1
- package/dist/link/link.interface.d.ts +1 -1
- package/dist/link/link.interface.d.ts.map +1 -1
- package/dist/node/node-log-scope.d.ts +11 -0
- package/dist/node/node-log-scope.d.ts.map +1 -0
- package/dist/node/node-log-scope.js +7 -0
- package/dist/node/node-log-scope.js.map +1 -0
- package/dist/node/node-runtime.base.d.ts +42 -0
- package/dist/node/node-runtime.base.d.ts.map +1 -0
- package/dist/node/node-runtime.base.js +397 -0
- package/dist/node/node-runtime.base.js.map +1 -0
- package/dist/node/node-task.preset.d.ts +1 -0
- package/dist/node/node-task.preset.d.ts.map +1 -0
- package/dist/node/node-task.preset.js +14 -0
- package/dist/node/node-task.preset.js.map +1 -0
- package/dist/node/node.api.d.ts +67 -31
- package/dist/node/node.api.d.ts.map +1 -1
- package/dist/node/node.api.js +65 -102
- package/dist/node/node.api.js.map +1 -1
- package/dist/node/node.interface.d.ts +7 -0
- package/dist/node/node.interface.d.ts.map +1 -1
- package/dist/node/node.interface.js +2 -0
- package/dist/node/node.interface.js.map +1 -1
- package/dist/node/node.utils.d.ts +5 -0
- package/dist/node/node.utils.d.ts.map +1 -0
- package/dist/node/node.utils.js +36 -0
- package/dist/node/node.utils.js.map +1 -0
- package/dist/node/task-node.d.ts +20 -0
- package/dist/node/task-node.d.ts.map +1 -0
- package/dist/node/task-node.js +245 -0
- package/dist/node/task-node.js.map +1 -0
- package/dist/node/trigger-node.d.ts +18 -0
- package/dist/node/trigger-node.d.ts.map +1 -0
- package/dist/node/trigger-node.js +150 -0
- package/dist/node/trigger-node.js.map +1 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +19 -0
- package/dist/node.js.map +1 -0
- package/dist/parcel/parcel-manager.d.ts +1 -1
- package/dist/parcel/parcel-manager.d.ts.map +1 -1
- package/dist/parcel/parcel-manager.js +1 -1
- package/dist/parcel/parcel-manager.js.map +1 -1
- package/dist/repo/repo-context.utils.d.ts +24 -0
- package/dist/repo/repo-context.utils.d.ts.map +1 -0
- package/dist/repo/repo-context.utils.js +81 -0
- package/dist/repo/repo-context.utils.js.map +1 -0
- package/dist/repo/repo-manager.d.ts +42 -0
- package/dist/repo/repo-manager.d.ts.map +1 -0
- package/dist/repo/repo-manager.js +166 -0
- package/dist/repo/repo-manager.js.map +1 -0
- package/dist/repo/repo.api.d.ts +16 -0
- package/dist/repo/repo.api.d.ts.map +1 -0
- package/dist/repo/repo.api.js +40 -0
- package/dist/repo/repo.api.js.map +1 -0
- package/dist/repo/repo.interface.d.ts +12 -5
- package/dist/repo/repo.interface.d.ts.map +1 -1
- package/dist/repo/repo.interface.js +2 -2
- package/dist/repo/repo.interface.js.map +1 -1
- package/dist/repo/repo.script-loader.d.ts +19 -0
- package/dist/repo/repo.script-loader.d.ts.map +1 -0
- package/dist/repo/repo.script-loader.js +108 -0
- package/dist/repo/repo.script-loader.js.map +1 -0
- package/dist/shared/log/create-api-logger.d.ts +21 -0
- package/dist/shared/log/create-api-logger.d.ts.map +1 -0
- package/dist/shared/log/create-api-logger.js +19 -0
- package/dist/shared/log/create-api-logger.js.map +1 -0
- package/dist/shared/log/index.d.ts +10 -0
- package/dist/shared/log/index.d.ts.map +1 -0
- package/dist/shared/log/index.js +10 -0
- package/dist/shared/log/index.js.map +1 -0
- package/dist/shared/log/log-manager.d.ts +93 -0
- package/dist/shared/log/log-manager.d.ts.map +1 -0
- package/dist/shared/log/log-manager.js +237 -0
- package/dist/shared/log/log-manager.js.map +1 -0
- package/dist/shared/log/log-scope.d.ts +49 -0
- package/dist/shared/log/log-scope.d.ts.map +1 -0
- package/dist/shared/log/log-scope.js +70 -0
- package/dist/shared/log/log-scope.js.map +1 -0
- package/dist/shared/utils/config-path.utils.d.ts +38 -0
- package/dist/shared/utils/config-path.utils.d.ts.map +1 -0
- package/dist/shared/utils/config-path.utils.js +85 -0
- package/dist/shared/utils/config-path.utils.js.map +1 -0
- package/dist/shared/utils/error.utils.d.ts +11 -0
- package/dist/shared/utils/error.utils.d.ts.map +1 -0
- package/dist/shared/utils/error.utils.js +15 -0
- package/dist/shared/utils/error.utils.js.map +1 -0
- package/dist/shared/utils/id.utils.d.ts +3 -0
- package/dist/shared/utils/id.utils.d.ts.map +1 -0
- package/dist/shared/utils/id.utils.js +5 -0
- package/dist/shared/utils/id.utils.js.map +1 -0
- package/dist/supabase.js +1 -1
- package/dist/supabase.js.map +1 -1
- package/dist/task/index.d.ts +15 -0
- package/dist/task/index.d.ts.map +1 -0
- package/dist/task/index.js +14 -0
- package/dist/task/index.js.map +1 -0
- package/dist/task/local-task-registry.d.ts +37 -0
- package/dist/task/local-task-registry.d.ts.map +1 -0
- package/dist/task/local-task-registry.js +214 -0
- package/dist/task/local-task-registry.js.map +1 -0
- package/dist/task/task-manager.d.ts +64 -0
- package/dist/task/task-manager.d.ts.map +1 -0
- package/dist/task/task-manager.js +196 -0
- package/dist/task/task-manager.js.map +1 -0
- package/dist/task/task-post.api.d.ts +49 -0
- package/dist/task/task-post.api.d.ts.map +1 -0
- package/dist/task/task-post.api.js +91 -0
- package/dist/task/task-post.api.js.map +1 -0
- package/dist/task/task-repo-context.d.ts +97 -0
- package/dist/task/task-repo-context.d.ts.map +1 -0
- package/dist/task/task-repo-context.js +12 -0
- package/dist/task/task-repo-context.js.map +1 -0
- package/dist/task/task-repo-validation.d.ts +46 -0
- package/dist/task/task-repo-validation.d.ts.map +1 -0
- package/dist/task/task-repo-validation.js +96 -0
- package/dist/task/task-repo-validation.js.map +1 -0
- package/dist/task/task.api.d.ts +198 -0
- package/dist/task/task.api.d.ts.map +1 -1
- package/dist/task/task.api.js +302 -1
- package/dist/task/task.api.js.map +1 -1
- package/dist/task/task.interface.d.ts +11 -2
- package/dist/task/task.interface.d.ts.map +1 -1
- package/dist/task/task.interface.js +10 -0
- package/dist/task/task.interface.js.map +1 -1
- package/dist/trigger/trigger.api.d.ts +140 -0
- package/dist/trigger/trigger.api.d.ts.map +1 -0
- package/dist/trigger/trigger.api.js +125 -0
- package/dist/trigger/trigger.api.js.map +1 -0
- package/dist/trigger/trigger.interface.d.ts +43 -0
- package/dist/trigger/trigger.interface.d.ts.map +1 -0
- package/dist/trigger/trigger.interface.js +11 -0
- package/dist/trigger/trigger.interface.js.map +1 -0
- package/dist/trigger/trigger.utils.d.ts +6 -0
- package/dist/trigger/trigger.utils.d.ts.map +1 -0
- package/dist/trigger/trigger.utils.js +39 -0
- package/dist/trigger/trigger.utils.js.map +1 -0
- package/package.json +30 -8
- package/src/auth/auth.api.ts +3 -1
- package/src/browser.ts +100 -0
- package/src/command/command.api.ts +88 -0
- package/src/command/command.interface.ts +17 -0
- package/src/core.api.ts +305 -26
- package/src/file/file-manager.ts +1 -1
- package/src/index.ts +2 -64
- package/src/link/link.interface.ts +1 -1
- package/src/node/node-log-scope.ts +15 -0
- package/src/node/node-runtime.base.ts +450 -0
- package/src/node/node-task.preset.ts +13 -0
- package/src/node/node.api.ts +95 -117
- package/src/node/node.interface.ts +8 -0
- package/src/node/node.utils.ts +36 -0
- package/src/node/task-node.ts +284 -0
- package/src/node/trigger-node.ts +180 -0
- package/src/node.ts +62 -0
- package/src/parcel/parcel-manager.ts +1 -1
- package/src/repo/repo-context.utils.ts +111 -0
- package/src/repo/repo-manager.ts +245 -0
- package/src/repo/repo.api.ts +50 -0
- package/src/repo/repo.interface.ts +13 -5
- package/src/repo/repo.script-loader.ts +140 -0
- package/src/shared/log/create-api-logger.ts +36 -0
- package/src/shared/log/index.ts +23 -0
- package/src/shared/log/log-manager.ts +335 -0
- package/src/shared/log/log-scope.ts +123 -0
- package/src/shared/utils/config-path.utils.ts +104 -0
- package/src/shared/utils/error.utils.ts +15 -0
- package/src/shared/utils/id.utils.ts +4 -0
- package/src/supabase.ts +1 -1
- package/src/task/index.ts +48 -0
- package/src/task/local-task-registry.ts +282 -0
- package/src/task/task-manager.ts +301 -0
- package/src/task/task-post.api.ts +116 -0
- package/src/task/task-repo-context.ts +105 -0
- package/src/task/task-repo-validation.ts +171 -0
- package/src/task/task.api.ts +369 -0
- package/src/task/task.interface.ts +12 -2
- package/src/trigger/trigger.api.ts +166 -0
- package/src/trigger/trigger.interface.ts +50 -0
- package/src/trigger/trigger.utils.ts +56 -0
- package/dist/task/task-node-manager.d.ts +0 -1
- package/dist/task/task-node-manager.d.ts.map +0 -1
- package/dist/task/task-node-manager.js +0 -2
- package/dist/task/task-node-manager.js.map +0 -1
- package/src/task/task-node-manager.ts +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { getScanRemoteRepoValues } from "../repo/repo.api";
|
|
2
|
+
import { TASK_REPO_USAGE } from "../repo/repo.interface";
|
|
3
|
+
import { LoggerWithScope } from "../shared/log";
|
|
4
|
+
import { ResultCode, Task } from "./task.interface";
|
|
5
|
+
import { TaskFn, TaskRunResult, ExecutableTaskFn } from "./task-repo-context";
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
TaskFn,
|
|
9
|
+
TaskRepoContext,
|
|
10
|
+
TaskRunResult,
|
|
11
|
+
ExecutableTaskFn,
|
|
12
|
+
TaskRepoScriptDetails,
|
|
13
|
+
TaskRepoScriptRecord,
|
|
14
|
+
TaskRunnerRootConfig,
|
|
15
|
+
TaskLocalPackageConfig,
|
|
16
|
+
BootstrapLocalTasksResult,
|
|
17
|
+
BootstrapLocalTasksStatus,
|
|
18
|
+
} from "./task-repo-context";
|
|
19
|
+
export {
|
|
20
|
+
TASK_REPO_SCRIPT_CATEGORY,
|
|
21
|
+
TASK_RUNNER_CONFIG_DIR,
|
|
22
|
+
TASK_RUNNER_ROOT_CONFIG_FILENAME,
|
|
23
|
+
TASK_RUNNER_ROOT_CONFIG_RELATIVE_PATH,
|
|
24
|
+
TASK_RUNNER_ROOT_CONFIG_LEGACY_RELATIVE_PATH,
|
|
25
|
+
TASK_LOCAL_PACKAGE_CONFIG_FILENAME,
|
|
26
|
+
} from "./task-repo-context";
|
|
27
|
+
import {
|
|
28
|
+
bootstrapLocalTasks,
|
|
29
|
+
BootstrapLocalTasksOptions,
|
|
30
|
+
clearBootstrapLocalTasksCache,
|
|
31
|
+
getRegisteredLocalTaskTypeKeys,
|
|
32
|
+
} from "./local-task-registry";
|
|
33
|
+
import type { BootstrapLocalTasksResult } from "./task-repo-context";
|
|
34
|
+
import { TaskRepoValidation, type TaskRepoValidationFailureReason } from "./task-repo-validation";
|
|
35
|
+
|
|
36
|
+
/** @deprecated use {@link TaskRepoValidationFailureReason} */
|
|
37
|
+
export type PrepareTaskFailureReason = TaskRepoValidationFailureReason;
|
|
38
|
+
|
|
39
|
+
export type {
|
|
40
|
+
TaskRepoValidationFailureReason,
|
|
41
|
+
ValidateTaskRepoAndParamsInput,
|
|
42
|
+
ValidateTaskRepoAndParamsResult,
|
|
43
|
+
} from "./task-repo-validation";
|
|
44
|
+
export { TaskRepoValidation } from "./task-repo-validation";
|
|
45
|
+
|
|
46
|
+
export interface PrepareTaskResponse {
|
|
47
|
+
isSuccess: boolean;
|
|
48
|
+
taskFn: ExecutableTaskFn | null;
|
|
49
|
+
code?: ResultCode;
|
|
50
|
+
message?: string;
|
|
51
|
+
reason?: PrepareTaskFailureReason;
|
|
52
|
+
/** Pipeline step for log aggregation */
|
|
53
|
+
step?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PrepareTaskParams {
|
|
57
|
+
logger: LoggerWithScope;
|
|
58
|
+
task: Task;
|
|
59
|
+
/**
|
|
60
|
+
* When false, do not fetch Repo from Supabase during validation.
|
|
61
|
+
* Should match {@link RegisterTasksOptions.includeRemote} on the worker.
|
|
62
|
+
*/
|
|
63
|
+
includeRemote?: boolean;
|
|
64
|
+
bootstrapLocalOptions?: BootstrapLocalTasksOptions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RegisterTasksOptions {
|
|
68
|
+
logger: LoggerWithScope;
|
|
69
|
+
local?: BootstrapLocalTasksOptions;
|
|
70
|
+
/** When `true` (default), union local keys with remote `Repo.value` from Supabase. */
|
|
71
|
+
includeRemote?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RegisterTasksResult {
|
|
75
|
+
availableTaskList: string[];
|
|
76
|
+
local: BootstrapLocalTasksResult;
|
|
77
|
+
remote: { values: string[] };
|
|
78
|
+
includeRemote: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function logLocalBootstrap(logger: LoggerWithScope, bootstrap: BootstrapLocalTasksResult): void {
|
|
82
|
+
switch (bootstrap.status) {
|
|
83
|
+
case "not_configured":
|
|
84
|
+
logger.info("未找到本地任務配置,跳過本地註冊", {
|
|
85
|
+
topic: "task",
|
|
86
|
+
data: { message: bootstrap.message },
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
case "failed":
|
|
90
|
+
logger.warn("本地任務註冊失敗", {
|
|
91
|
+
topic: "task",
|
|
92
|
+
data: { message: bootstrap.message, errors: bootstrap.errors },
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
case "empty":
|
|
96
|
+
logger.info("本地任務掃描完成,無已註冊任務", {
|
|
97
|
+
topic: "task",
|
|
98
|
+
data: { message: bootstrap.message, skipped: bootstrap.skipped },
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
case "loaded":
|
|
102
|
+
logger.info("本地任務註冊完成", {
|
|
103
|
+
topic: "task",
|
|
104
|
+
data: {
|
|
105
|
+
registered: bootstrap.registered,
|
|
106
|
+
skipped: bootstrap.skipped,
|
|
107
|
+
errors: bootstrap.errors,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Worker startup: discover local task packages + remote Repo rows, merge into `availableTaskList`.
|
|
116
|
+
*
|
|
117
|
+
* - Local: {@link bootstrapLocalTasks} → registers modules in {@link RepoManager}
|
|
118
|
+
* - Remote: {@link getScanRemoteRepoValues} with {@link TASK_REPO_USAGE}
|
|
119
|
+
* - Merge: union of both; execution still prefers local registry in {@link RepoManager.getRepoContext}
|
|
120
|
+
*
|
|
121
|
+
* @throws When merged list is empty (no task types to claim).
|
|
122
|
+
*/
|
|
123
|
+
async function registerTasks({
|
|
124
|
+
logger,
|
|
125
|
+
local: localOptions,
|
|
126
|
+
includeRemote = true,
|
|
127
|
+
}: RegisterTasksOptions): Promise<RegisterTasksResult> {
|
|
128
|
+
logger.info("開始註冊任務(本地 + 遠程)", { topic: "task" });
|
|
129
|
+
|
|
130
|
+
const local = await bootstrapLocalTasks(localOptions ?? {});
|
|
131
|
+
logLocalBootstrap(logger, local);
|
|
132
|
+
|
|
133
|
+
let remoteValues: string[] = [];
|
|
134
|
+
if (includeRemote) {
|
|
135
|
+
logger.info("查詢遠程 Repo 列表", {
|
|
136
|
+
topic: "task",
|
|
137
|
+
data: { usage: TASK_REPO_USAGE },
|
|
138
|
+
});
|
|
139
|
+
const { data, error: remoteError } = await getScanRemoteRepoValues({ usage: TASK_REPO_USAGE });
|
|
140
|
+
if (remoteError) {
|
|
141
|
+
throw new Error(remoteError.message);
|
|
142
|
+
}
|
|
143
|
+
remoteValues = data ?? [];
|
|
144
|
+
logger.info("遠程 Repo 查詢完成", {
|
|
145
|
+
topic: "task",
|
|
146
|
+
data: { count: remoteValues.length, values: remoteValues },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const availableTaskList = [...new Set([...local.registered, ...remoteValues])];
|
|
151
|
+
if (availableTaskList.length === 0) {
|
|
152
|
+
throw new Error("[registerTasks] 任務註冊失敗:本地與遠程均無可用任務類型");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
logger.success("任務註冊完成", {
|
|
156
|
+
topic: "task",
|
|
157
|
+
data: {
|
|
158
|
+
availableTaskList,
|
|
159
|
+
localRegistered: local.registered,
|
|
160
|
+
remoteValues,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
availableTaskList,
|
|
166
|
+
local,
|
|
167
|
+
remote: { values: remoteValues },
|
|
168
|
+
includeRemote,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** 将 taskFn 与 params 闭包绑定,供 TaskNode 无参调用 `await taskFn()` */
|
|
173
|
+
function bindTaskFn(taskFn: TaskFn, taskParams: unknown): ExecutableTaskFn {
|
|
174
|
+
const run = async () => taskFn(taskParams);
|
|
175
|
+
return Object.assign(run, {
|
|
176
|
+
displayName: taskFn.displayName,
|
|
177
|
+
taskTypeKey: taskFn.taskTypeKey,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 將 Supabase 認領到的 Task 解析為可執行的 `ExecutableTaskFn`。
|
|
183
|
+
*
|
|
184
|
+
* 流程:
|
|
185
|
+
* 1. 從 task.details 取出 `params`;`task.value` 作為 taskTypeKey(同 {@link Repo.value})
|
|
186
|
+
* 2. {@link TaskRepoValidation.validate} — 本地註冊優先;未命中時可選遠端(`includeRemote`)
|
|
187
|
+
* Worker 在 registry 未命中時會補掃本地(`local_bootstrap_on_miss`)
|
|
188
|
+
* 3. bindTaskFn,返回閉包後的無參執行函數
|
|
189
|
+
*
|
|
190
|
+
* 失敗時返回 `{ isSuccess: false, code, message, reason, step }`(不 throw)。
|
|
191
|
+
*/
|
|
192
|
+
const prepareTask = async ({
|
|
193
|
+
logger,
|
|
194
|
+
task,
|
|
195
|
+
includeRemote = true,
|
|
196
|
+
bootstrapLocalOptions,
|
|
197
|
+
}: PrepareTaskParams): Promise<PrepareTaskResponse> => {
|
|
198
|
+
const fail = (params: {
|
|
199
|
+
reason: PrepareTaskFailureReason;
|
|
200
|
+
code: ResultCode;
|
|
201
|
+
message: string;
|
|
202
|
+
step: string;
|
|
203
|
+
}): PrepareTaskResponse => {
|
|
204
|
+
logger.warn(params.message, {
|
|
205
|
+
topic: "task",
|
|
206
|
+
data: {
|
|
207
|
+
taskId: task.id,
|
|
208
|
+
taskTypeKey: task.value,
|
|
209
|
+
reason: params.reason,
|
|
210
|
+
step: params.step,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
isSuccess: false,
|
|
215
|
+
taskFn: null,
|
|
216
|
+
code: params.code,
|
|
217
|
+
message: params.message,
|
|
218
|
+
reason: params.reason,
|
|
219
|
+
step: params.step,
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const { id: taskId, name: taskName, value: taskTypeKey, details } = task;
|
|
224
|
+
logger.info(`開始準備任務 ${taskName}-${taskTypeKey}`, {
|
|
225
|
+
topic: "task",
|
|
226
|
+
data: { taskId, taskName, taskTypeKey },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const { params: taskParams } = details;
|
|
230
|
+
|
|
231
|
+
if (!getRegisteredLocalTaskTypeKeys().includes(taskTypeKey)) {
|
|
232
|
+
const bootstrap = await bootstrapLocalTasks({
|
|
233
|
+
...bootstrapLocalOptions,
|
|
234
|
+
forTaskTypeKey: taskTypeKey,
|
|
235
|
+
});
|
|
236
|
+
logger.debug("本地 registry 未命中,補掃本地任務", {
|
|
237
|
+
topic: "task",
|
|
238
|
+
data: {
|
|
239
|
+
taskId,
|
|
240
|
+
taskTypeKey,
|
|
241
|
+
step: "local_bootstrap_on_miss",
|
|
242
|
+
status: bootstrap.status,
|
|
243
|
+
registered: bootstrap.registered,
|
|
244
|
+
cached: bootstrap.cached ?? false,
|
|
245
|
+
message: bootstrap.message,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const validation = await TaskRepoValidation.validate({
|
|
251
|
+
logger,
|
|
252
|
+
taskTypeKey,
|
|
253
|
+
params: taskParams,
|
|
254
|
+
includeRemote,
|
|
255
|
+
});
|
|
256
|
+
if (!validation.isValid) {
|
|
257
|
+
return fail({
|
|
258
|
+
reason: validation.reason,
|
|
259
|
+
code: validation.code,
|
|
260
|
+
message: validation.message,
|
|
261
|
+
step: validation.step,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const { repoContext } = validation;
|
|
266
|
+
|
|
267
|
+
logger.info("Repo 上下文加載成功", {
|
|
268
|
+
topic: "task",
|
|
269
|
+
data: {
|
|
270
|
+
taskTypeKey,
|
|
271
|
+
taskFnDisplayName: repoContext.taskFn.displayName,
|
|
272
|
+
step: "repo_load",
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const boundTaskFn = bindTaskFn(repoContext.taskFn, taskParams);
|
|
277
|
+
logger.success("任務準備完成", {
|
|
278
|
+
topic: "task",
|
|
279
|
+
data: {
|
|
280
|
+
taskId,
|
|
281
|
+
taskTypeKey,
|
|
282
|
+
displayName: boundTaskFn.displayName,
|
|
283
|
+
step: "bind",
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
isSuccess: true,
|
|
289
|
+
taskFn: boundTaskFn,
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const TaskManager = {
|
|
294
|
+
registerTasks,
|
|
295
|
+
prepareTask,
|
|
296
|
+
bootstrapLocalTasks,
|
|
297
|
+
getRegisteredLocalTaskTypeKeys,
|
|
298
|
+
clearBootstrapLocalTasksCache,
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export { TaskManager };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createTarget, validateWithSchema } from "../core.api";
|
|
2
|
+
import { generateResponse, type SupabaseResponse } from "../core.interface";
|
|
3
|
+
import { createApiLogger, logManager } from "../shared/log";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { CategoryTask, ResultCode, Task, TaskStatus } from "./task.interface";
|
|
6
|
+
import { TaskRepoValidation } from "./task-repo-validation";
|
|
7
|
+
|
|
8
|
+
const traceIdSchema = z.string().trim().min(1).optional();
|
|
9
|
+
const traceParentIdSchema = z.string().trim().min(1).nullable().optional();
|
|
10
|
+
|
|
11
|
+
/** Initial status for a new task — OPEN (draft) or TODO (claimable by workers). */
|
|
12
|
+
const postTaskInitialStatusSchema = z
|
|
13
|
+
.union([z.literal(TaskStatus.OPEN), z.literal(TaskStatus.TODO)])
|
|
14
|
+
.optional()
|
|
15
|
+
.default(TaskStatus.TODO);
|
|
16
|
+
|
|
17
|
+
export const postTaskSchema = z.object({
|
|
18
|
+
name: z.string().trim().min(1),
|
|
19
|
+
/** Task type key — matches `task.value`, {@link patchClaimTask}, {@link Repo.value}, and local Repo registry. */
|
|
20
|
+
value: z.string().trim().min(1),
|
|
21
|
+
params: z.unknown(),
|
|
22
|
+
taskStatus: postTaskInitialStatusSchema,
|
|
23
|
+
tagList: z.array(z.string()).optional().default([]),
|
|
24
|
+
extra: z.string().optional(),
|
|
25
|
+
traceId: traceIdSchema,
|
|
26
|
+
traceParentId: traceParentIdSchema,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type PostTaskPayload = z.infer<typeof postTaskSchema>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a task row (scheduler / admin). **Node entry only** — requires local Repo bootstrap.
|
|
33
|
+
*
|
|
34
|
+
* @see target-supabase-sdk/node
|
|
35
|
+
*/
|
|
36
|
+
export const postTask = validateWithSchema(
|
|
37
|
+
postTaskSchema,
|
|
38
|
+
"postTaskSchema",
|
|
39
|
+
)(async ({
|
|
40
|
+
name,
|
|
41
|
+
value,
|
|
42
|
+
params,
|
|
43
|
+
taskStatus,
|
|
44
|
+
tagList,
|
|
45
|
+
extra,
|
|
46
|
+
traceId,
|
|
47
|
+
traceParentId,
|
|
48
|
+
}) => {
|
|
49
|
+
const taskTraceId = traceId?.trim() || logManager.generateTraceId();
|
|
50
|
+
const logger = createApiLogger("postTask", {
|
|
51
|
+
traceId: taskTraceId,
|
|
52
|
+
traceParentId: traceParentId ?? null,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const validation = await TaskRepoValidation.validate({
|
|
56
|
+
logger,
|
|
57
|
+
taskTypeKey: value,
|
|
58
|
+
params,
|
|
59
|
+
bootstrapLocal: true,
|
|
60
|
+
});
|
|
61
|
+
if (!validation.isValid) {
|
|
62
|
+
logger.warn(validation.message, {
|
|
63
|
+
topic: "task",
|
|
64
|
+
data: {
|
|
65
|
+
taskTypeKey: value,
|
|
66
|
+
reason: validation.reason,
|
|
67
|
+
step: validation.step,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return generateResponse.error(
|
|
71
|
+
validation.message,
|
|
72
|
+
undefined,
|
|
73
|
+
String(validation.code),
|
|
74
|
+
) as SupabaseResponse<Task>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = await createTarget<Task, PostTaskPayload>({
|
|
78
|
+
payload: {
|
|
79
|
+
name,
|
|
80
|
+
value,
|
|
81
|
+
params,
|
|
82
|
+
taskStatus,
|
|
83
|
+
tagList,
|
|
84
|
+
extra,
|
|
85
|
+
traceId: taskTraceId,
|
|
86
|
+
traceParentId,
|
|
87
|
+
},
|
|
88
|
+
createFn: () => ({
|
|
89
|
+
name,
|
|
90
|
+
value,
|
|
91
|
+
category: CategoryTask.TASK,
|
|
92
|
+
tagList,
|
|
93
|
+
extra,
|
|
94
|
+
details: {
|
|
95
|
+
manifestVersion: 0,
|
|
96
|
+
status: taskStatus,
|
|
97
|
+
params,
|
|
98
|
+
progress: 0,
|
|
99
|
+
nodeId: null,
|
|
100
|
+
traceId: taskTraceId,
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
logger.info("任務已創建", {
|
|
106
|
+
topic: "task",
|
|
107
|
+
data: {
|
|
108
|
+
taskId: result.data?.id,
|
|
109
|
+
taskTypeKey: value,
|
|
110
|
+
status: taskStatus,
|
|
111
|
+
traceId: taskTraceId,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface TaskRunResult {
|
|
2
|
+
isSuccess: boolean;
|
|
3
|
+
cost: number;
|
|
4
|
+
extra: unknown;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface TaskFn {
|
|
8
|
+
displayName: string;
|
|
9
|
+
taskTypeKey: string;
|
|
10
|
+
(taskParams: unknown): Promise<TaskRunResult>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Params already bound — used by node executor after prepareTask */
|
|
14
|
+
export type ExecutableTaskFn = (() => Promise<TaskRunResult>) & {
|
|
15
|
+
displayName: string;
|
|
16
|
+
taskTypeKey: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Repo script module must expose this shape (see repo-context.utils). */
|
|
20
|
+
export interface TaskRepoContext {
|
|
21
|
+
taskParamsValidator: (taskParams: unknown) => boolean;
|
|
22
|
+
taskFn: TaskFn;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Supabase `target.category` for rows that store task script payloads */
|
|
26
|
+
export const TASK_REPO_SCRIPT_CATEGORY = "script" as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `target.details` for task script rows — not a separate Target subtype in repo.interface.
|
|
30
|
+
* Loaded dynamically to produce {@link TaskRepoContext}.
|
|
31
|
+
*/
|
|
32
|
+
export interface TaskRepoScriptDetails {
|
|
33
|
+
manifestVersion: number;
|
|
34
|
+
/** Inline ESM source — written to temp file then dynamically imported (Node) */
|
|
35
|
+
source?: string;
|
|
36
|
+
/** Absolute path to an ESM module on the worker machine */
|
|
37
|
+
modulePath?: string;
|
|
38
|
+
/** Named export to read TaskRepoContext from; default export if omitted */
|
|
39
|
+
exportName?: string;
|
|
40
|
+
/** Content hash for cache invalidation */
|
|
41
|
+
hash?: string;
|
|
42
|
+
/** Prefer this script as entry when multiple scripts exist for one repo */
|
|
43
|
+
isEntry?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Minimal target row shape used by script loader (avoids a dedicated Script Target type). */
|
|
47
|
+
export interface TaskRepoScriptRecord {
|
|
48
|
+
id: string;
|
|
49
|
+
value: string;
|
|
50
|
+
details: TaskRepoScriptDetails;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Host app root config at `config/task.config.js` — where to discover local task packages */
|
|
54
|
+
export interface TaskRunnerRootConfig {
|
|
55
|
+
/** Directory containing one folder per task (relative to root config file), e.g. `"./tasks"` */
|
|
56
|
+
taskDir: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Per-task `task.config.js` inside `<taskDir>/<name>/` */
|
|
60
|
+
export interface TaskLocalPackageConfig {
|
|
61
|
+
/** Must match `task.value` when claiming from Supabase */
|
|
62
|
+
taskTypeKey: string;
|
|
63
|
+
/** Entry module relative to this task folder, e.g. `"./index.mjs"` */
|
|
64
|
+
entry: string;
|
|
65
|
+
/** Named export on entry module; default export if omitted */
|
|
66
|
+
exportName?: string;
|
|
67
|
+
displayName?: string;
|
|
68
|
+
/** Skip registration when `false` (default `true`) */
|
|
69
|
+
enabled?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type BootstrapLocalTasksStatus =
|
|
73
|
+
/** Root config found and scan finished (individual tasks may still have errors) */
|
|
74
|
+
| "loaded"
|
|
75
|
+
/** Root config found but zero tasks registered */
|
|
76
|
+
| "empty"
|
|
77
|
+
/** No root config at default/explicit paths — not an error; use remote scripts or manual register */
|
|
78
|
+
| "not_configured"
|
|
79
|
+
/** Root config or taskDir invalid */
|
|
80
|
+
| "failed";
|
|
81
|
+
|
|
82
|
+
export interface BootstrapLocalTasksResult {
|
|
83
|
+
status: BootstrapLocalTasksStatus;
|
|
84
|
+
/** Human-readable summary for logs */
|
|
85
|
+
message?: string;
|
|
86
|
+
registered: string[];
|
|
87
|
+
skipped: { taskDir: string; reason: string }[];
|
|
88
|
+
errors: { taskDir: string; error: string }[];
|
|
89
|
+
/** True when scan was skipped due to unchanged config fingerprint */
|
|
90
|
+
cached?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Host app config directory (relative to `cwd`) — recommended for library consumers */
|
|
94
|
+
export const TASK_RUNNER_CONFIG_DIR = "config" as const;
|
|
95
|
+
|
|
96
|
+
export const TASK_RUNNER_ROOT_CONFIG_FILENAME = "task.config.js" as const;
|
|
97
|
+
|
|
98
|
+
/** Default root config path: `config/task.config.js` */
|
|
99
|
+
export const TASK_RUNNER_ROOT_CONFIG_RELATIVE_PATH = `${TASK_RUNNER_CONFIG_DIR}/${TASK_RUNNER_ROOT_CONFIG_FILENAME}` as const;
|
|
100
|
+
|
|
101
|
+
/** Legacy fallback at project root */
|
|
102
|
+
export const TASK_RUNNER_ROOT_CONFIG_LEGACY_RELATIVE_PATH = TASK_RUNNER_ROOT_CONFIG_FILENAME;
|
|
103
|
+
|
|
104
|
+
/** Per-task config filename inside each folder under `taskDir` */
|
|
105
|
+
export const TASK_LOCAL_PACKAGE_CONFIG_FILENAME = "task.config.js" as const;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { RepoManager, type RepoContextFailureReason } from "../repo/repo-manager";
|
|
2
|
+
import { LoggerWithScope } from "../shared/log";
|
|
3
|
+
import { bootstrapLocalTasks, type BootstrapLocalTasksOptions } from "./local-task-registry";
|
|
4
|
+
import { ResultCode } from "./task.interface";
|
|
5
|
+
import type { TaskRepoContext } from "./task-repo-context";
|
|
6
|
+
|
|
7
|
+
export type TaskRepoValidationFailureReason =
|
|
8
|
+
| "MISSING_PARAMS"
|
|
9
|
+
| RepoContextFailureReason
|
|
10
|
+
| "PARAMS_VALIDATION_FAILED"
|
|
11
|
+
| "TASK_TYPE_KEY_MISMATCH";
|
|
12
|
+
|
|
13
|
+
export type ValidateTaskRepoAndParamsInput = {
|
|
14
|
+
logger: LoggerWithScope;
|
|
15
|
+
taskTypeKey: string;
|
|
16
|
+
params: unknown;
|
|
17
|
+
/**
|
|
18
|
+
* When true, run {@link bootstrapLocalTasks} before resolve (CLI / Trigger / postTask).
|
|
19
|
+
* Worker paths should omit — {@link TaskManager.registerTasks} already bootstrapped.
|
|
20
|
+
*/
|
|
21
|
+
bootstrapLocal?: boolean;
|
|
22
|
+
bootstrapLocalOptions?: BootstrapLocalTasksOptions;
|
|
23
|
+
/**
|
|
24
|
+
* When false, do not fetch Repo from Supabase if the task type is not locally registered.
|
|
25
|
+
* Default `true`. Align with {@link RegisterTasksOptions.includeRemote}.
|
|
26
|
+
*/
|
|
27
|
+
includeRemote?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ValidateTaskRepoAndParamsFailure = {
|
|
31
|
+
isValid: false;
|
|
32
|
+
code: ResultCode;
|
|
33
|
+
message: string;
|
|
34
|
+
reason: TaskRepoValidationFailureReason;
|
|
35
|
+
step: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ValidateTaskRepoAndParamsSuccess = {
|
|
39
|
+
isValid: true;
|
|
40
|
+
repoContext: TaskRepoContext;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ValidateTaskRepoAndParamsResult =
|
|
44
|
+
| ValidateTaskRepoAndParamsFailure
|
|
45
|
+
| ValidateTaskRepoAndParamsSuccess;
|
|
46
|
+
|
|
47
|
+
function validateTaskParams(
|
|
48
|
+
validator: TaskRepoContext["taskParamsValidator"],
|
|
49
|
+
taskParams: unknown,
|
|
50
|
+
logger: LoggerWithScope
|
|
51
|
+
): boolean {
|
|
52
|
+
try {
|
|
53
|
+
return validator(taskParams) === true;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.warn("taskParamsValidator 拋出異常", {
|
|
56
|
+
topic: "task",
|
|
57
|
+
data: { error: error instanceof Error ? error.message : error },
|
|
58
|
+
});
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function invalid(params: {
|
|
64
|
+
reason: TaskRepoValidationFailureReason;
|
|
65
|
+
code: ResultCode;
|
|
66
|
+
message: string;
|
|
67
|
+
step: string;
|
|
68
|
+
}): ValidateTaskRepoAndParamsFailure {
|
|
69
|
+
return { isValid: false, ...params };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve task Repo context and validate `params` via `taskParamsValidator`.
|
|
74
|
+
*
|
|
75
|
+
* Repo resolution uses {@link RepoManager.getRepoContext}:
|
|
76
|
+
* local registry first; when no local entry and `includeRemote`, loads from Supabase.
|
|
77
|
+
*/
|
|
78
|
+
async function validateTaskRepoAndParams({
|
|
79
|
+
logger,
|
|
80
|
+
taskTypeKey,
|
|
81
|
+
params,
|
|
82
|
+
bootstrapLocal = false,
|
|
83
|
+
bootstrapLocalOptions,
|
|
84
|
+
includeRemote = true,
|
|
85
|
+
}: ValidateTaskRepoAndParamsInput): Promise<ValidateTaskRepoAndParamsResult> {
|
|
86
|
+
if (params == null) {
|
|
87
|
+
return invalid({
|
|
88
|
+
reason: "MISSING_PARAMS",
|
|
89
|
+
code: ResultCode.PARAMS_NOT_VALID,
|
|
90
|
+
message: "任務缺少 params",
|
|
91
|
+
step: "params",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (bootstrapLocal) {
|
|
96
|
+
const bootstrap = await bootstrapLocalTasks({
|
|
97
|
+
...bootstrapLocalOptions,
|
|
98
|
+
forTaskTypeKey: taskTypeKey,
|
|
99
|
+
});
|
|
100
|
+
logger.debug("本地任務 bootstrap 完成", {
|
|
101
|
+
topic: "task",
|
|
102
|
+
data: {
|
|
103
|
+
taskTypeKey,
|
|
104
|
+
step: "local_bootstrap",
|
|
105
|
+
status: bootstrap.status,
|
|
106
|
+
registered: bootstrap.registered,
|
|
107
|
+
cached: bootstrap.cached ?? false,
|
|
108
|
+
message: bootstrap.message,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
logger.debug("開始解析 Repo 上下文", {
|
|
114
|
+
topic: "task",
|
|
115
|
+
data: { taskTypeKey, step: "repo_load", includeRemote },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const {
|
|
119
|
+
context: repoContext,
|
|
120
|
+
error: repoError,
|
|
121
|
+
failureReason,
|
|
122
|
+
resolvedVia,
|
|
123
|
+
} = await RepoManager.getRepoContext<TaskRepoContext>({
|
|
124
|
+
logger,
|
|
125
|
+
taskTypeKey,
|
|
126
|
+
includeRemote,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (repoContext == null) {
|
|
130
|
+
return invalid({
|
|
131
|
+
reason: failureReason ?? "REMOTE_REPO_LOAD_FAILED",
|
|
132
|
+
code: ResultCode.REPO_NOT_VALID,
|
|
133
|
+
message: repoError ?? "無法加載 Repo 上下文",
|
|
134
|
+
step: "repo_load",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
logger.debug("Repo 上下文解析完成", {
|
|
139
|
+
topic: "task",
|
|
140
|
+
data: { taskTypeKey, step: "repo_load", resolvedVia },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (repoContext.taskFn.taskTypeKey !== taskTypeKey) {
|
|
144
|
+
return invalid({
|
|
145
|
+
reason: "TASK_TYPE_KEY_MISMATCH",
|
|
146
|
+
code: ResultCode.REPO_NOT_VALID,
|
|
147
|
+
message: `taskFn.taskTypeKey (${repoContext.taskFn.taskTypeKey}) 與 task.value (${taskTypeKey}) 不一致`,
|
|
148
|
+
step: "task_type_key",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
logger.debug("開始校驗任務參數", { topic: "task", data: { taskTypeKey, step: "params_validation" } });
|
|
153
|
+
if (!validateTaskParams(repoContext.taskParamsValidator, params, logger)) {
|
|
154
|
+
return invalid({
|
|
155
|
+
reason: "PARAMS_VALIDATION_FAILED",
|
|
156
|
+
code: ResultCode.PARAMS_NOT_VALID,
|
|
157
|
+
message: "任務參數校驗失敗",
|
|
158
|
+
step: "params_validation",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
logger.debug("任務參數校驗通過", { topic: "task", data: { taskTypeKey, step: "params_validation" } });
|
|
163
|
+
|
|
164
|
+
return { isValid: true, repoContext };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const TaskRepoValidation = {
|
|
168
|
+
validate: validateTaskRepoAndParams,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export { TaskRepoValidation, validateTaskRepoAndParams };
|