rowpipe 2.0.0 → 2.2.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.
Files changed (314) hide show
  1. package/README.md +420 -59
  2. package/dist/analytics/profiler.d.ts +56 -0
  3. package/dist/analytics/profiler.d.ts.map +1 -0
  4. package/dist/analytics/profiler.js +277 -0
  5. package/dist/analytics/profiler.js.map +1 -0
  6. package/dist/analytics/schema-inference.d.ts.map +1 -1
  7. package/dist/analytics/schema-inference.js +14 -0
  8. package/dist/analytics/schema-inference.js.map +1 -1
  9. package/dist/cli/commands/clean.d.ts +15 -0
  10. package/dist/cli/commands/clean.d.ts.map +1 -0
  11. package/dist/cli/commands/clean.js +108 -0
  12. package/dist/cli/commands/clean.js.map +1 -0
  13. package/dist/cli/commands/convert.d.ts +6 -0
  14. package/dist/cli/commands/convert.d.ts.map +1 -1
  15. package/dist/cli/commands/convert.js +6 -6
  16. package/dist/cli/commands/convert.js.map +1 -1
  17. package/dist/cli/commands/count.d.ts +12 -0
  18. package/dist/cli/commands/count.d.ts.map +1 -0
  19. package/dist/cli/commands/count.js +49 -0
  20. package/dist/cli/commands/count.js.map +1 -0
  21. package/dist/cli/commands/db.d.ts +35 -0
  22. package/dist/cli/commands/db.d.ts.map +1 -0
  23. package/dist/cli/commands/db.js +173 -0
  24. package/dist/cli/commands/db.js.map +1 -0
  25. package/dist/cli/commands/explain.d.ts +10 -0
  26. package/dist/cli/commands/explain.d.ts.map +1 -0
  27. package/dist/cli/commands/explain.js +98 -0
  28. package/dist/cli/commands/explain.js.map +1 -0
  29. package/dist/cli/commands/files.d.ts +2 -1
  30. package/dist/cli/commands/files.d.ts.map +1 -1
  31. package/dist/cli/commands/files.js +5 -2
  32. package/dist/cli/commands/files.js.map +1 -1
  33. package/dist/cli/commands/group.d.ts +12 -0
  34. package/dist/cli/commands/group.d.ts.map +1 -0
  35. package/dist/cli/commands/group.js +51 -0
  36. package/dist/cli/commands/group.js.map +1 -0
  37. package/dist/cli/commands/head.d.ts +7 -0
  38. package/dist/cli/commands/head.d.ts.map +1 -0
  39. package/dist/cli/commands/head.js +6 -0
  40. package/dist/cli/commands/head.js.map +1 -0
  41. package/dist/cli/commands/join.d.ts +23 -0
  42. package/dist/cli/commands/join.d.ts.map +1 -0
  43. package/dist/cli/commands/join.js +97 -0
  44. package/dist/cli/commands/join.js.map +1 -0
  45. package/dist/cli/commands/limit.d.ts +11 -0
  46. package/dist/cli/commands/limit.d.ts.map +1 -0
  47. package/dist/cli/commands/limit.js +40 -0
  48. package/dist/cli/commands/limit.js.map +1 -0
  49. package/dist/cli/commands/offset.d.ts +11 -0
  50. package/dist/cli/commands/offset.d.ts.map +1 -0
  51. package/dist/cli/commands/offset.js +39 -0
  52. package/dist/cli/commands/offset.js.map +1 -0
  53. package/dist/cli/commands/pipeline.d.ts +63 -0
  54. package/dist/cli/commands/pipeline.d.ts.map +1 -0
  55. package/dist/cli/commands/pipeline.js +239 -0
  56. package/dist/cli/commands/pipeline.js.map +1 -0
  57. package/dist/cli/commands/profile.d.ts +22 -0
  58. package/dist/cli/commands/profile.d.ts.map +1 -0
  59. package/dist/cli/commands/profile.js +53 -0
  60. package/dist/cli/commands/profile.js.map +1 -0
  61. package/dist/cli/commands/sort.d.ts +17 -0
  62. package/dist/cli/commands/sort.d.ts.map +1 -0
  63. package/dist/cli/commands/sort.js +55 -0
  64. package/dist/cli/commands/sort.js.map +1 -0
  65. package/dist/cli/commands/tail.d.ts +13 -0
  66. package/dist/cli/commands/tail.d.ts.map +1 -0
  67. package/dist/cli/commands/tail.js +39 -0
  68. package/dist/cli/commands/tail.js.map +1 -0
  69. package/dist/cli/commands/top.d.ts +19 -0
  70. package/dist/cli/commands/top.d.ts.map +1 -0
  71. package/dist/cli/commands/top.js +51 -0
  72. package/dist/cli/commands/top.js.map +1 -0
  73. package/dist/cli/commands/unique.d.ts +15 -0
  74. package/dist/cli/commands/unique.d.ts.map +1 -0
  75. package/dist/cli/commands/unique.js +44 -0
  76. package/dist/cli/commands/unique.js.map +1 -0
  77. package/dist/cli/commands/view.d.ts +12 -0
  78. package/dist/cli/commands/view.d.ts.map +1 -0
  79. package/dist/cli/commands/view.js +52 -0
  80. package/dist/cli/commands/view.js.map +1 -0
  81. package/dist/cli/commands/window.d.ts +10 -0
  82. package/dist/cli/commands/window.d.ts.map +1 -0
  83. package/dist/cli/commands/window.js +87 -0
  84. package/dist/cli/commands/window.js.map +1 -0
  85. package/dist/cli/index.js +448 -3
  86. package/dist/cli/index.js.map +1 -1
  87. package/dist/core/batch.d.ts +1 -1
  88. package/dist/core/batch.d.ts.map +1 -1
  89. package/dist/core/batch.js.map +1 -1
  90. package/dist/core/pipeline.d.ts +42 -2
  91. package/dist/core/pipeline.d.ts.map +1 -1
  92. package/dist/core/pipeline.js +70 -2
  93. package/dist/core/pipeline.js.map +1 -1
  94. package/dist/core/types.d.ts +21 -1
  95. package/dist/core/types.d.ts.map +1 -1
  96. package/dist/db/adapters/index.d.ts +6 -0
  97. package/dist/db/adapters/index.d.ts.map +1 -0
  98. package/dist/db/adapters/index.js +21 -0
  99. package/dist/db/adapters/index.js.map +1 -0
  100. package/dist/db/adapters/mysql.d.ts +16 -0
  101. package/dist/db/adapters/mysql.d.ts.map +1 -0
  102. package/dist/db/adapters/mysql.js +230 -0
  103. package/dist/db/adapters/mysql.js.map +1 -0
  104. package/dist/db/adapters/postgres.d.ts +17 -0
  105. package/dist/db/adapters/postgres.d.ts.map +1 -0
  106. package/dist/db/adapters/postgres.js +239 -0
  107. package/dist/db/adapters/postgres.js.map +1 -0
  108. package/dist/db/adapters/sqlite.d.ts +18 -0
  109. package/dist/db/adapters/sqlite.d.ts.map +1 -0
  110. package/dist/db/adapters/sqlite.js +210 -0
  111. package/dist/db/adapters/sqlite.js.map +1 -0
  112. package/dist/db/index.d.ts +11 -0
  113. package/dist/db/index.d.ts.map +1 -0
  114. package/dist/db/index.js +11 -0
  115. package/dist/db/index.js.map +1 -0
  116. package/dist/db/mapping.d.ts +45 -0
  117. package/dist/db/mapping.d.ts.map +1 -0
  118. package/dist/db/mapping.js +222 -0
  119. package/dist/db/mapping.js.map +1 -0
  120. package/dist/db/pushdown.d.ts +25 -0
  121. package/dist/db/pushdown.d.ts.map +1 -0
  122. package/dist/db/pushdown.js +165 -0
  123. package/dist/db/pushdown.js.map +1 -0
  124. package/dist/db/sink.d.ts +15 -0
  125. package/dist/db/sink.d.ts.map +1 -0
  126. package/dist/db/sink.js +47 -0
  127. package/dist/db/sink.js.map +1 -0
  128. package/dist/db/source.d.ts +14 -0
  129. package/dist/db/source.d.ts.map +1 -0
  130. package/dist/db/source.js +69 -0
  131. package/dist/db/source.js.map +1 -0
  132. package/dist/db/types.d.ts +80 -0
  133. package/dist/db/types.d.ts.map +1 -0
  134. package/dist/db/types.js +2 -0
  135. package/dist/db/types.js.map +1 -0
  136. package/dist/db/url.d.ts +15 -0
  137. package/dist/db/url.d.ts.map +1 -0
  138. package/dist/db/url.js +145 -0
  139. package/dist/db/url.js.map +1 -0
  140. package/dist/diff/hash.d.ts.map +1 -1
  141. package/dist/diff/hash.js +4 -3
  142. package/dist/diff/hash.js.map +1 -1
  143. package/dist/diff/key.d.ts.map +1 -1
  144. package/dist/diff/key.js +8 -1
  145. package/dist/diff/key.js.map +1 -1
  146. package/dist/diff/reporter.d.ts.map +1 -1
  147. package/dist/diff/reporter.js +5 -3
  148. package/dist/diff/reporter.js.map +1 -1
  149. package/dist/diff/storage/disk-index.d.ts.map +1 -1
  150. package/dist/diff/storage/disk-index.js +2 -1
  151. package/dist/diff/storage/disk-index.js.map +1 -1
  152. package/dist/diff/storage/memory-index.d.ts.map +1 -1
  153. package/dist/diff/storage/memory-index.js +3 -2
  154. package/dist/diff/storage/memory-index.js.map +1 -1
  155. package/dist/index.d.ts +20 -1
  156. package/dist/index.d.ts.map +1 -1
  157. package/dist/index.js +22 -1
  158. package/dist/index.js.map +1 -1
  159. package/dist/planner/executor.d.ts +8 -0
  160. package/dist/planner/executor.d.ts.map +1 -0
  161. package/dist/planner/executor.js +93 -0
  162. package/dist/planner/executor.js.map +1 -0
  163. package/dist/planner/explain.d.ts +6 -0
  164. package/dist/planner/explain.d.ts.map +1 -0
  165. package/dist/planner/explain.js +91 -0
  166. package/dist/planner/explain.js.map +1 -0
  167. package/dist/planner/index.d.ts +5 -0
  168. package/dist/planner/index.d.ts.map +1 -0
  169. package/dist/planner/index.js +5 -0
  170. package/dist/planner/index.js.map +1 -0
  171. package/dist/planner/optimizer.d.ts +9 -0
  172. package/dist/planner/optimizer.d.ts.map +1 -0
  173. package/dist/planner/optimizer.js +78 -0
  174. package/dist/planner/optimizer.js.map +1 -0
  175. package/dist/planner/types.d.ts +67 -0
  176. package/dist/planner/types.d.ts.map +1 -0
  177. package/dist/planner/types.js +2 -0
  178. package/dist/planner/types.js.map +1 -0
  179. package/dist/readers/csv.d.ts.map +1 -1
  180. package/dist/readers/csv.js +1 -2
  181. package/dist/readers/csv.js.map +1 -1
  182. package/dist/readers/index.d.ts +5 -1
  183. package/dist/readers/index.d.ts.map +1 -1
  184. package/dist/readers/index.js +43 -0
  185. package/dist/readers/index.js.map +1 -1
  186. package/dist/readers/json.d.ts.map +1 -1
  187. package/dist/readers/json.js +1 -2
  188. package/dist/readers/json.js.map +1 -1
  189. package/dist/readers/jsonl.d.ts.map +1 -1
  190. package/dist/readers/jsonl.js +1 -2
  191. package/dist/readers/jsonl.js.map +1 -1
  192. package/dist/readers/multi-file.d.ts +29 -0
  193. package/dist/readers/multi-file.d.ts.map +1 -0
  194. package/dist/readers/multi-file.js +169 -0
  195. package/dist/readers/multi-file.js.map +1 -0
  196. package/dist/transforms/clean.d.ts +18 -0
  197. package/dist/transforms/clean.d.ts.map +1 -0
  198. package/dist/transforms/clean.js +118 -0
  199. package/dist/transforms/clean.js.map +1 -0
  200. package/dist/transforms/count.d.ts +21 -0
  201. package/dist/transforms/count.d.ts.map +1 -0
  202. package/dist/transforms/count.js +65 -0
  203. package/dist/transforms/count.js.map +1 -0
  204. package/dist/transforms/expression.d.ts.map +1 -1
  205. package/dist/transforms/expression.js +12 -0
  206. package/dist/transforms/expression.js.map +1 -1
  207. package/dist/transforms/group.d.ts +25 -0
  208. package/dist/transforms/group.d.ts.map +1 -0
  209. package/dist/transforms/group.js +71 -0
  210. package/dist/transforms/group.js.map +1 -0
  211. package/dist/transforms/jit-compiler.d.ts +50 -0
  212. package/dist/transforms/jit-compiler.d.ts.map +1 -0
  213. package/dist/transforms/jit-compiler.js +275 -0
  214. package/dist/transforms/jit-compiler.js.map +1 -0
  215. package/dist/transforms/join/index-storage.d.ts +33 -0
  216. package/dist/transforms/join/index-storage.d.ts.map +1 -0
  217. package/dist/transforms/join/index-storage.js +139 -0
  218. package/dist/transforms/join/index-storage.js.map +1 -0
  219. package/dist/transforms/join/index.d.ts +4 -0
  220. package/dist/transforms/join/index.d.ts.map +1 -0
  221. package/dist/transforms/join/index.js +4 -0
  222. package/dist/transforms/join/index.js.map +1 -0
  223. package/dist/transforms/join/join.d.ts +15 -0
  224. package/dist/transforms/join/join.d.ts.map +1 -0
  225. package/dist/transforms/join/join.js +216 -0
  226. package/dist/transforms/join/join.js.map +1 -0
  227. package/dist/transforms/join/types.d.ts +29 -0
  228. package/dist/transforms/join/types.d.ts.map +1 -0
  229. package/dist/transforms/join/types.js +2 -0
  230. package/dist/transforms/join/types.js.map +1 -0
  231. package/dist/transforms/limit.d.ts +7 -0
  232. package/dist/transforms/limit.d.ts.map +1 -0
  233. package/dist/transforms/limit.js +40 -0
  234. package/dist/transforms/limit.js.map +1 -0
  235. package/dist/transforms/offset.d.ts +7 -0
  236. package/dist/transforms/offset.d.ts.map +1 -0
  237. package/dist/transforms/offset.js +38 -0
  238. package/dist/transforms/offset.js.map +1 -0
  239. package/dist/transforms/sort/comparator.d.ts +43 -0
  240. package/dist/transforms/sort/comparator.d.ts.map +1 -0
  241. package/dist/transforms/sort/comparator.js +131 -0
  242. package/dist/transforms/sort/comparator.js.map +1 -0
  243. package/dist/transforms/sort/external-sort.d.ts +8 -0
  244. package/dist/transforms/sort/external-sort.d.ts.map +1 -0
  245. package/dist/transforms/sort/external-sort.js +159 -0
  246. package/dist/transforms/sort/external-sort.js.map +1 -0
  247. package/dist/transforms/sort/index.d.ts +10 -0
  248. package/dist/transforms/sort/index.d.ts.map +1 -0
  249. package/dist/transforms/sort/index.js +11 -0
  250. package/dist/transforms/sort/index.js.map +1 -0
  251. package/dist/transforms/tail.d.ts +6 -0
  252. package/dist/transforms/tail.d.ts.map +1 -0
  253. package/dist/transforms/tail.js +27 -0
  254. package/dist/transforms/tail.js.map +1 -0
  255. package/dist/transforms/top.d.ts +17 -0
  256. package/dist/transforms/top.d.ts.map +1 -0
  257. package/dist/transforms/top.js +52 -0
  258. package/dist/transforms/top.js.map +1 -0
  259. package/dist/transforms/unique.d.ts +13 -0
  260. package/dist/transforms/unique.d.ts.map +1 -0
  261. package/dist/transforms/unique.js +91 -0
  262. package/dist/transforms/unique.js.map +1 -0
  263. package/dist/transforms/window.d.ts +24 -0
  264. package/dist/transforms/window.d.ts.map +1 -0
  265. package/dist/transforms/window.js +317 -0
  266. package/dist/transforms/window.js.map +1 -0
  267. package/dist/ui/colors.d.ts +57 -0
  268. package/dist/ui/colors.d.ts.map +1 -0
  269. package/dist/ui/colors.js +94 -0
  270. package/dist/ui/colors.js.map +1 -0
  271. package/dist/ui/viewer.d.ts +67 -0
  272. package/dist/ui/viewer.d.ts.map +1 -0
  273. package/dist/ui/viewer.js +673 -0
  274. package/dist/ui/viewer.js.map +1 -0
  275. package/dist/utils/compression.d.ts +68 -19
  276. package/dist/utils/compression.d.ts.map +1 -1
  277. package/dist/utils/compression.js +222 -34
  278. package/dist/utils/compression.js.map +1 -1
  279. package/dist/utils/formatting.d.ts +5 -0
  280. package/dist/utils/formatting.d.ts.map +1 -1
  281. package/dist/utils/formatting.js +15 -0
  282. package/dist/utils/formatting.js.map +1 -1
  283. package/dist/utils/heap.d.ts +25 -0
  284. package/dist/utils/heap.d.ts.map +1 -0
  285. package/dist/utils/heap.js +90 -0
  286. package/dist/utils/heap.js.map +1 -0
  287. package/dist/utils/keystore.d.ts +80 -0
  288. package/dist/utils/keystore.d.ts.map +1 -0
  289. package/dist/utils/keystore.js +217 -0
  290. package/dist/utils/keystore.js.map +1 -0
  291. package/dist/utils/ring-buffer.d.ts +15 -0
  292. package/dist/utils/ring-buffer.d.ts.map +1 -0
  293. package/dist/utils/ring-buffer.js +40 -0
  294. package/dist/utils/ring-buffer.js.map +1 -0
  295. package/dist/writers/csv.d.ts.map +1 -1
  296. package/dist/writers/csv.js +13 -14
  297. package/dist/writers/csv.js.map +1 -1
  298. package/dist/writers/index.d.ts +1 -0
  299. package/dist/writers/index.d.ts.map +1 -1
  300. package/dist/writers/index.js +35 -0
  301. package/dist/writers/index.js.map +1 -1
  302. package/dist/writers/json.d.ts.map +1 -1
  303. package/dist/writers/json.js +6 -5
  304. package/dist/writers/json.js.map +1 -1
  305. package/dist/writers/jsonl.d.ts.map +1 -1
  306. package/dist/writers/jsonl.js +6 -5
  307. package/dist/writers/jsonl.js.map +1 -1
  308. package/dist/writers/markdown.d.ts.map +1 -1
  309. package/dist/writers/markdown.js +5 -4
  310. package/dist/writers/markdown.js.map +1 -1
  311. package/dist/writers/parquet.d.ts +1 -1
  312. package/dist/writers/parquet.d.ts.map +1 -1
  313. package/package.json +11 -2
  314. package/skills/rowpipe/SKILL.md +16 -3
