sliftutils 1.4.57 → 1.4.58

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/index.d.ts CHANGED
@@ -1231,6 +1231,7 @@ declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseMerge" {
1231
1231
  collectionName: string;
1232
1232
  targetFileBytes?: number;
1233
1233
  targetBatchBytes?: number;
1234
+ log?: (line: string) => void;
1234
1235
  writeFile: (data: Buffer) => Promise<{
1235
1236
  name: string;
1236
1237
  size: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.4.57",
3
+ "version": "1.4.58",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -13,7 +13,7 @@ import {
13
13
  import { runPlannedMerge } from "./BulkDatabaseMerge";
14
14
  import { blockCache, encodeCompressedBlocks } from "./blockCache";
15
15
  import { formatNumber, formatTime } from "socket-function/src/formatting/format";
16
- import { blue } from "socket-function/src/formatting/logColors";
16
+ import { blue, magenta } from "socket-function/src/formatting/logColors";
17
17
  import { STREAM_EXTENSION, frameDeletes, frameRows, streamReaderFromEntries } from "./streamLog";
18
18
  import { broadcast as syncBroadcast, broadcastSeal as syncBroadcastSeal, connect as syncConnect, isSyncSupported, RemoteWrite } from "./syncClient";
19
19
  import { DELETED } from "./WriteOverlay";
@@ -654,14 +654,18 @@ export class BulkDatabaseBase<T extends { key: string }> {
654
654
  ];
655
655
  const inTotal = inputs.reduce((a, f) => a + f.size, 0);
656
656
  const mergeStartMs = Date.now();
657
- console.log(`${blue(this.name)} merge: reading ${inputs.length} files (${fmtBytes(inTotal)}) at ${new Date(mergeStartMs).toISOString()}`);
658
- for (const f of inputs) console.log(` in ${f.name} ${fmtBytes(f.size)}`);
657
+ // Collect each step instead of logging it live; emitted at the end as one
658
+ // expanded console.group so a merge takes one collapsible block, not a
659
+ // screenful (and never the per-file name dump it used to spew).
660
+ const steps: string[] = [];
661
+ steps.push(`read ${inputs.length} input file(s), ${fmtBytes(inTotal)}`);
659
662
 
660
663
  const newNames: string[] = [];
661
664
  const mergeResult = await runPlannedMerge({
662
665
  sources: readers,
663
666
  sourceNames: readerNames,
664
667
  collectionName: this.name,
668
+ log: line => steps.push(line),
665
669
  writeFile: async (data) => {
666
670
  const fname = newFileName(timestamp);
667
671
  await storage.set(fname, encodeCompressedBlocks(data));
@@ -681,8 +685,11 @@ export class BulkDatabaseBase<T extends { key: string }> {
681
685
 
682
686
  const outputs = await Promise.all(outNames.map(async n => ({ name: n, size: (await storage.getInfo(n).catch(() => undefined))?.size ?? 0 })));
683
687
  const outTotal = outputs.reduce((a, f) => a + f.size, 0);
684
- console.log(`${blue(this.name)} merge: wrote ${outputs.length} files (${fmtBytes(outTotal)}, from ${fmtBytes(inTotal)})${carriedDeletes ? `, ${carriedDeletes} tombstones carried` : ""} at ${new Date().toISOString()} (took ${formatTime(Date.now() - mergeStartMs)})`);
685
- for (const f of outputs) console.log(` out ${f.name} ${fmtBytes(f.size)}`);
688
+ steps.push(`wrote ${outputs.length} output file(s), ${fmtBytes(outTotal)}${carriedDeletes ? `, ${carriedDeletes} tombstones carried` : ""}`);
689
+
690
+ console.group(`${blue(this.name)} ${magenta("merge")}: ${fmtBytes(inTotal)} → ${fmtBytes(outTotal)} (${inputs.length}→${outputs.length} files) in ${formatTime(Date.now() - mergeStartMs)}`);
691
+ for (const line of steps) console.log(line);
692
+ console.groupEnd();
686
693
 
687
694
  const remove = async (name: string) => { try { await storage.remove(name); } catch { /* already gone */ } };
688
695
  for (const f of consumedBulk) await remove(f.fileName);
@@ -746,7 +753,7 @@ export class BulkDatabaseBase<T extends { key: string }> {
746
753
  const sizes = await Promise.all(streamFiles.map(async f => { try { return (await storage.getInfo(f.fileName))?.size ?? 0; } catch { return 0; } }));
747
754
  const totalStreamBytes = sizes.reduce((a, b) => a + b, 0);
748
755
  if (totalStreamBytes > bulkDatabase2Timing.streamFoldHardLimitBytes) {
749
- console.log(`${blue(this.name)} stream tier ${fmtBytes(totalStreamBytes)} over hard limit ${fmtBytes(bulkDatabase2Timing.streamFoldHardLimitBytes)} — folding all streams now`);
756
+ console.log(`${blue(this.name)} ${magenta("fold")} stream tier ${fmtBytes(totalStreamBytes)} over hard limit ${fmtBytes(bulkDatabase2Timing.streamFoldHardLimitBytes)} — folding all streams now`);
750
757
  if (await this.mergeFileSet([], streamFiles, false, true)) merged = true;
751
758
  }
752
759
  }
@@ -38,6 +38,7 @@ export declare function runPlannedMerge(config: {
38
38
  collectionName: string;
39
39
  targetFileBytes?: number;
40
40
  targetBatchBytes?: number;
41
+ log?: (line: string) => void;
41
42
  writeFile: (data: Buffer) => Promise<{
42
43
  name: string;
43
44
  size: number;
@@ -88,10 +88,14 @@ export async function runPlannedMerge(config: {
88
88
  collectionName: string;
89
89
  targetFileBytes?: number;
90
90
  targetBatchBytes?: number;
91
+ // Sink for step lines. When omitted, falls back to a blue-prefixed
92
+ // console.log so standalone callers still get identifiable output.
93
+ log?: (line: string) => void;
91
94
  writeFile: (data: Buffer) => Promise<{ name: string; size: number }>;
92
95
  }): Promise<{ outputs: PlannedMergeOutput[]; carriedDeletes: Map<string, number> }> {
93
96
  const targetFileBytes = config.targetFileBytes ?? TARGET_FILE_BYTES;
94
97
  const targetBatchBytes = config.targetBatchBytes ?? DEFAULT_OUTPUT_BATCH_BYTES;
98
+ const log = config.log ?? (line => console.log(`${blue(config.collectionName)} ${line}`));
95
99
 
96
100
  // ─────────────────────────────────────────── Phase 1: plan ───────────────────────────────────────────
97
101
  const planStart = Date.now();
@@ -219,7 +223,7 @@ export async function runPlannedMerge(config: {
219
223
  const plans = fileKeyRanges.map(range => buildOutputPlan(range, liveKeys, cellsPerKey, allColumns, keyTime));
220
224
 
221
225
  const planTime = Date.now() - planStart;
222
- console.log(`${blue(config.collectionName)} planned merge: mapping done — ${formatNumber(liveKeys.length)} live keys, ${plans.length} output file(s), ${formatNumber(carriedDeletes.size)} tombstones carried, in ${red(formatTime(planTime))}`);
226
+ log(`mapping done — ${formatNumber(liveKeys.length)} live keys, ${plans.length} output file(s), ${formatNumber(carriedDeletes.size)} tombstones carried, in ${red(formatTime(planTime))}`);
223
227
 
224
228
  // ───────────────────────────────────────── Phase 2: execute ──────────────────────────────────────────
225
229
  // Group output files into batches that fit within targetBatchBytes so we read inputs once per batch
@@ -244,13 +248,13 @@ export async function runPlannedMerge(config: {
244
248
  for (let bi = 0; bi < batches.length; bi++) {
245
249
  const batch = batches[bi];
246
250
  const batchBytes = batch.reduce((a, p) => a + p.estimatedFileBytes, 0);
247
- console.log(`${blue(config.collectionName)} merge batch ${bi + 1}/${batches.length}: ${batch.length} output file(s), ~${fmtBytes(batchBytes)} budget`);
248
- const batchOutputs = await executeBatch(batch, indexesPerSource, config.sources, config.sourceNames, config.collectionName, config.writeFile);
251
+ log(`batch ${bi + 1}/${batches.length}: ${batch.length} output file(s), ~${fmtBytes(batchBytes)} budget`);
252
+ const batchOutputs = await executeBatch(batch, indexesPerSource, config.sources, config.sourceNames, config.writeFile, log);
249
253
  outputs.push(...batchOutputs);
250
254
  }
251
255
  const execTime = Date.now() - execStart;
252
256
  const writtenBytes = outputs.reduce((a, o) => a + o.size, 0);
253
- console.log(`${blue(config.collectionName)} planned merge: done — ${outputs.length} file(s), ${fmtBytes(writtenBytes)} written, in ${red(formatTime(execTime))} (planning + execute: ${red(formatTime(Date.now() - planStart))})`);
257
+ log(`execute done — ${outputs.length} file(s), ${fmtBytes(writtenBytes)} written, in ${red(formatTime(execTime))} (plan + execute: ${red(formatTime(Date.now() - planStart))})`);
254
258
 
255
259
  return { outputs, carriedDeletes };
256
260
  }
@@ -334,8 +338,8 @@ async function executeBatch(
334
338
  indexesPerSource: Map<string, ColumnIndex>[],
335
339
  sources: BaseBulkDatabaseReader[],
336
340
  sourceNames: string[],
337
- collectionName: string,
338
341
  writeFile: (data: Buffer) => Promise<{ name: string; size: number }>,
342
+ log: (line: string) => void,
339
343
  ): Promise<PlannedMergeOutput[]> {
340
344
  // Allocate one big buffer per (output file, column) — offsets + types are written immediately from
341
345
  // the plan, the data section is filled below by the per-source copy loop.
@@ -390,8 +394,7 @@ async function executeBatch(
390
394
  const elapsed = Date.now() - start;
391
395
  const sourcesNamed = new Map<string, number>();
392
396
  for (const [si, n] of plan.sourceCounts) sourcesNamed.set(sourceNames[si], n);
393
- const srcText = [...sourcesNamed.entries()].sort((a, b) => b[1] - a[1]).map(([s, n]) => `${s}:${formatNumber(n)}`).join(", ") || "—";
394
- console.log(` ${blue(collectionName)} output ${name}: ${formatNumber(plan.keys.length)} rows [${plan.minKey} .. ${plan.maxKey}] from {${srcText}}, ${fmtBytes(size)} in ${formatTime(elapsed)}`);
397
+ log(`output: ${formatNumber(plan.keys.length)} rows from ${plan.sourceCounts.size} input(s), ${fmtBytes(size)} in ${formatTime(elapsed)}`);
395
398
  outputs.push({ name, minKey: plan.minKey, maxKey: plan.maxKey, rowCount: plan.keys.length, size, sources: sourcesNamed });
396
399
  }
397
400
  return outputs;