@nmakarov/cli-toolkit 0.61.0 → 0.65.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.
@@ -2405,6 +2405,9 @@ var Logger = class _Logger {
2405
2405
  const message = this.inspectChunks([operation, ...chunks]);
2406
2406
  this.out({ level: "response", message });
2407
2407
  }
2408
+ /**
2409
+ * @returns {boolean} true when the progress line was emitted (not throttled)
2410
+ */
2408
2411
  progress(message, opts) {
2409
2412
  const { prefix, count, total } = opts;
2410
2413
  const paddedTotal = String(total).length;
@@ -2438,7 +2441,9 @@ var Logger = class _Logger {
2438
2441
  if (this.options.progressThrottle && prefix) {
2439
2442
  this.lastProgressTimes[prefix] = Date.now();
2440
2443
  }
2444
+ return true;
2441
2445
  }
2446
+ return false;
2442
2447
  }
2443
2448
  shouldOutputProgress(prefix, count, total) {
2444
2449
  if (!this.options.progressThrottle) {
@@ -2841,13 +2846,15 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
2841
2846
  "57P02",
2842
2847
  "57P03"
2843
2848
  ]);
2844
- var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET|Timeout acquiring a connection/i;
2849
+ var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET/i;
2845
2850
  var Db = class _Db {
2846
2851
  static async init(context, options = {}) {
2847
2852
  const buildConfig = async () => {
2848
2853
  const defs2 = {
2849
2854
  dbName: "string",
2850
- dbProfile: "boolean default false"
2855
+ dbProfile: "boolean default false",
2856
+ /** Cap knex pool size (default 10). Size ≈ expected concurrency + headroom. */
2857
+ dbPoolMax: "number"
2851
2858
  };
2852
2859
  const discovered = context?.params?.getAllForModule?.("db", defs2) ?? {};
2853
2860
  const merged = { ...discovered, ...options };
@@ -2906,12 +2913,23 @@ var Db = class _Db {
2906
2913
  context?.args?.env,
2907
2914
  merged.name
2908
2915
  );
2916
+ const poolMaxRaw = merged.dbPoolMax ?? merged.pool?.max;
2917
+ const poolMax = Number(poolMaxRaw);
2918
+ const pool = {
2919
+ ...KNEX_DEFAULTS.pool,
2920
+ ...merged.pool && typeof merged.pool === "object" ? merged.pool : {}
2921
+ };
2922
+ if (Number.isFinite(poolMax) && poolMax >= 1) {
2923
+ pool.max = Math.floor(poolMax);
2924
+ }
2909
2925
  return {
2910
2926
  ...KNEX_DEFAULTS,
2911
2927
  connectionString: dbConnectionString,
2912
2928
  name: displayName,
2913
2929
  profile: !!dbProfile,
2914
- logger: context.logger
2930
+ logger: context.logger,
2931
+ pool,
2932
+ ...merged.acquireConnectionTimeout != null ? { acquireConnectionTimeout: merged.acquireConnectionTimeout } : {}
2915
2933
  };
2916
2934
  };
2917
2935
  const config2 = context?.params?.runWithModuleAsync ? await context.params.runWithModuleAsync("db", buildConfig) : await buildConfig();
@@ -4055,6 +4073,34 @@ async function updateTaskProgress(context, tasksTable, taskId, progress) {
4055
4073
  progress: typeof progress === "string" ? progress : JSON.stringify(progress)
4056
4074
  });
4057
4075
  }
