target-supabase-sdk 0.1.8 → 0.1.9
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 +8 -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/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 +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/node/node-manager.d.ts +48 -0
- package/dist/node/node-manager.d.ts.map +1 -0
- package/dist/node/node-manager.js +619 -0
- package/dist/node/node-manager.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/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 +31 -0
- package/dist/repo/repo-manager.d.ts.map +1 -0
- package/dist/repo/repo-manager.js +156 -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 +17 -0
- package/dist/shared/log/create-api-logger.d.ts.map +1 -0
- package/dist/shared/log/create-api-logger.js +15 -0
- package/dist/shared/log/create-api-logger.js.map +1 -0
- package/dist/shared/log/log-manager.d.ts +114 -0
- package/dist/shared/log/log-manager.d.ts.map +1 -0
- package/dist/shared/log/log-manager.js +218 -0
- package/dist/shared/log/log-manager.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/local-task-registry.d.ts +27 -0
- package/dist/task/local-task-registry.d.ts.map +1 -0
- package/dist/task/local-task-registry.js +153 -0
- package/dist/task/local-task-registry.js.map +1 -0
- package/dist/task/task-manager.d.ts +52 -0
- package/dist/task/task-manager.d.ts.map +1 -0
- package/dist/task/task-manager.js +217 -0
- package/dist/task/task-manager.js.map +1 -0
- package/dist/task/task-repo-context.d.ts +95 -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.api.d.ts +240 -0
- package/dist/task/task.api.d.ts.map +1 -1
- package/dist/task/task.api.js +350 -1
- package/dist/task/task.api.js.map +1 -1
- package/dist/task/task.interface.d.ts +9 -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/package.json +10 -3
- package/src/auth/auth.api.ts +3 -1
- 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 +18 -3
- package/src/node/node-manager.ts +673 -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/parcel/parcel-manager.ts +1 -1
- package/src/repo/repo-context.utils.ts +111 -0
- package/src/repo/repo-manager.ts +217 -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 +25 -0
- package/src/shared/log/log-manager.ts +340 -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/local-task-registry.ts +195 -0
- package/src/task/task-manager.ts +312 -0
- package/src/task/task-repo-context.ts +103 -0
- package/src/task/task.api.ts +429 -0
- package/src/task/task.interface.ts +10 -2
- 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
package/src/task/task.api.ts
CHANGED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { createTarget, getPossibleTarget, QueryFilter, updateTargetDetails, validateWithSchema, isOptimisticLockError, OPTIMISTIC_LOCK_FAILED_MESSAGE } from "../core.api";
|
|
2
|
+
import { generateResponse, type SupabaseResponse } from "../core.interface";
|
|
3
|
+
import { createApiLogger, type LoggerWithContext } from "../shared/log/log-manager";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { CategoryTask, ResultCode, Task, TaskDetails, TaskStatus, TaskStatusAction } from "./task.interface";
|
|
6
|
+
|
|
7
|
+
const TASK_STATUS_FIELD = "details->>status" as const;
|
|
8
|
+
const TASK_NODE_ID_FIELD = "details->>nodeId" as const;
|
|
9
|
+
|
|
10
|
+
// ─── State machine ───────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Task state machine (field: `details.status`)
|
|
14
|
+
*
|
|
15
|
+
* ```text
|
|
16
|
+
* publish claim (CLAIM / patchClaimTask) finish
|
|
17
|
+
* OPEN ──────► TODO ──────────────────► DOING ─────────────► DONE
|
|
18
|
+
* │ │ reset │ cancel │ close
|
|
19
|
+
* │ │ └──────► TODO │
|
|
20
|
+
* └──────── reset ─┴──────────────────────── reset ────────────┘
|
|
21
|
+
* | │
|
|
22
|
+
* └──────────────────── close ─────────────────────────────────┘
|
|
23
|
+
* ▼
|
|
24
|
+
* CLOSED
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* | TaskStatusAction | from | to | lock filters | caller |
|
|
28
|
+
* |------------------|-------------------|--------|--------------------------------|---------------|
|
|
29
|
+
* | PUBLISH | OPEN | TODO | status = OPEN | scheduler |
|
|
30
|
+
* | CLAIM | TODO | DOING | status = TODO | worker |
|
|
31
|
+
* | RESET | TODO | OPEN | status = TODO (no nodeId) | admin |
|
|
32
|
+
* | RESET | DOING, DONE | OPEN | status ∈ {DOING,DONE} + nodeId | worker |
|
|
33
|
+
* | CANCEL | DOING | TODO | DOING + nodeId | worker |
|
|
34
|
+
* | FINISH | DOING | DONE | DOING + nodeId | worker |
|
|
35
|
+
* | CLOSE | OPEN, DONE | CLOSED | status ∈ {OPEN, DONE} | scheduler |
|
|
36
|
+
*
|
|
37
|
+
* RESET: omit `nodeId` to reset from **TODO**; pass `nodeId` to reset from **DOING** or **DONE**.
|
|
38
|
+
*
|
|
39
|
+
* All public APIs validate input with Zod (`validateWithSchema`). Optimistic locks live in
|
|
40
|
+
* `optimisticLockFilterList` on UPDATE — see `.cursor/skills/optimistic-lock-update/SKILL.md`.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
// ─── Zod schemas ─────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
const taskIdSchema = z.string().trim().min(1);
|
|
46
|
+
const nodeIdSchema = z.string().trim().min(1);
|
|
47
|
+
/** Optional — orchestrator (e.g. NodeManager loop) passes its trace to correlate logs. */
|
|
48
|
+
const traceIdSchema = z.string().trim().min(1).optional();
|
|
49
|
+
|
|
50
|
+
const patchChangeTaskStatusBaseSchema = {
|
|
51
|
+
id: taskIdSchema,
|
|
52
|
+
extra: z.string().optional(),
|
|
53
|
+
traceId: traceIdSchema,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Shared by CLAIM / CANCEL / FINISH — worker-side actions that require nodeId */
|
|
57
|
+
const patchChangeTaskStatusNodeOwnedSchema = {
|
|
58
|
+
...patchChangeTaskStatusBaseSchema,
|
|
59
|
+
nodeId: nodeIdSchema,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const patchChangeTaskStatusSchema = z.discriminatedUnion("action", [
|
|
63
|
+
z.object({
|
|
64
|
+
...patchChangeTaskStatusBaseSchema,
|
|
65
|
+
action: z.literal(TaskStatusAction.PUBLISH),
|
|
66
|
+
}),
|
|
67
|
+
z.object({
|
|
68
|
+
...patchChangeTaskStatusBaseSchema,
|
|
69
|
+
action: z.literal(TaskStatusAction.CLOSE),
|
|
70
|
+
}),
|
|
71
|
+
z.object({
|
|
72
|
+
...patchChangeTaskStatusNodeOwnedSchema,
|
|
73
|
+
action: z.literal(TaskStatusAction.CLAIM),
|
|
74
|
+
}),
|
|
75
|
+
z.object({
|
|
76
|
+
...patchChangeTaskStatusBaseSchema,
|
|
77
|
+
action: z.literal(TaskStatusAction.RESET),
|
|
78
|
+
nodeId: nodeIdSchema.optional(),
|
|
79
|
+
}),
|
|
80
|
+
z.object({
|
|
81
|
+
...patchChangeTaskStatusNodeOwnedSchema,
|
|
82
|
+
action: z.literal(TaskStatusAction.CANCEL),
|
|
83
|
+
}),
|
|
84
|
+
z.object({
|
|
85
|
+
...patchChangeTaskStatusNodeOwnedSchema,
|
|
86
|
+
action: z.literal(TaskStatusAction.FINISH),
|
|
87
|
+
cost: z.number().min(0),
|
|
88
|
+
}),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
export type PatchChangeTaskStatusPayload = z.infer<typeof patchChangeTaskStatusSchema>;
|
|
92
|
+
|
|
93
|
+
export const patchTaskProgressSchema = z.object({
|
|
94
|
+
id: taskIdSchema,
|
|
95
|
+
progress: z.number().min(0).max(100),
|
|
96
|
+
nodeId: nodeIdSchema,
|
|
97
|
+
traceId: traceIdSchema,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export type PatchTaskProgressPayload = z.infer<typeof patchTaskProgressSchema>;
|
|
101
|
+
|
|
102
|
+
export const patchClaimTaskSchema = z.object({
|
|
103
|
+
nodeId: nodeIdSchema,
|
|
104
|
+
availableTaskList: z.array(z.string()).min(1),
|
|
105
|
+
traceId: traceIdSchema,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type PatchClaimTaskPayload = z.infer<typeof patchClaimTaskSchema>;
|
|
109
|
+
|
|
110
|
+
/** Initial status for a new task — OPEN (draft) or TODO (claimable by workers). */
|
|
111
|
+
const postTaskInitialStatusSchema = z
|
|
112
|
+
.union([z.literal(TaskStatus.OPEN), z.literal(TaskStatus.TODO)])
|
|
113
|
+
.optional()
|
|
114
|
+
.default(TaskStatus.TODO);
|
|
115
|
+
|
|
116
|
+
export const postTaskSchema = z.object({
|
|
117
|
+
name: z.string().trim().min(1),
|
|
118
|
+
/** Task type key — matches `task.value`, {@link patchClaimTask}, {@link Repo.value}, and local Repo registry. */
|
|
119
|
+
value: z.string().trim().min(1),
|
|
120
|
+
params: z.unknown(),
|
|
121
|
+
taskStatus: postTaskInitialStatusSchema,
|
|
122
|
+
tagList: z.array(z.string()).optional().default([]),
|
|
123
|
+
extra: z.string().optional(),
|
|
124
|
+
traceId: traceIdSchema,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export type PostTaskPayload = z.infer<typeof postTaskSchema>;
|
|
128
|
+
|
|
129
|
+
// ─── Optimistic lock helpers ─────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
function lockOnStatus(...allowed: TaskStatus[]): QueryFilter[] {
|
|
132
|
+
if (allowed.length === 1) {
|
|
133
|
+
return [{ field: TASK_STATUS_FIELD, operator: "eq", value: allowed[0] }];
|
|
134
|
+
}
|
|
135
|
+
return [{ field: TASK_STATUS_FIELD, operator: "in", value: allowed }];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function lockOnOwnerNode(nodeId: string): QueryFilter {
|
|
139
|
+
return { field: TASK_NODE_ID_FIELD, operator: "eq", value: nodeId };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** DOING row owned by `nodeId` — used by FINISH, CANCEL, patchTaskProgress */
|
|
143
|
+
function lockOnDoingOwner(nodeId: string): QueryFilter[] {
|
|
144
|
+
return [...lockOnStatus(TaskStatus.DOING), lockOnOwnerNode(nodeId)];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** TODO without nodeId; DOING/DONE with owner when nodeId is provided */
|
|
148
|
+
function lockOnReset(nodeId?: string): QueryFilter[] {
|
|
149
|
+
if (nodeId != null) {
|
|
150
|
+
return [...lockOnStatus(TaskStatus.DOING, TaskStatus.DONE), lockOnOwnerNode(nodeId)];
|
|
151
|
+
}
|
|
152
|
+
return lockOnStatus(TaskStatus.TODO);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function clearExecutionState(): Pick<TaskDetails, "progress" | "nodeId" | "result"> {
|
|
156
|
+
return { progress: 0, nodeId: null, result: undefined };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── Internal transitions ────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
async function transitionTask({
|
|
162
|
+
id,
|
|
163
|
+
optimisticLockFilterList,
|
|
164
|
+
updateFn,
|
|
165
|
+
updateExtraFn,
|
|
166
|
+
}: {
|
|
167
|
+
id: string;
|
|
168
|
+
optimisticLockFilterList: QueryFilter[];
|
|
169
|
+
updateFn: (details: TaskDetails) => TaskDetails;
|
|
170
|
+
updateExtraFn?: (existing: TaskDetails) => string;
|
|
171
|
+
}): Promise<Task> {
|
|
172
|
+
return updateTargetDetails<Task, TaskDetails>({
|
|
173
|
+
id,
|
|
174
|
+
optimisticLockFilterList,
|
|
175
|
+
updateFn,
|
|
176
|
+
updateExtraFn,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function logTaskTransition(
|
|
181
|
+
logger: LoggerWithContext,
|
|
182
|
+
action: TaskStatusAction | "patchTaskProgress" | "patchClaimTask",
|
|
183
|
+
task: Task,
|
|
184
|
+
context?: Record<string, unknown>
|
|
185
|
+
): void {
|
|
186
|
+
logger.info("任务状态迁移成功", {
|
|
187
|
+
topic: "task",
|
|
188
|
+
context: {
|
|
189
|
+
action,
|
|
190
|
+
taskId: task.id,
|
|
191
|
+
taskValue: task.value,
|
|
192
|
+
status: task.details.status,
|
|
193
|
+
nodeId: task.details.nodeId,
|
|
194
|
+
...context,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** TODO → DOING — lock status only (no owner yet); used by CLAIM and {@link patchClaimTask} */
|
|
200
|
+
async function claimTaskById({
|
|
201
|
+
taskId,
|
|
202
|
+
nodeId,
|
|
203
|
+
updateExtraFn,
|
|
204
|
+
}: {
|
|
205
|
+
taskId: string;
|
|
206
|
+
nodeId: string;
|
|
207
|
+
updateExtraFn?: (existing: TaskDetails) => string;
|
|
208
|
+
}): Promise<Task> {
|
|
209
|
+
return transitionTask({
|
|
210
|
+
id: taskId,
|
|
211
|
+
optimisticLockFilterList: lockOnStatus(TaskStatus.TODO),
|
|
212
|
+
updateFn: (details) => ({
|
|
213
|
+
...details,
|
|
214
|
+
status: TaskStatus.DOING,
|
|
215
|
+
nodeId,
|
|
216
|
+
}),
|
|
217
|
+
updateExtraFn,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── patchChangeTaskStatus ───────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
export const patchChangeTaskStatus = validateWithSchema(
|
|
224
|
+
patchChangeTaskStatusSchema,
|
|
225
|
+
"patchChangeTaskStatusSchema"
|
|
226
|
+
)(async (params) => {
|
|
227
|
+
const logger = createApiLogger("patchChangeTaskStatus", { traceId: params.traceId });
|
|
228
|
+
const { id, action, extra } = params;
|
|
229
|
+
const updateExtraFn = extra == null ? undefined : () => extra;
|
|
230
|
+
|
|
231
|
+
switch (action) {
|
|
232
|
+
case TaskStatusAction.PUBLISH: {
|
|
233
|
+
const data = await transitionTask({
|
|
234
|
+
id,
|
|
235
|
+
optimisticLockFilterList: lockOnStatus(TaskStatus.OPEN),
|
|
236
|
+
updateFn: (details) => ({
|
|
237
|
+
...details,
|
|
238
|
+
...clearExecutionState(),
|
|
239
|
+
status: TaskStatus.TODO,
|
|
240
|
+
}),
|
|
241
|
+
updateExtraFn,
|
|
242
|
+
});
|
|
243
|
+
logTaskTransition(logger, action, data);
|
|
244
|
+
return generateResponse.success<Task>(data);
|
|
245
|
+
}
|
|
246
|
+
case TaskStatusAction.CLAIM: {
|
|
247
|
+
const { nodeId } = params;
|
|
248
|
+
const data = await claimTaskById({ taskId: id, nodeId, updateExtraFn });
|
|
249
|
+
logTaskTransition(logger, action, data);
|
|
250
|
+
return generateResponse.success<Task>(data);
|
|
251
|
+
}
|
|
252
|
+
case TaskStatusAction.RESET: {
|
|
253
|
+
const { nodeId } = params;
|
|
254
|
+
const data = await transitionTask({
|
|
255
|
+
id,
|
|
256
|
+
optimisticLockFilterList: lockOnReset(nodeId),
|
|
257
|
+
updateFn: (details) => ({
|
|
258
|
+
...details,
|
|
259
|
+
...clearExecutionState(),
|
|
260
|
+
status: TaskStatus.OPEN,
|
|
261
|
+
}),
|
|
262
|
+
updateExtraFn,
|
|
263
|
+
});
|
|
264
|
+
logTaskTransition(logger, action, data, nodeId != null ? undefined : { resetFrom: "TODO" });
|
|
265
|
+
return generateResponse.success<Task>(data);
|
|
266
|
+
}
|
|
267
|
+
case TaskStatusAction.CANCEL: {
|
|
268
|
+
const { nodeId } = params;
|
|
269
|
+
const data = await transitionTask({
|
|
270
|
+
id,
|
|
271
|
+
optimisticLockFilterList: lockOnDoingOwner(nodeId),
|
|
272
|
+
updateFn: (details) => ({
|
|
273
|
+
...details,
|
|
274
|
+
...clearExecutionState(),
|
|
275
|
+
status: TaskStatus.TODO,
|
|
276
|
+
}),
|
|
277
|
+
updateExtraFn,
|
|
278
|
+
});
|
|
279
|
+
logTaskTransition(logger, action, data);
|
|
280
|
+
return generateResponse.success<Task>(data);
|
|
281
|
+
}
|
|
282
|
+
case TaskStatusAction.CLOSE: {
|
|
283
|
+
const data = await transitionTask({
|
|
284
|
+
id,
|
|
285
|
+
optimisticLockFilterList: lockOnStatus(TaskStatus.OPEN, TaskStatus.DONE),
|
|
286
|
+
updateFn: (details) => ({
|
|
287
|
+
...details,
|
|
288
|
+
status: TaskStatus.CLOSED,
|
|
289
|
+
}),
|
|
290
|
+
updateExtraFn,
|
|
291
|
+
});
|
|
292
|
+
logTaskTransition(logger, action, data);
|
|
293
|
+
return generateResponse.success<Task>(data);
|
|
294
|
+
}
|
|
295
|
+
case TaskStatusAction.FINISH: {
|
|
296
|
+
const { cost, nodeId } = params;
|
|
297
|
+
const data = await transitionTask({
|
|
298
|
+
id,
|
|
299
|
+
optimisticLockFilterList: lockOnDoingOwner(nodeId),
|
|
300
|
+
updateFn: (details) => ({
|
|
301
|
+
...details,
|
|
302
|
+
progress: 100,
|
|
303
|
+
status: TaskStatus.DONE,
|
|
304
|
+
nodeId,
|
|
305
|
+
result: {
|
|
306
|
+
cost,
|
|
307
|
+
code: ResultCode.SUCCESS,
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
updateExtraFn,
|
|
311
|
+
});
|
|
312
|
+
logTaskTransition(logger, action, data, { cost });
|
|
313
|
+
return generateResponse.success<Task>(data);
|
|
314
|
+
}
|
|
315
|
+
default: {
|
|
316
|
+
const _exhaustive: never = action;
|
|
317
|
+
throw new Error(`[patchChangeTaskStatus] Unknown action: ${_exhaustive}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ─── patchTaskProgress ───────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
/** Update progress on a DOING task; only the owning node may write (see `lockOnDoingOwner`). */
|
|
325
|
+
export const patchTaskProgress = validateWithSchema(
|
|
326
|
+
patchTaskProgressSchema,
|
|
327
|
+
"patchTaskProgressSchema"
|
|
328
|
+
)(async ({ id, progress, nodeId, traceId }) => {
|
|
329
|
+
const logger = createApiLogger("patchTaskProgress", { traceId, nodeId });
|
|
330
|
+
const data = await transitionTask({
|
|
331
|
+
id,
|
|
332
|
+
optimisticLockFilterList: lockOnDoingOwner(nodeId),
|
|
333
|
+
updateFn: (details) => ({
|
|
334
|
+
...details,
|
|
335
|
+
progress,
|
|
336
|
+
}),
|
|
337
|
+
});
|
|
338
|
+
logTaskTransition(logger, "patchTaskProgress", data, { progress });
|
|
339
|
+
return generateResponse.success<Task>(data);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// ─── patchClaimTask ──────────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Discover and claim one TODO task for this node.
|
|
346
|
+
*
|
|
347
|
+
* Flow: filter by `availableTaskList` → {@link claimTaskById} (same as `action: CLAIM`).
|
|
348
|
+
* Equivalent to `patchChangeTaskStatus({ id, action: CLAIM, nodeId })` when `id` is already known.
|
|
349
|
+
*
|
|
350
|
+
* Returns `data: null` when no TODO task matches.
|
|
351
|
+
* Optimistic lock conflicts return `{ error }` with `code: "OPTIMISTIC_LOCK"` (not thrown).
|
|
352
|
+
*/
|
|
353
|
+
export const patchClaimTask = validateWithSchema(
|
|
354
|
+
patchClaimTaskSchema,
|
|
355
|
+
"patchClaimTaskSchema"
|
|
356
|
+
)(async ({ nodeId, availableTaskList, traceId }): Promise<SupabaseResponse<Task | null>> => {
|
|
357
|
+
const logger = createApiLogger("patchClaimTask", { traceId, nodeId });
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const { data: possibleTask } = await getPossibleTarget({
|
|
361
|
+
filterList: [
|
|
362
|
+
{ field: "category", operator: "eq", value: CategoryTask.TASK },
|
|
363
|
+
{ field: TASK_STATUS_FIELD, operator: "eq", value: TaskStatus.TODO },
|
|
364
|
+
{ field: "value", operator: "in", value: availableTaskList },
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (possibleTask == null) {
|
|
369
|
+
return generateResponse.success(null);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const todoTask = possibleTask as Task;
|
|
373
|
+
const data = await claimTaskById({ taskId: todoTask.id, nodeId });
|
|
374
|
+
logTaskTransition(logger, "patchClaimTask", data);
|
|
375
|
+
return generateResponse.success(data);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (isOptimisticLockError(error)) {
|
|
378
|
+
logger.debug("認領競爭失敗,本輪無可認領任務", { topic: "task" });
|
|
379
|
+
return generateResponse.error(OPTIMISTIC_LOCK_FAILED_MESSAGE, undefined, "OPTIMISTIC_LOCK") as SupabaseResponse<Task | null>;
|
|
380
|
+
}
|
|
381
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
382
|
+
logger.error("認領任務失敗", { topic: "task", context: { error: message } });
|
|
383
|
+
return generateResponse.error(message) as SupabaseResponse<Task | null>;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// ─── postTask ────────────────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Create a task row (scheduler / admin).
|
|
391
|
+
*
|
|
392
|
+
* `taskStatus` defaults to **TODO** (worker-claimable). Pass **OPEN** for draft tasks
|
|
393
|
+
* that require {@link patchChangeTaskStatus} `publish` before workers can claim.
|
|
394
|
+
*/
|
|
395
|
+
export const postTask = validateWithSchema(
|
|
396
|
+
postTaskSchema,
|
|
397
|
+
"postTaskSchema"
|
|
398
|
+
)(async ({ name, value, params, taskStatus, tagList, extra, traceId }) => {
|
|
399
|
+
const logger = createApiLogger("postTask", { traceId });
|
|
400
|
+
|
|
401
|
+
const result = await createTarget<Task, PostTaskPayload>({
|
|
402
|
+
payload: { name, value, params, taskStatus, tagList, extra, traceId },
|
|
403
|
+
createFn: () => ({
|
|
404
|
+
name,
|
|
405
|
+
value,
|
|
406
|
+
category: CategoryTask.TASK,
|
|
407
|
+
tagList,
|
|
408
|
+
extra,
|
|
409
|
+
details: {
|
|
410
|
+
manifestVersion: 0,
|
|
411
|
+
status: taskStatus,
|
|
412
|
+
params,
|
|
413
|
+
progress: 0,
|
|
414
|
+
nodeId: null,
|
|
415
|
+
},
|
|
416
|
+
}),
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
logger.info("任務已創建", {
|
|
420
|
+
topic: "task",
|
|
421
|
+
context: {
|
|
422
|
+
taskId: result.data?.id,
|
|
423
|
+
taskTypeKey: value,
|
|
424
|
+
status: taskStatus,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return result;
|
|
429
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Target } from "../core.interface";
|
|
2
|
-
import { Repo } from "../repo/repo.interface";
|
|
3
2
|
|
|
4
3
|
export enum CategoryTask {
|
|
5
4
|
TASK = "task",
|
|
@@ -13,6 +12,16 @@ export enum TaskStatus {
|
|
|
13
12
|
CLOSED = "CLOSED",
|
|
14
13
|
}
|
|
15
14
|
|
|
15
|
+
/** Named transitions for {@link patchChangeTaskStatus} — verb aligned with state machine edges */
|
|
16
|
+
export enum TaskStatusAction {
|
|
17
|
+
PUBLISH = "publish",
|
|
18
|
+
CLAIM = "claim",
|
|
19
|
+
RESET = "reset",
|
|
20
|
+
CANCEL = "cancel",
|
|
21
|
+
CLOSE = "close",
|
|
22
|
+
FINISH = "finish",
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export enum ResultCode {
|
|
17
26
|
SUCCESS = 1000,
|
|
18
27
|
|
|
@@ -40,7 +49,6 @@ export interface Task extends Target {
|
|
|
40
49
|
export interface TaskDetails {
|
|
41
50
|
manifestVersion: number;
|
|
42
51
|
status: TaskStatus;
|
|
43
|
-
repo: Repo;
|
|
44
52
|
params: unknown;
|
|
45
53
|
/** [0,100] */
|
|
46
54
|
progress: number;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=task-node-manager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"task-node-manager.d.ts","sourceRoot":"","sources":["../../src/task/task-node-manager.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"task-node-manager.js","sourceRoot":"","sources":["../../src/task/task-node-manager.ts"],"names":[],"mappings":""}
|
|
File without changes
|