target-supabase-sdk 0.2.22 → 0.2.23

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.
@@ -1,5 +1,6 @@
1
1
  import { BaseNodeRuntime, type NodeLoopContext } from "../node/node-runtime.base";
2
2
  import { type LoggerWithScope } from "../shared/log";
3
+ import { type PatchClaimTaskPayload } from "./task.api";
3
4
  /**
4
5
  * Task worker node: commands → heartbeat → claim & execute tasks.
5
6
  * Built on {@link BaseNodeRuntime}.
@@ -8,6 +9,8 @@ declare class TaskNode extends BaseNodeRuntime {
8
9
  private _availableTaskList;
9
10
  private _includeRemote;
10
11
  get availableTaskList(): readonly string[];
12
+ /** Optional claim filters passed to {@link patchClaimTask} (e.g. name prefix per task type). */
13
+ protected getPatchClaimTaskOptions(): Pick<PatchClaimTaskPayload, "namePrefixList">;
11
14
  constructor();
12
15
  protected onBeforeRegisterNode(logger: LoggerWithScope): Promise<void>;
13
16
  protected runLoopSteps(ctx: NodeLoopContext, heartbeatOk: boolean): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"task-node.d.ts","sourceRoot":"","sources":["../../src/task/task-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAkB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAA6B,KAAK,eAAe,EAAc,MAAM,eAAe,CAAC;AAO5F;;;GAGG;AACH,cAAM,QAAS,SAAQ,eAAe;IAClC,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAQ;IAE9B,IAAI,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAEzC;;cAMe,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;cAM5D,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;YAezE,WAAW;YA6KX,YAAY;YA2BZ,eAAe;CAqDhC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"task-node.d.ts","sourceRoot":"","sources":["../../src/task/task-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAkB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAA6B,KAAK,eAAe,EAAc,MAAM,eAAe,CAAC;AAE5F,OAAO,EAAE,KAAK,qBAAqB,EAAyC,MAAM,YAAY,CAAC;AAK/F;;;GAGG;AACH,cAAM,QAAS,SAAQ,eAAe;IAClC,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAQ;IAE9B,IAAI,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAEzC;IAED,gGAAgG;IAChG,SAAS,CAAC,wBAAwB,IAAI,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;;cAQnE,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;cAM5D,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;YAezE,WAAW;YA8KX,YAAY;YA2BZ,eAAe;CAqDhC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -127,16 +127,24 @@ export declare const patchTaskProgressSchema: z.ZodObject<{
127
127
  export type PatchTaskProgressPayload = z.infer<typeof patchTaskProgressSchema>;
128
128
  export declare const patchClaimTaskSchema: z.ZodObject<{
129
129
  nodeId: z.ZodString;
130
+ /** Task `value` must be in this list (always applied). */
130
131
  availableTaskList: z.ZodArray<z.ZodString, "many">;
131
132
  traceId: z.ZodOptional<z.ZodString>;
133
+ /**
134
+ * Optional second axis on `name` (AND with `availableTaskList`, not coupled to `value`).
135
+ * When non-empty: `name` must equal a prefix or start with `prefix:`; query uses PostgREST `ilike`.
136
+ */
137
+ namePrefixList: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
132
138
  }, "strip", z.ZodTypeAny, {
133
139
  nodeId: string;
134
140
  availableTaskList: string[];
135
141
  traceId?: string | undefined;
142
+ namePrefixList?: string[] | undefined;
136
143
  }, {
137
144
  nodeId: string;
138
145
  availableTaskList: string[];
139
146
  traceId?: string | undefined;
147
+ namePrefixList?: string[] | undefined;
140
148
  }>;
141
149
  export type PatchClaimTaskPayload = z.infer<typeof patchClaimTaskSchema>;
142
150
  export declare const patchChangeTaskStatus: (payload: {
@@ -185,7 +193,11 @@ export declare const patchTaskProgress: (payload: {
185
193
  /**
186
194
  * Discover and claim one TODO task for this node.
187
195
  *
188
- * Flow: oldest TODO in `availableTaskList` (FIFO by `created_at`) → {@link claimTaskById}.
196
+ * Match criteria (all required): `details.status = TODO`, `value ∈ availableTaskList`, and when
197
+ * `namePrefixList` is non-empty, `name` matches any listed prefix. Those filters are independent
198
+ * (AND); omitting `namePrefixList` does not relax the `value` constraint.
199
+ *
200
+ * Flow: oldest matching row (FIFO by `created_at`) → {@link claimTaskById}.
189
201
  * Equivalent to `patchChangeTaskStatus({ id, action: CLAIM, nodeId })` when `id` is already known.
190
202
  *
191
203
  * Returns `data: null` when no TODO task matches.
@@ -195,5 +207,6 @@ export declare const patchClaimTask: (payload: {
195
207
  nodeId: string;
196
208
  availableTaskList: string[];
197
209
  traceId?: string | undefined;
210
+ namePrefixList?: string[] | undefined;
198
211
  }) => SupabaseResponse<Task | null> | Promise<SupabaseResponse<Task | null>>;
199
212
  //# sourceMappingURL=task.api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"task.api.d.ts","sourceRoot":"","sources":["../../src/task/task.api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI5E,OAAO,EAA4B,KAAK,IAAI,EAAgC,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA0DvH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BtC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEvF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;EAKlC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE/E,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAgGzE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAmGhC,CAAC;AAIH,gGAAgG;AAChG,eAAO,MAAM,iBAAiB;;;;;8DAe5B,CAAC;AAIH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;;4EA0DzB,CAAC"}
1
+ {"version":3,"file":"task.api.d.ts","sourceRoot":"","sources":["../../src/task/task.api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAM5E,OAAO,EAA4B,KAAK,IAAI,EAAgC,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA0DvH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BtC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEvF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;EAKlC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE/E,eAAO,MAAM,oBAAoB;;IAE7B,0DAA0D;;;IAG1D;;;OAGG;;;;;;;;;;;;EAEL,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA0GzE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAmGhC,CAAC;AAIH,gGAAgG;AAChG,eAAO,MAAM,iBAAiB;;;;;8DAe5B,CAAC;AAIH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc;;;;;4EAkFzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "target-supabase-sdk",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "Supabase client initializer and data access APIs",
5
5
  "license": "MIT",
6
6
  "author": "KyLoc20",
@@ -2,7 +2,7 @@ import { isOptimisticLockResponse } from "../core.api";
2
2
  import { BaseNodeRuntime, LOG_TOPIC_NODE, type NodeLoopContext } from "../node/node-runtime.base";
3
3
  import { createLogger, createScope, type LoggerWithScope, withModule } from "../shared/log";
4
4
  import { getErrorMessage, toError } from "../shared/utils/error.utils";
5
- import { patchChangeTaskStatus, patchClaimTask } from "./task.api";
5
+ import { type PatchClaimTaskPayload, patchChangeTaskStatus, patchClaimTask } from "./task.api";
6
6
  import { CRITICAL_UNKNOWN_TASK_TRACE_ID, LOG_TOPIC_TASK } from "./task.constant";
7
7
  import { TaskStatusAction } from "./task.interface";
8
8
  import { TaskManager, type TaskRunResult } from "./task-manager";
@@ -19,6 +19,11 @@ class TaskNode extends BaseNodeRuntime {
19
19
  return [...this._availableTaskList];
20
20
  }
21
21
 
22
+ /** Optional claim filters passed to {@link patchClaimTask} (e.g. name prefix per task type). */
23
+ protected getPatchClaimTaskOptions(): Pick<PatchClaimTaskPayload, "namePrefixList"> {
24
+ return {};
25
+ }
26
+
22
27
  constructor() {
23
28
  super("taskNode");
24
29
  }
@@ -63,6 +68,7 @@ class TaskNode extends BaseNodeRuntime {
63
68
  nodeId,
64
69
  availableTaskList: [...availableTaskList],
65
70
  traceId: loopTraceId,
71
+ ...this.getPatchClaimTaskOptions(),
66
72
  });
67
73
  if (claimError) {
68
74
  if (isOptimisticLockResponse(claimError)) {
@@ -5,12 +5,15 @@ import {
5
5
  OPTIMISTIC_LOCK_ERROR_CODE,
6
6
  OPTIMISTIC_LOCK_FAILED_MESSAGE,
7
7
  type QueryFilter,
8
+ type TargetFilterBuilder,
8
9
  updateTargetDetails,
9
10
  validateWithSchema,
10
11
  } from "../core.api";
11
12
  import { generateResponse, type SupabaseResponse } from "../core.interface";
12
13
  import { createLogger, type LoggerWithScope } from "../shared/log";
13
14
  import { getErrorMessage } from "../shared/utils/error.utils";
15
+ import { escapeIlikePattern } from "../shared/utils/postgrest.utils";
16
+ import { supabase } from "../supabase";
14
17
  import { LOG_TOPIC_TASK } from "./task.constant";
15
18
  import { CategoryTask, ResultCode, type Task, type TaskDetails, TaskStatus, TaskStatusAction } from "./task.interface";
16
19
 
@@ -112,12 +115,28 @@ export type PatchTaskProgressPayload = z.infer<typeof patchTaskProgressSchema>;
112
115
 
113
116
  export const patchClaimTaskSchema = z.object({
114
117
  nodeId: nodeIdSchema,
118
+ /** Task `value` must be in this list (always applied). */
115
119
  availableTaskList: z.array(z.string()).min(1),
116
120
  traceId: traceIdSchema,
121
+ /**
122
+ * Optional second axis on `name` (AND with `availableTaskList`, not coupled to `value`).
123
+ * When non-empty: `name` must equal a prefix or start with `prefix:`; query uses PostgREST `ilike`.
124
+ */
125
+ namePrefixList: z.array(z.string().trim().min(1)).optional(),
117
126
  });
118
127
 
119
128
  export type PatchClaimTaskPayload = z.infer<typeof patchClaimTaskSchema>;
120
129
 
130
+ /** `name === prefix` or `name.startsWith(prefix + ':')` via `name ilike 'prefix%'`. */
131
+ function buildClaimTaskNamePrefixFilterBuilder(namePrefixes: string[]): TargetFilterBuilder {
132
+ const base = supabase.client.from("target").select("*");
133
+ if (namePrefixes.length === 1) {
134
+ return base.ilike("name", `${escapeIlikePattern(namePrefixes[0])}%`);
135
+ }
136
+ const nameOrParts = namePrefixes.map((prefix) => `name.ilike.${escapeIlikePattern(prefix)}%`);
137
+ return base.or(nameOrParts.join(","));
138
+ }
139
+
121
140
  // ─── Optimistic lock helpers ─────────────────────────────────────────────────
122
141
 
123
142
  function lockOnStatus(...allowed: TaskStatus[]): QueryFilter[] {
@@ -338,7 +357,11 @@ export const patchTaskProgress = validateWithSchema(
338
357
  /**
339
358
  * Discover and claim one TODO task for this node.
340
359
  *
341
- * Flow: oldest TODO in `availableTaskList` (FIFO by `created_at`) → {@link claimTaskById}.
360
+ * Match criteria (all required): `details.status = TODO`, `value ∈ availableTaskList`, and when
361
+ * `namePrefixList` is non-empty, `name` matches any listed prefix. Those filters are independent
362
+ * (AND); omitting `namePrefixList` does not relax the `value` constraint.
363
+ *
364
+ * Flow: oldest matching row (FIFO by `created_at`) → {@link claimTaskById}.
342
365
  * Equivalent to `patchChangeTaskStatus({ id, action: CLAIM, nodeId })` when `id` is already known.
343
366
  *
344
367
  * Returns `data: null` when no TODO task matches.
@@ -347,25 +370,49 @@ export const patchTaskProgress = validateWithSchema(
347
370
  export const patchClaimTask = validateWithSchema(
348
371
  patchClaimTaskSchema,
349
372
  "patchClaimTaskSchema",
350
- )(async ({ nodeId, availableTaskList, traceId }): Promise<SupabaseResponse<Task | null>> => {
373
+ )(async ({ nodeId, availableTaskList, traceId, namePrefixList }): Promise<SupabaseResponse<Task | null>> => {
351
374
  const logger = createLogger({ module: "patchClaimTask", traceId, labels: { nodeId } });
352
375
 
376
+ const namePrefixes = (namePrefixList ?? []).map((prefix) => prefix.trim()).filter((prefix) => prefix !== "");
377
+
378
+ function taskNameMatchesPrefixList(name: string): boolean {
379
+ return namePrefixes.some((prefix) => name === prefix || name.startsWith(`${prefix}:`));
380
+ }
381
+
353
382
  let todoTask: Task;
354
383
  try {
355
- const { data: taskCandidates } = await getTargetList<Task>({
356
- category: CategoryTask.TASK,
357
- filterList: [
358
- { field: TASK_STATUS_FIELD, operator: "eq", value: TaskStatus.TODO },
359
- { field: "value", operator: "in", value: availableTaskList },
360
- ],
361
- limit: 1,
362
- orderBy: { field: "created_at", ascending: true },
363
- });
364
- const possibleTask = taskCandidates?.[0] ?? null;
365
- if (possibleTask == null) {
366
- return generateResponse.success(null);
384
+ if (namePrefixes.length === 0) {
385
+ const { data: taskCandidates } = await getTargetList<Task>({
386
+ category: CategoryTask.TASK,
387
+ filterList: [
388
+ { field: TASK_STATUS_FIELD, operator: "eq", value: TaskStatus.TODO },
389
+ { field: "value", operator: "in", value: availableTaskList },
390
+ ],
391
+ limit: 1,
392
+ orderBy: { field: "created_at", ascending: true },
393
+ });
394
+ const possibleTask = taskCandidates?.[0] ?? null;
395
+ if (possibleTask == null) {
396
+ return generateResponse.success(null);
397
+ }
398
+ todoTask = possibleTask;
399
+ } else {
400
+ const { data: taskCandidates } = await getTargetList<Task>({
401
+ category: CategoryTask.TASK,
402
+ filterList: [
403
+ { field: TASK_STATUS_FIELD, operator: "eq", value: TaskStatus.TODO },
404
+ { field: "value", operator: "in", value: availableTaskList },
405
+ ],
406
+ limit: 1,
407
+ orderBy: { field: "created_at", ascending: true },
408
+ filterBuilder: buildClaimTaskNamePrefixFilterBuilder(namePrefixes),
409
+ });
410
+ const possibleTask = taskCandidates?.find((task) => taskNameMatchesPrefixList(task.name)) ?? null;
411
+ if (possibleTask == null) {
412
+ return generateResponse.success(null);
413
+ }
414
+ todoTask = possibleTask;
367
415
  }
368
- todoTask = possibleTask;
369
416
  } catch (error) {
370
417
  const message = getErrorMessage(error);
371
418
  logger.error("獲取 TODO 任務失敗", { topic: LOG_TOPIC_TASK, data: { error: message, availableTaskList } });