@nmakarov/cli-toolkit 0.68.0 → 0.70.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/dist/tasks.js CHANGED
@@ -342,12 +342,13 @@ function tasksSchemaSpec(queueName = "tasks") {
342
342
  }
343
343
  };
344
344
  }
345
- function taskHistoryInsertFromQueueRow(row, overrides) {
345
+ function taskHistoryInsertFromQueueRow(row, overrides = {}) {
346
346
  const { id, ...snapshot } = row;
347
- void id;
347
+ const opid = overrides.opid !== void 0 ? overrides.opid : snapshot.opid != null && String(snapshot.opid).trim() !== "" ? snapshot.opid : id;
348
348
  return {
349
349
  ...snapshot,
350
- ...overrides
350
+ ...overrides,
351
+ opid
351
352
  };
352
353
  }
353
354
  async function ensureTaskTables(context, options = {}) {
@@ -2686,69 +2687,6 @@ var TaskStopRunner = class extends AbstractTask {
2686
2687
  }
2687
2688
  };
2688
2689
 
2689
- // src/tasks/coreTasks/TaskGetLogs.js
2690
- var TaskGetLogs = class extends AbstractTask {
2691
- /**
2692
- * @param {object} context
2693
- * @param {Record<string, unknown>} [overrides]
2694
- * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
2695
- */
2696
- static async resolveCustomParams(context, overrides = {}) {
2697
- const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
2698
- source: "string",
2699
- resource: "string",
2700
- tail: "number default 100",
2701
- afterTs: "string"
2702
- }, overrides);
2703
- const source = typeof merged.source === "string" ? merged.source.trim() : "";
2704
- const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
2705
- if (!source) throw new ParamError('getLogs: param "source" is required');
2706
- if (!resource) throw new ParamError('getLogs: param "resource" is required');
2707
- let tail = Number(merged.tail);
2708
- if (!Number.isFinite(tail) || tail < 1) tail = 100;
2709
- tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
2710
- const out = { source, resource, tail };
2711
- if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
2712
- out.afterTs = merged.afterTs.trim();
2713
- }
2714
- return out;
2715
- }
2716
- /**
2717
- * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
2718
- * @returns {Promise<{ success: boolean, results: unknown }>}
2719
- */
2720
- async run(_reportProgress) {
2721
- const p = this.task.params ?? {};
2722
- const source = String(p.source ?? "").trim();
2723
- const resource = String(p.resource ?? "").trim();
2724
- const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
2725
- const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
2726
- if (!source || !resource) {
2727
- return {
2728
- success: false,
2729
- results: { error: 'getLogs requires params "source" and "resource"' }
2730
- };
2731
- }
2732
- try {
2733
- const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
2734
- source,
2735
- resource,
2736
- tail,
2737
- afterTs
2738
- });
2739
- return {
2740
- success: true,
2741
- results: { records, latestTs, source, resource }
2742
- };
2743
- } catch (e) {
2744
- return {
2745
- success: false,
2746
- results: { error: e?.message ?? String(e) }
2747
- };
2748
- }
2749
- }
2750
- };
2751
-
2752
2690
  // src/tasks/runtimeParams.js
2753
2691
  var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