4076
+ function createTaskProgressReporter(context, tasksTable, taskId) {
4077
+ let pump = null;
4078
+ let pending = void 0;
4079
+ let hasPending = false;
4080
+ return (progress) => {
4081
+ pending = progress;
4082
+ hasPending = true;
4083
+ if (pump) return pump;
4084
+ pump = (async () => {
4085
+ try {
4086
+ while (hasPending) {
4087
+ hasPending = false;
4088
+ const value = pending;
4089
+ try {
4090
+ await updateTaskProgress(context, tasksTable, taskId, value);
4091
+ } catch (err) {
4092
+ context.logger?.warn?.(
4093
+ `[tasks] progress update failed for ${taskId}: ${err?.message ?? err}`
4094
+ );
4095
+ }
4096
+ }
4097
+ } finally {
4098
+ pump = null;
4099
+ }
4100
+ })();
4101
+ return pump;
4102
+ };
4103
+ }
4058
4104
 
4059
4105
  // src/tasks/servicesRegistry.js
4060
4106
  function getDb2(context) {
@@ -4448,8 +4494,13 @@ var FileDatabase = class _FileDatabase {
4448
4494
  this.currentVersionFolder = await ensurePath(this.getDestinationPath(), version);
4449
4495
  }
4450
4496
  /**
4451
- * Create a new version folder with comprehensive timestamp logic
4452
- * Only works in versioned mode
4497
+ * Create a new version folder named with the current UTC second.
4498
+ * Only works in versioned mode.
4499
+ *
4500
+ * Clash protection: if that name already exists (two writers in the same
4501
+ * second, or wall clock behind the latest version), advance +1s until free.
4502
+ * Do NOT always derive from max(existing)+1s — that made successive harvests
4503
+ * hours apart still land one second apart on disk.
4453
4504
  */
4454
4505
  async makeNewVersion() {
4455
4506
  if (!this.versioned) {
@@ -4457,19 +4508,22 @@ var FileDatabase = class _FileDatabase {
4457
4508
  }
4458
4509
  this.metadata = this.getDefaultMetadata();
4459
4510
  const existingVersions = await this.getVersions();
4460
- let versionName;
4511
+ const existingSet = new Set(existingVersions);
4512
+ let candidateMs = Date.now();
4461
4513
  if (existingVersions.length > 0) {
4462
- const maxTimestamp = existingVersions.reduce((max, version) => {
4463
- const versionDate = new Date(version.replace("Z", ""));
4464
- const maxDate2 = new Date(max.replace("Z", ""));
4465
- return versionDate > maxDate2 ? version : max;
4466
- });
4467
- const maxDate = new Date(maxTimestamp.replace("Z", ""));
4468
- const nextDate = new Date(maxDate.getTime() + 1e3);
4469
- versionName = nextDate.toISOString().split(".")[0] + "Z";
4470
- } else {
4471
- const now = /* @__PURE__ */ new Date();
4472
- versionName = now.toISOString().split(".")[0] + "Z";
4514
+ let maxMs = 0;
4515
+ for (const version of existingVersions) {
4516
+ const ms = new Date(version).getTime();
4517
+ if (Number.isFinite(ms) && ms > maxMs) maxMs = ms;
4518
+ }
4519
+ if (candidateMs <= maxMs) {
4520
+ candidateMs = maxMs + 1e3;
4521
+ }
4522
+ }
4523
+ let versionName = new Date(candidateMs).toISOString().split(".")[0] + "Z";
4524
+ while (existingSet.has(versionName)) {
4525
+ candidateMs += 1e3;
4526
+ versionName = new Date(candidateMs).toISOString().split(".")[0] + "Z";
4473
4527
  }
4474
4528
  await this.setCurrentVersion(versionName);
4475
4529
  this.currentFileNumber = 0;
@@ -6723,7 +6777,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
6723
6777
  try {
6724
6778
  taskInstance = new TaskClass(context, row);
6725
6779
  runningTaskInstances.set(row.id, taskInstance);
6726
- const runResult = await taskInstance.run((progress) => updateTaskProgress(context, tasksTable, row.id, progress));
6780
+ const reportProgress = createTaskProgressReporter(context, tasksTable, row.id);
6781
+ const runResult = await taskInstance.run(reportProgress);
6727
6782
  success = !!runResult?.success;
6728
6783
  results = runResult?.results ?? null;
6729
6784
  } catch (error) {