@@ -0,0 +1,673 @@
1
+ import { ANSI, bgBlue, bgGray, bold, cyan, dim, green, inverse, magenta, stripAnsi, truncateVisible, yellow, } from "./colors.js";
2
+ import { formatNumber } from "../utils/formatting.js";
3
+ /**
4
+ * Interactive Terminal Data Viewer for streaming tabular datasets.
5
+ * Features bounded memory sliding windows, keyboard navigation, column browsing,
6
+ * live in-memory searching, sorting, and seamless terminal restoration.
7
+ */
8
+ export class TerminalViewer {
9
+ streamIterator;
10
+ isStreamExhausted = false;
11
+ loadedRows = [];
12
+ columns = [];
13
+ options;
14
+ // Navigation state
15
+ cursorRow = 0;
16
+ scrollRow = 0;
17
+ cursorCol = 0;
18
+ scrollCol = 0;
19
+ // Interactive modes
20
+ mode = "normal";
21
+ searchQuery = "";
22
+ searchInput = "";
23
+ jumpInput = "";
24
+ matchingRowIndices = null;
25
+ activeMatchIndex = -1;
26
+ // Sorting
27
+ sortColumn = null;
28
+ sortAsc = true;
29
+ sortedRowIndices = null;
30
+ // Display configuration
31
+ autoFitCols = true;
32
+ termRows = 24;
33
+ termCols = 80;
34
+ isRunning = false;
35
+ stdinRawSet = false;
36
+ constructor(source, options = {}) {
37
+ this.options = {
38
+ maxBufferRows: 25000,
39
+ initialRows: 500,
40
+ ...options,
41
+ };
42
+ const stream = "read" in source && typeof source.read === "function"
43
+ ? source.read()
44
+ : source;
45
+ this.streamIterator = stream[Symbol.asyncIterator]();
46
+ }
47
+ /**
48
+ * Loads at least `targetCount` rows into memory buffer from stream.
49
+ */
50
+ async loadRows(targetCount) {
51
+ while (!this.isStreamExhausted && this.loadedRows.length < targetCount) {
52
+ const result = await this.streamIterator.next();
53
+ if (result.done) {
54
+ this.isStreamExhausted = true;
55
+ break;
56
+ }
57
+ const batch = result.value;
58
+ if (batch && Array.isArray(batch.rows)) {
59
+ for (const row of batch.rows) {
60
+ this.loadedRows.push(row);
61
+ // Discover new column names dynamically
62
+ for (const key of Object.keys(row)) {
63
+ if (!this.columns.includes(key)) {
64
+ this.columns.push(key);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Starts the interactive terminal viewer loop.
73
+ */
74
+ async run() {
75
+ // Initial data load
76
+ await this.loadRows(this.options.initialRows || 200);
77
+ // Non-interactive fallback (e.g. CI / piped output)
78
+ if (!process.stdin.isTTY || this.options.interactive === false) {
79
+ this.renderNonInteractive();
80
+ return;
81
+ }
82
+ this.isRunning = true;
83
+ this.updateDimensions();
84
+ // Setup terminal
85
+ process.stdout.write(ANSI.enterAltScreen);
86
+ process.stdout.write(ANSI.hideCursor);
87
+ if (process.stdin.isTTY) {
88
+ process.stdin.setRawMode(true);
89
+ this.stdinRawSet = true;
90
+ process.stdin.resume();
91
+ process.stdin.setEncoding("utf8");
92
+ }
93
+ const onResize = () => {
94
+ this.updateDimensions();
95
+ this.render();
96
+ };
97
+ process.stdout.on("resize", onResize);
98
+ const onData = async (data) => {
99
+ if (!this.isRunning)
100
+ return;
101
+ await this.handleInput(data);
102
+ this.render();
103
+ };
104
+ process.stdin.on("data", onData);
105
+ // Initial render
106
+ this.render();
107
+ // Keep running until exit requested
108
+ return new Promise((resolve) => {
109
+ const cleanup = () => {
110
+ this.isRunning = false;
111
+ process.stdout.off("resize", onResize);
112
+ process.stdin.off("data", onData);
113
+ if (this.stdinRawSet && process.stdin.isTTY) {
114
+ process.stdin.setRawMode(false);
115
+ process.stdin.pause();
116
+ }
117
+ process.stdout.write(ANSI.showCursor);
118
+ process.stdout.write(ANSI.exitAltScreen);
119
+ resolve();
120
+ };
121
+ this.exitCleanup = cleanup;
122
+ });
123
+ }
124
+ exitCleanup;
125
+ exit() {
126
+ if (this.exitCleanup) {
127
+ this.exitCleanup();
128
+ }
129
+ }
130
+ updateDimensions() {
131
+ this.termRows = process.stdout.rows || 24;
132
+ this.termCols = process.stdout.columns || 80;
133
+ }
134
+ getEffectiveRowIndices() {
135
+ if (this.matchingRowIndices !== null) {
136
+ return this.matchingRowIndices;
137
+ }
138
+ if (this.sortedRowIndices !== null) {
139
+ return this.sortedRowIndices;
140
+ }
141
+ const indices = [];
142
+ for (let i = 0; i < this.loadedRows.length; i++) {
143
+ indices.push(i);
144
+ }
145
+ return indices;
146
+ }
147
+ applySearch(query) {
148
+ this.searchQuery = query.trim();
149
+ if (!this.searchQuery) {
150
+ this.matchingRowIndices = null;
151
+ this.activeMatchIndex = -1;
152
+ return;
153
+ }
154
+ const lowerQuery = this.searchQuery.toLowerCase();
155
+ const matches = [];
156
+ const sourceIndices = this.sortedRowIndices || Array.from({ length: this.loadedRows.length }, (_, i) => i);
157
+ for (const idx of sourceIndices) {
158
+ const row = this.loadedRows[idx];
159
+ if (!row)
160
+ continue;
161
+ const rowText = Object.values(row).map(String).join(" ").toLowerCase();
162
+ if (rowText.includes(lowerQuery)) {
163
+ matches.push(idx);
164
+ }
165
+ }
166
+ this.matchingRowIndices = matches;
167
+ this.activeMatchIndex = matches.length > 0 ? 0 : -1;
168
+ this.cursorRow = 0;
169
+ this.scrollRow = 0;
170
+ }
171
+ applySort(column) {
172
+ if (this.sortColumn === column) {
173
+ if (this.sortAsc) {
174
+ this.sortAsc = false;
175
+ }
176
+ else {
177
+ // Clear sort
178
+ this.sortColumn = null;
179
+ this.sortedRowIndices = null;
180
+ if (this.searchQuery) {
181
+ this.applySearch(this.searchQuery);
182
+ }
183
+ return;
184
+ }
185
+ }
186
+ else {
187
+ this.sortColumn = column;
188
+ this.sortAsc = true;
189
+ }
190
+ const indices = Array.from({ length: this.loadedRows.length }, (_, i) => i);
191
+ const asc = this.sortAsc;
192
+ indices.sort((a, b) => {
193
+ const valA = this.loadedRows[a]?.[column];
194
+ const valB = this.loadedRows[b]?.[column];
195
+ if (valA === valB)
196
+ return 0;
197
+ if (valA === null || valA === undefined)
198
+ return 1;
199
+ if (valB === null || valB === undefined)
200
+ return -1;
201
+ if (typeof valA === "number" && typeof valB === "number") {
202
+ return asc ? valA - valB : valB - valA;
203
+ }
204
+ const strA = String(valA);
205
+ const strB = String(valB);
206
+ return asc ? strA.localeCompare(strB) : strB.localeCompare(strA);
207
+ });
208
+ this.sortedRowIndices = indices;
209
+ if (this.searchQuery) {
210
+ this.applySearch(this.searchQuery);
211
+ }
212
+ }
213
+ async handleInput(key) {
214
+ // -------------------------------------------------------------
215
+ // 1. Search Mode Input
216
+ // -------------------------------------------------------------
217
+ if (this.mode === "search") {
218
+ if (key === "\r" || key === "\n") {
219
+ // Submit search
220
+ this.applySearch(this.searchInput);
221
+ this.mode = "normal";
222
+ }
223
+ else if (key === "\x1b" || key === "\x03") {
224
+ // Escape / Cancel
225
+ this.mode = "normal";
226
+ this.searchInput = this.searchQuery;
227
+ }
228
+ else if (key === "\x7f" || key === "\b" || key === "\x08") {
229
+ // Backspace
230
+ this.searchInput = this.searchInput.slice(0, -1);
231
+ }
232
+ else if (key.length === 1 && key >= " ") {
233
+ this.searchInput += key;
234
+ }
235
+ return;
236
+ }
237
+ // -------------------------------------------------------------
238
+ // 2. Jump to Row Mode Input
239
+ // -------------------------------------------------------------
240
+ if (this.mode === "jump") {
241
+ if (key === "\r" || key === "\n") {
242
+ const targetRow = Number.parseInt(this.jumpInput, 10);
243
+ if (!Number.isNaN(targetRow) && targetRow >= 1) {
244
+ const zeroIdx = targetRow - 1;
245
+ await this.loadRows(targetRow + 50);
246
+ const effective = this.getEffectiveRowIndices();
247
+ this.cursorRow = Math.min(zeroIdx, Math.max(0, effective.length - 1));
248
+ this.scrollRow = Math.max(0, this.cursorRow - Math.floor(this.getVisibleDataRowCount() / 2));
249
+ }
250
+ this.mode = "normal";
251
+ this.jumpInput = "";
252
+ }
253
+ else if (key === "\x1b" || key === "\x03") {
254
+ this.mode = "normal";
255
+ this.jumpInput = "";
256
+ }
257
+ else if (key === "\x7f" || key === "\b") {
258
+ this.jumpInput = this.jumpInput.slice(0, -1);
259
+ }
260
+ else if (/^\d$/.test(key)) {
261
+ this.jumpInput += key;
262
+ }
263
+ return;
264
+ }
265
+ // -------------------------------------------------------------
266
+ // 3. Help Mode
267
+ // -------------------------------------------------------------
268
+ if (this.mode === "help") {
269
+ if (key === "q" || key === "\x1b" || key === "?" || key === "\r" || key === " ") {
270
+ this.mode = "normal";
271
+ }
272
+ return;
273
+ }
274
+ // -------------------------------------------------------------
275
+ // 4. Normal Navigation Mode
276
+ // -------------------------------------------------------------
277
+ const effectiveIndices = this.getEffectiveRowIndices();
278
+ const totalEffectiveRows = effectiveIndices.length;
279
+ const pageRows = this.getVisibleDataRowCount();
280
+ // Quit
281
+ if (key === "q" || key === "Q" || key === "\x03") {
282
+ this.exit();
283
+ return;
284
+ }
285
+ // Help
286
+ if (key === "?") {
287
+ this.mode = "help";
288
+ return;
289
+ }
290
+ // Enter Search
291
+ if (key === "/") {
292
+ this.mode = "search";
293
+ this.searchInput = "";
294
+ return;
295
+ }
296
+ // Jump to row
297
+ if (key === ":" || key === "J") {
298
+ this.mode = "jump";
299
+ this.jumpInput = "";
300
+ return;
301
+ }
302
+ // Clear search
303
+ if (key === "\x1b") {
304
+ if (this.searchQuery) {
305
+ this.searchQuery = "";
306
+ this.matchingRowIndices = null;
307
+ }
308
+ return;
309
+ }
310
+ // Next match (n) / Prev match (N)
311
+ if (key === "n" && this.matchingRowIndices && this.matchingRowIndices.length > 0) {
312
+ this.cursorRow = (this.cursorRow + 1) % this.matchingRowIndices.length;
313
+ this.adjustScroll();
314
+ return;
315
+ }
316
+ if (key === "N" && this.matchingRowIndices && this.matchingRowIndices.length > 0) {
317
+ this.cursorRow = (this.cursorRow - 1 + this.matchingRowIndices.length) % this.matchingRowIndices.length;
318
+ this.adjustScroll();
319
+ return;
320
+ }
321
+ // Sort by selected column (s)
322
+ if (key === "s" || key === "S") {
323
+ const activeCol = this.columns[this.cursorCol];
324
+ if (activeCol) {
325
+ this.applySort(activeCol);
326
+ }
327
+ return;
328
+ }
329
+ // Toggle column auto-fit
330
+ if (key === "c" || key === "C") {
331
+ this.autoFitCols = !this.autoFitCols;
332
+ return;
333
+ }
334
+ // Up / k / w
335
+ if (key === "\x1b[A" || key === "k" || key === "w") {
336
+ if (this.cursorRow > 0) {
337
+ this.cursorRow--;
338
+ this.adjustScroll();
339
+ }
340
+ return;
341
+ }
342
+ // Down / j
343
+ if (key === "\x1b[B" || key === "j") {
344
+ if (this.cursorRow < totalEffectiveRows - 1) {
345
+ this.cursorRow++;
346
+ this.adjustScroll();
347
+ }
348
+ else if (!this.isStreamExhausted) {
349
+ await this.loadRows(this.loadedRows.length + 200);
350
+ const updated = this.getEffectiveRowIndices();
351
+ if (this.cursorRow < updated.length - 1) {
352
+ this.cursorRow++;
353
+ this.adjustScroll();
354
+ }
355
+ }
356
+ return;
357
+ }
358
+ // Page Down / Space / f / Ctrl+F
359
+ if (key === "\x1b[6~" || key === " " || key === "f" || key === "\x06") {
360
+ const nextRow = Math.min(totalEffectiveRows - 1, this.cursorRow + pageRows);
361
+ if (nextRow >= totalEffectiveRows - 5 && !this.isStreamExhausted) {
362
+ await this.loadRows(this.loadedRows.length + pageRows * 2);
363
+ }
364
+ this.cursorRow = Math.min(this.getEffectiveRowIndices().length - 1, this.cursorRow + pageRows);
365
+ this.adjustScroll();
366
+ return;
367
+ }
368
+ // Page Up / b / Ctrl+B
369
+ if (key === "\x1b[5~" || key === "b" || key === "\x02") {
370
+ this.cursorRow = Math.max(0, this.cursorRow - pageRows);
371
+ this.adjustScroll();
372
+ return;
373
+ }
374
+ // Top / g / Home
375
+ if (key === "\x1b[H" || key === "\x1b[1~" || key === "g") {
376
+ this.cursorRow = 0;
377
+ this.scrollRow = 0;
378
+ return;
379
+ }
380
+ // End / G
381
+ if (key === "\x1b[F" || key === "\x1b[4~" || key === "G") {
382
+ // Load all or up to maxBufferRows
383
+ while (!this.isStreamExhausted && this.loadedRows.length < (this.options.maxBufferRows || 25000)) {
384
+ await this.loadRows(this.loadedRows.length + 1000);
385
+ }
386
+ const updated = this.getEffectiveRowIndices();
387
+ this.cursorRow = Math.max(0, updated.length - 1);
388
+ this.adjustScroll();
389
+ return;
390
+ }
391
+ // Left / h / a
392
+ if (key === "\x1b[D" || key === "h" || key === "a" || key === "\x1b[Z") {
393
+ if (this.cursorCol > 0) {
394
+ this.cursorCol--;
395
+ if (this.cursorCol < this.scrollCol) {
396
+ this.scrollCol = this.cursorCol;
397
+ }
398
+ }
399
+ return;
400
+ }
401
+ // Right / l / d / Tab
402
+ if (key === "\x1b[C" || key === "l" || key === "d" || key === "\t") {
403
+ if (this.cursorCol < this.columns.length - 1) {
404
+ this.cursorCol++;
405
+ // Adjust scrollCol if needed in render
406
+ }
407
+ return;
408
+ }
409
+ }
410
+ adjustScroll() {
411
+ const visibleDataRows = this.getVisibleDataRowCount();
412
+ if (this.cursorRow < this.scrollRow) {
413
+ this.scrollRow = this.cursorRow;
414
+ }
415
+ else if (this.cursorRow >= this.scrollRow + visibleDataRows) {
416
+ this.scrollRow = this.cursorRow - visibleDataRows + 1;
417
+ }
418
+ }
419
+ getVisibleDataRowCount() {
420
+ return Math.max(1, this.termRows - 4); // 1 title, 1 header, 1 divider, 1 status
421
+ }
422
+ calculateColumnWidths() {
423
+ const widths = {};
424
+ const defaultWidth = 14;
425
+ for (const col of this.columns) {
426
+ widths[col] = Math.max(col.length, 8);
427
+ }
428
+ if (this.autoFitCols) {
429
+ // Sample visible rows
430
+ const indices = this.getEffectiveRowIndices();
431
+ const sampleSlice = indices.slice(this.scrollRow, this.scrollRow + 50);
432
+ for (const idx of sampleSlice) {
433
+ const row = this.loadedRows[idx];
434
+ if (!row)
435
+ continue;
436
+ for (const col of this.columns) {
437
+ const val = row[col];
438
+ const str = val === null || val === undefined ? "null" : String(val);
439
+ widths[col] = Math.min(32, Math.max(widths[col] || 8, str.length));
440
+ }
441
+ }
442
+ }
443
+ else {
444
+ for (const col of this.columns) {
445
+ widths[col] = defaultWidth;
446
+ }
447
+ }
448
+ return widths;
449
+ }
450
+ /**
451
+ * Main rendering routine that constructs and prints a single terminal frame.
452
+ */
453
+ render() {
454
+ if (!this.isRunning)
455
+ return;
456
+ const visibleRowsCount = this.getVisibleDataRowCount();
457
+ const effectiveIndices = this.getEffectiveRowIndices();
458
+ const totalEffectiveRows = effectiveIndices.length;
459
+ const colWidths = this.calculateColumnWidths();
460
+ const rowNumWidth = Math.max(5, String(totalEffectiveRows).length + 2);
461
+ let availableCols = this.termCols - rowNumWidth - 3; // for borders & padding
462
+ // Determine visible columns based on scrollCol and available width
463
+ const visibleCols = [];
464
+ let usedColsWidth = 0;
465
+ for (let c = this.scrollCol; c < this.columns.length; c++) {
466
+ const colName = this.columns[c];
467
+ const width = (colWidths[colName] || 12) + 2; // +2 for padding
468
+ if (visibleCols.length > 0 && usedColsWidth + width > availableCols) {
469
+ break;
470
+ }
471
+ visibleCols.push(colName);
472
+ usedColsWidth += width;
473
+ }
474
+ // Adjust scrollCol if cursorCol is beyond visible columns
475
+ if (this.cursorCol < this.scrollCol) {
476
+ this.scrollCol = this.cursorCol;
477
+ }
478
+ else if (this.cursorCol >= this.scrollCol + visibleCols.length) {
479
+ this.scrollCol = Math.max(0, this.cursorCol - visibleCols.length + 1);
480
+ }
481
+ const lines = [];
482
+ // -------------------------------------------------------------
483
+ // Line 1: Title & Summary Bar
484
+ // -------------------------------------------------------------
485
+ const titleText = this.options.title || this.options.filePath || "Rowpipe Interactive Viewer";
486
+ const eofBadge = this.isStreamExhausted ? green("● EOF") : yellow("⏳ Streaming");
487
+ const sortBadge = this.sortColumn
488
+ ? magenta(` [Sort: ${this.sortColumn} ${this.sortAsc ? "▲" : "▼"}]`)
489
+ : "";
490
+ const searchBadge = this.searchQuery ? yellow(` [Filter: "${this.searchQuery}"]`) : "";
491
+ const titleLeft = ` ${bold(cyan("ROWPIPE"))} ${dim("│")} ${bold(titleText)}${sortBadge}${searchBadge}`;
492
+ const titleRight = `${eofBadge} ${dim("│")} ${formatNumber(totalEffectiveRows)} rows ${dim("│")} ${this.columns.length} cols `;
493
+ const titlePadding = Math.max(0, this.termCols - stripAnsi(titleLeft).length - stripAnsi(titleRight).length);
494
+ lines.push(bgGray(`${titleLeft}${" ".repeat(titlePadding)}${titleRight}`));
495
+ // -------------------------------------------------------------
496
+ // Line 2: Table Column Headers
497
+ // -------------------------------------------------------------
498
+ let headerLine = dim("#".padStart(rowNumWidth)) + " " + dim("│");
499
+ for (const col of visibleCols) {
500
+ const width = colWidths[col] || 12;
501
+ const isActiveCol = this.columns[this.cursorCol] === col;
502
+ const isSortedCol = this.sortColumn === col;
503
+ const sortArrow = isSortedCol ? (this.sortAsc ? " ▲" : " ▼") : "";
504
+ let colLabel = truncateVisible(col + sortArrow, width);
505
+ colLabel = colLabel.padEnd(width);
506
+ if (isActiveCol) {
507
+ headerLine += ` ${bold(cyan(colLabel))} ${dim("│")}`;
508
+ }
509
+ else {
510
+ headerLine += ` ${bold(colLabel)} ${dim("│")}`;
511
+ }
512
+ }
513
+ lines.push(headerLine);
514
+ // -------------------------------------------------------------
515
+ // Line 3: Header Separator
516
+ // -------------------------------------------------------------
517
+ let divider = dim("─".repeat(rowNumWidth)) + dim("┼");
518
+ for (const col of visibleCols) {
519
+ const width = colWidths[col] || 12;
520
+ divider += dim("─".repeat(width + 2)) + dim("┼");
521
+ }
522
+ lines.push(divider);
523
+ // -------------------------------------------------------------
524
+ // Lines 4+: Data Rows
525
+ // -------------------------------------------------------------
526
+ for (let i = 0; i < visibleRowsCount; i++) {
527
+ const rowIdxInView = this.scrollRow + i;
528
+ if (rowIdxInView >= totalEffectiveRows) {
529
+ // Empty placeholder row
530
+ lines.push(dim("~"));
531
+ continue;
532
+ }
533
+ const originalRowIdx = effectiveIndices[rowIdxInView];
534
+ const row = this.loadedRows[originalRowIdx];
535
+ const isCursorRow = rowIdxInView === this.cursorRow;
536
+ const rowNumStr = String(rowIdxInView + 1).padStart(rowNumWidth);
537
+ let rowLine = isCursorRow ? bold(cyan(rowNumStr)) : dim(rowNumStr);
538
+ rowLine += " " + dim("│");
539
+ for (const col of visibleCols) {
540
+ const width = colWidths[col] || 12;
541
+ const val = row?.[col];
542
+ let cellText = "";
543
+ if (val === null || val === undefined) {
544
+ cellText = dim("null").padEnd(width);
545
+ }
546
+ else if (typeof val === "number" || typeof val === "bigint") {
547
+ cellText = yellow(String(val)).padStart(width);
548
+ }
549
+ else if (typeof val === "boolean") {
550
+ cellText = magenta(val ? "true" : "false").padEnd(width);
551
+ }
552
+ else {
553
+ const str = String(val);
554
+ const truncated = truncateVisible(str, width).padEnd(width);
555
+ // Highlight search matches
556
+ if (this.searchQuery && str.toLowerCase().includes(this.searchQuery.toLowerCase())) {
557
+ cellText = bold(yellow(truncated));
558
+ }
559
+ else {
560
+ cellText = truncated;
561
+ }
562
+ }
563
+ rowLine += ` ${cellText} ${dim("│")}`;
564
+ }
565
+ if (isCursorRow) {
566
+ lines.push(inverse(rowLine));
567
+ }
568
+ else {
569
+ lines.push(rowLine);
570
+ }
571
+ }
572
+ // -------------------------------------------------------------
573
+ // Bottom Status / Command Bar
574
+ // -------------------------------------------------------------
575
+ let statusBar = "";
576
+ if (this.mode === "search") {
577
+ statusBar = bgBlue(` SEARCH: /${this.searchInput}█ `) + ` (Enter: apply, Esc: cancel, n/N: next/prev)`;
578
+ }
579
+ else if (this.mode === "jump") {
580
+ statusBar = bgBlue(` JUMP TO ROW #: ${this.jumpInput}█ `) + ` (Enter: go, Esc: cancel)`;
581
+ }
582
+ else if (this.mode === "help") {
583
+ statusBar = bgBlue(" HELP MODE ") + ` (Press ? or Esc to close)`;
584
+ }
585
+ else {
586
+ const posInfo = `Row ${formatNumber(this.cursorRow + 1)}/${formatNumber(totalEffectiveRows)} │ Col ${this.cursorCol + 1}/${this.columns.length}`;
587
+ const shortcuts = `[?]Help [/]Search [s]Sort [c]Width [g/G]Top/End [:]Jump [q]Quit`;
588
+ const padding = Math.max(1, this.termCols - posInfo.length - shortcuts.length - 3);
589
+ statusBar = bgGray(` ${posInfo}${" ".repeat(padding)}${shortcuts} `);
590
+ }
591
+ lines.push(statusBar);
592
+ // -------------------------------------------------------------
593
+ // Modal Overlay for Help Mode
594
+ // -------------------------------------------------------------
595
+ if (this.mode === "help") {
596
+ this.drawHelpModal(lines);
597
+ }
598
+ // Frame output to terminal
599
+ process.stdout.write(ANSI.cursorHome + lines.slice(0, this.termRows).join("\n"));
600
+ }
601
+ drawHelpModal(lines) {
602
+ const helpBox = [
603
+ "┌────────────────────────────────────────────────────────┐",
604
+ "│ ROWPIPE INTERACTIVE VIEWER HELP │",
605
+ "├────────────────────────────────────────────────────────┤",
606
+ "│ Navigation: │",
607
+ "│ ↑ / k / w : Move cursor up 1 row │",
608
+ "│ ↓ / j / s : Move cursor down 1 row │",
609
+ "│ ← / h / a : Scroll left column │",
610
+ "│ → / l / d / Tab : Scroll right column │",
611
+ "│ PageUp / b : Page up │",
612
+ "│ PageDown / Space : Page down │",
613
+ "│ Home / g : Jump to top of dataset │",
614
+ "│ End / G : Jump to end of loaded stream │",
615
+ "│ │",
616
+ "│ Data Operations: │",
617
+ "│ / : Live text search & row filter │",
618
+ "│ n / N : Jump to next / previous match │",
619
+ "│ s : Cycle sort order on active col │",
620
+ "│ c : Toggle auto-fit column width │",
621
+ "│ : or J : Jump to specific row number │",
622
+ "│ │",
623
+ "│ Exit: │",
624
+ "│ q / Esc / Ctrl+C : Quit viewer & restore terminal │",
625
+ "└────────────────────────────────────────────────────────┘",
626
+ ];
627
+ const startRow = Math.max(2, Math.floor((this.termRows - helpBox.length) / 2));
628
+ const startCol = Math.max(0, Math.floor((this.termCols - 60) / 2));
629
+ for (let r = 0; r < helpBox.length; r++) {
630
+ const lineIdx = startRow + r;
631
+ if (lineIdx < lines.length) {
632
+ const boxLine = helpBox[r];
633
+ const currentLine = lines[lineIdx] || "";
634
+ const paddedBox = " ".repeat(startCol) + bold(cyan(boxLine));
635
+ lines[lineIdx] = paddedBox;
636
+ }
637
+ }
638
+ }
639
+ /**
640
+ * Non-interactive fallback printer for scripts, CI, and non-TTY pipes.
641
+ */
642
+ renderNonInteractive(maxRows = 50) {
643
+ const colWidths = this.calculateColumnWidths();
644
+ const rowsToPrint = this.loadedRows.slice(0, maxRows);
645
+ let headerLine = dim("#".padStart(5)) + " │";
646
+ for (const col of this.columns) {
647
+ const width = colWidths[col] || 12;
648
+ headerLine += ` ${bold(col.padEnd(width))} │`;
649
+ }
650
+ let divider = dim("─".repeat(5)) + "┼";
651
+ for (const col of this.columns) {
652
+ const width = colWidths[col] || 12;
653
+ divider += "─".repeat(width + 2) + "┼";
654
+ }
655
+ process.stdout.write(headerLine + "\n");
656
+ process.stdout.write(divider + "\n");
657
+ for (let i = 0; i < rowsToPrint.length; i++) {
658
+ const row = rowsToPrint[i];
659
+ let rowLine = dim(String(i + 1).padStart(5)) + " │";
660
+ for (const col of this.columns) {
661
+ const width = colWidths[col] || 12;
662
+ const val = row[col];
663
+ const cell = val === null || val === undefined ? "null" : String(val);
664
+ rowLine += ` ${cell.padEnd(width)} │`;
665
+ }
666
+ process.stdout.write(rowLine + "\n");
667
+ }
668
+ if (this.loadedRows.length > maxRows || !this.isStreamExhausted) {
669
+ process.stdout.write(dim(`\nShowing first ${rowsToPrint.length} rows (Stream has more rows). Run in interactive terminal to explore full dataset.\n`));
670
+ }
671
+ }
672
+ }
673
+ //# sourceMappingURL=viewer.js.map