2754
2692
  var LOGGER_RUNTIME_KEYS = [
@@ -2762,7 +2700,16 @@ var LOGGER_RUNTIME_KEYS = [
2762
2700
  "progressWithTimes",
2763
2701
  "progressThrottleMs"
2764
2702
  ];
2765
- var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
2703
+ var CONTROL_LANE_TASK_NAMES = [
2704
+ "stopRunner",
2705
+ "stop",
2706
+ "pauseRunner",
2707
+ "pause",
2708
+ "unpauseRunner",
2709
+ "unpause",
2710
+ "setRuntimeParam",
2711
+ "setRunnerParam"
2712
+ ];
2766
2713
  function controlLaneTaskNames(extra) {
2767
2714
  const names = [...CONTROL_LANE_TASK_NAMES];
2768
2715
  if (extra == null || extra === "") return names;
@@ -2825,6 +2772,7 @@ function ensureTasksRuntime(context, seed = {}) {
2825
2772
  if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
2826
2773
  if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
2827
2774
  if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
2775
+ if (rt.paused === void 0) rt.paused = seed.paused === true;
2828
2776
  return rt;
2829
2777
  }
2830
2778
  async function applyRuntimeParam(context, key, value) {
@@ -2889,6 +2837,140 @@ function readLoopRuntime(context) {
2889
2837
  };
2890
2838
  }
2891
2839
 
2840
+ // src/tasks/coreTasks/TaskPauseRunner.js
2841
+ async function applyRunnerPaused(context, paused) {
2842
+ const runtime = ensureTasksRuntime(context);
2843
+ const was = runtime.paused === true;
2844
+ runtime.paused = paused === true;
2845
+ const registry = context.servicesRegistry;
2846
+ if (registry && typeof registry === "object") {
2847
+ await updateServicesRegistryMetadata(context, registry, {
2848
+ paused: runtime.paused,
2849
+ pausedAt: runtime.paused ? (/* @__PURE__ */ new Date()).toISOString() : null
2850
+ });
2851
+ }
2852
+ const message = runtime.paused ? was ? "Runner already paused (no new worker tasks)." : "Runner paused: finishing in-flight work; no new worker tasks until unpause." : was ? "Runner unpaused: claiming worker tasks again." : "Runner already running (not paused).";
2853
+ context.logger?.warn?.(
2854
+ runtime.paused ? `[TaskPauseRunner] ${message}` : `[TaskUnpauseRunner] ${message}`
2855
+ );
2856
+ return {
2857
+ success: true,
2858
+ results: {
2859
+ paused: runtime.paused,
2860
+ message
2861
+ }
2862
+ };
2863
+ }
2864
+ var TaskPauseRunner = class extends AbstractTask {
2865
+ static taskName = "pauseRunner";
2866
+ static description = "Pause a runner: finish in-flight tasks, claim no new worker tasks until unpaused";
2867
+ static aliases = ["pause"];
2868
+ static defaultWaitForResult = true;
2869
+ /**
2870
+ * @param {object} context
2871
+ * @param {Record<string, unknown>} [overrides]
2872
+ * @returns {Promise<object>}
2873
+ */
2874
+ static async resolveParams(context, overrides = {}) {
2875
+ const main = await super.resolveParams(context, overrides);
2876
+ if (!main.serviceName) {
2877
+ throw new ParamError(
2878
+ "pause/pauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
2879
+ );
2880
+ }
2881
+ return main;
2882
+ }
2883
+ async run() {
2884
+ return applyRunnerPaused(this.context, true);
2885
+ }
2886
+ };
2887
+ var TaskUnpauseRunner = class extends AbstractTask {
2888
+ static taskName = "unpauseRunner";
2889
+ static description = "Unpause a runner: resume claiming worker tasks";
2890
+ static aliases = ["unpause"];
2891
+ static defaultWaitForResult = true;
2892
+ /**
2893
+ * @param {object} context
2894
+ * @param {Record<string, unknown>} [overrides]
2895
+ * @returns {Promise<object>}
2896
+ */
2897
+ static async resolveParams(context, overrides = {}) {
2898
+ const main = await super.resolveParams(context, overrides);
2899
+ if (!main.serviceName) {
2900
+ throw new ParamError(
2901
+ "unpause/unpauseRunner requires --serviceName (registry instance name; optional --serviceGroup, --instanceNumber, --serverName to narrow targeting)"
2902
+ );
2903
+ }
2904
+ return main;
2905
+ }
2906
+ async run() {
2907
+ return applyRunnerPaused(this.context, false);
2908
+ }
2909
+ };
2910
+
2911
+ // src/tasks/coreTasks/TaskGetLogs.js
2912
+ var TaskGetLogs = class extends AbstractTask {
2913
+ /**
2914
+ * @param {object} context
2915
+ * @param {Record<string, unknown>} [overrides]
2916
+ * @returns {Promise<{ source: string, resource: string, tail: number, afterTs?: string }>}
2917
+ */
2918
+ static async resolveCustomParams(context, overrides = {}) {
2919
+ const merged = AbstractTask._mergeTypedParams(context, "task-get-logs", {
2920
+ source: "string",
2921
+ resource: "string",
2922
+ tail: "number default 100",
2923
+ afterTs: "string"
2924
+ }, overrides);
2925
+ const source = typeof merged.source === "string" ? merged.source.trim() : "";
2926
+ const resource = typeof merged.resource === "string" ? merged.resource.trim() : "";
2927
+ if (!source) throw new ParamError('getLogs: param "source" is required');
2928
+ if (!resource) throw new ParamError('getLogs: param "resource" is required');
2929
+ let tail = Number(merged.tail);
2930
+ if (!Number.isFinite(tail) || tail < 1) tail = 100;
2931
+ tail = Math.min(1e4, Math.max(1, Math.floor(tail)));
2932
+ const out = { source, resource, tail };
2933
+ if (typeof merged.afterTs === "string" && merged.afterTs.trim()) {
2934
+ out.afterTs = merged.afterTs.trim();
2935
+ }
2936
+ return out;
2937
+ }
2938
+ /**
2939
+ * @param {unknown} _reportProgress Unused (single-shot read, no progress events).
2940
+ * @returns {Promise<{ success: boolean, results: unknown }>}
2941
+ */
2942
+ async run(_reportProgress) {
2943
+ const p = this.task.params ?? {};
2944
+ const source = String(p.source ?? "").trim();
2945
+ const resource = String(p.resource ?? "").trim();
2946
+ const tail = Math.max(1, Math.min(1e4, Number(p.tail) > 0 ? Number(p.tail) : 100));
2947
+ const afterTs = p.afterTs != null && String(p.afterTs).trim() ? String(p.afterTs).trim() : null;
2948
+ if (!source || !resource) {
2949
+ return {
2950
+ success: false,
2951
+ results: { error: 'getLogs requires params "source" and "resource"' }
2952
+ };
2953
+ }
2954
+ try {
2955
+ const { records, latestTs } = await readTaskIpcLogsSnapshot(this.context, {
2956
+ source,
2957
+ resource,
2958
+ tail,
2959
+ afterTs
2960
+ });
2961
+ return {
2962
+ success: true,
2963
+ results: { records, latestTs, source, resource }
2964
+ };
2965
+ } catch (e) {
2966
+ return {
2967
+ success: false,
2968
+ results: { error: e?.message ?? String(e) }
2969
+ };
2970
+ }
2971
+ }
2972
+ };
2973
+
2892
2974
  // src/tasks/coreTasks/TaskSetRuntimeParam.js
2893
2975
  var TaskSetRuntimeParam = class extends AbstractTask {
2894
2976
  static defaultWaitForResult = true;
@@ -3044,7 +3126,7 @@ var TasksRegistry = class _TasksRegistry {
3044
3126
  * @returns {TasksRegistry}
3045
3127
  */
3046
3128
  static withCoreTasks() {
3047
- return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
3129
+ return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("pauseRunner", TaskPauseRunner).add("pause", TaskPauseRunner).add("unpauseRunner", TaskUnpauseRunner).add("unpause", TaskUnpauseRunner).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
3048
3130
  }
3049
3131
  /**
3050
3132
  * Register a single task class under a name. Overwrites any previous entry.
@@ -3147,6 +3229,10 @@ var SERVICE_TASK_NAMES = [
3147
3229
  "ping",
3148
3230
  "stop",
3149
3231
  "stopRunner",
3232
+ "pause",
3233
+ "pauseRunner",
3234
+ "unpause",
3235
+ "unpauseRunner",
3150
3236
  "shellCommand",
3151
3237
  "systemInfo",
3152
3238
  "info",
@@ -3612,6 +3698,7 @@ async function runTasksLoop(context, options) {
3612
3698
  const defaultMeta = {
3613
3699
  component: "tasks-runner",
3614
3700
  allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
3701
+ paused: false,
3615
3702
  runtime: {
3616
3703
  maxParallel: loop0.maxParallel,
3617
3704
  pollMs: loop0.pollMs,
@@ -3678,28 +3765,38 @@ async function runTasksLoop(context, options) {
3678
3765
  if (claimJitterMs > 0) {
3679
3766
  await sleepMs(Math.floor(Math.random() * (claimJitterMs + 1)));
3680
3767
  }
3681
- while (runningPromises.size < maxParallel) {
3682
- const claimed = await claimNextRunnableTask(
3683
- context,
3684
- tasksTable,
3685
- target,
3686
- registry,
3687
- scanLimit,
3688
- allowedTasks,
3689
- runnerIdentity
3690
- );
3691
- if (!claimed) break;
3692
- const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
3693
- if (outcome.stopRunnerRequested && !stopRequested) {
3694
- stopRequested = true;
3695
- stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
3696
- context.tasksRunnerStop = true;
3697
- await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
3698
- }
3699
- }).finally(() => {
3700
- runningPromises.delete(p);
3701
- });
3702
- runningPromises.add(p);
3768
+ const paused = context.tasksRuntime?.paused === true;
3769
+ if (!paused) {
3770
+ while (runningPromises.size < maxParallel) {
3771
+ const claimed = await claimNextRunnableTask(
3772
+ context,
3773
+ tasksTable,
3774
+ target,
3775
+ registry,
3776
+ scanLimit,
3777
+ allowedTasks,
3778
+ runnerIdentity
3779
+ );
3780
+ if (!claimed) break;
3781
+ const p = executeClaimedTask(
3782
+ context,
3783
+ tasksTable,
3784
+ historyTable,
3785
+ claimed,
3786
+ registry,
3787
+ runningTaskInstances
3788
+ ).then(async (outcome) => {
3789
+ if (outcome.stopRunnerRequested && !stopRequested) {
3790
+ stopRequested = true;
3791
+ stopAllowanceMs = outcome.stopAllowanceMs || stopAllowanceMs;
3792
+ context.tasksRunnerStop = true;
3793
+ await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
3794
+ }
3795
+ }).finally(() => {
3796
+ runningPromises.delete(p);
3797
+ });
3798
+ runningPromises.add(p);
3799
+ }
3703
3800
  }
3704
3801
  const wakePromises = [...runningPromises];
3705
3802
  if (runningControlPromise) {
@@ -3753,16 +3850,26 @@ async function waitForTaskResult(context, taskId, options = {}) {
3753
3850
  const pollMs = options.pollMs ?? 500;
3754
3851
  const { tasksTable, historyTable } = queueToTableNames(queueName);
3755
3852
  const deadline = Date.now() + timeoutMs;
3756
- const waitStartedAt = /* @__PURE__ */ new Date();
3757
- let cachedNameOpid = null;
3758
- async function historySinceWait(name, opid) {
3759
- let q = db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
3853
+ const waitStartedAt = new Date(Date.now() - 5e3);
3854
+ let cachedNameOpid = options.name != null && String(options.name).trim() !== "" ? {
3855
+ name: String(options.name).trim(),
3856
+ opid: options.opid !== void 0 ? options.opid : null
3857
+ } : null;
3858
+ async function findHistory(name, opid) {
3859
+ const base = () => db(historyTable).where({ name }).where("completed_at", ">=", waitStartedAt);
3860
+ if (opid != null && String(opid).trim() !== "") {
3861
+ const byOpid = await base().where({ opid }).orderBy("completed_at", "desc").first();
3862
+ if (byOpid) return byOpid;
3863
+ }
3864
+ const byQueueId = await base().where({ opid: taskId }).orderBy("completed_at", "desc").first();
3865
+ if (byQueueId) return byQueueId;
3866
+ const byQueueIdAny = await db(historyTable).where({ name, opid: taskId }).orderBy("completed_at", "desc").first();
3867
+ if (byQueueIdAny) return byQueueIdAny;
3760
3868
  if (opid == null || opid === "") {
3761
- q = q.whereNull("opid");
3762
- } else {
3763
- q = q.where({ opid });
3869
+ const byNull = await base().whereNull("opid").orderBy("completed_at", "desc").first();
3870
+ if (byNull) return byNull;
3764
3871
  }
3765
- return await q.orderBy("completed_at", "desc").first();
3872
+ return void 0;
3766
3873
  }
3767
3874
  while (Date.now() <= deadline) {
3768
3875
  const legacy = await db(historyTable).where({ id: taskId }).orderBy("created_at", "desc").first();
@@ -3772,18 +3879,17 @@ async function waitForTaskResult(context, taskId, options = {}) {
3772
3879
  const pending = await db(tasksTable).where({ id: taskId }).first();
3773
3880
  if (pending) {
3774
3881
  cachedNameOpid = { name: pending.name, opid: pending.opid };
3775
- const done = await historySinceWait(pending.name, pending.opid);
3776
- if (done) {
3777
- return done;
3778
- }
3779
- } else if (cachedNameOpid) {
3780
- const done = await historySinceWait(cachedNameOpid.name, cachedNameOpid.opid);
3882
+ }
3883
+ if (cachedNameOpid) {
3884
+ const done = await findHistory(cachedNameOpid.name, cachedNameOpid.opid);
3781
3885
  if (done) {
3782
3886
  return done;
3783
3887
  }
3784
- return null;
3785
3888
  } else {
3786
- return null;
3889
+ const byQueueId = await db(historyTable).where({ opid: taskId }).orderBy("completed_at", "desc").first();
3890
+ if (byQueueId) {
3891
+ return byQueueId;
3892
+ }
3787
3893
  }
3788
3894
  await sleepMs(pollMs);
3789
3895
  }
@@ -3927,6 +4033,7 @@ export {
3927
4033
  LOOP_RUNTIME_KEYS,
3928
4034
  SERVICE_TASK_NAMES,
3929
4035
  TaskGetLogs,
4036
+ TaskPauseRunner,
3930
4037
  TaskPing,
3931
4038
  TaskSampleProcess,
3932
4039
  TaskSetRuntimeParam,
@@ -3934,9 +4041,11 @@ export {
3934
4041
  TaskStopRunner,
3935
4042
  TaskSumAB,
3936
4043
  TaskSystemInfo,
4044
+ TaskUnpauseRunner,
3937
4045
  TasksManager,
3938
4046
  TasksRegistry,
3939
4047
  appendTaskIpcLog,
4048
+ applyRunnerPaused,
3940
4049
  applyRuntimeParam,
3941
4050
  applyRuntimePatch,
3942
4051
  coerceRuntimeValue,