@@ -12,15 +12,16 @@ export function getDefaultFormat(): OutputFormat {
1212export function formatOutput (
1313 data : unknown ,
1414 format : OutputFormat ,
15- columns ?: string [ ]
15+ columns ?: string [ ] ,
16+ params ?: Record < string , unknown >
1617) : string {
1718 switch ( format ) {
1819 case "json" :
1920 return JSON . stringify ( data , null , 2 ) ;
2021 case "pretty" :
2122 return colorizeJson ( data ) ;
2223 case "table" :
23- return formatTable ( data , columns ) ;
24+ return formatTable ( data , columns , params ) ;
2425 default : {
2526 const _exhaustive : never = format ;
2627 throw new Error ( `Unknown format: ${ _exhaustive } ` ) ;
@@ -58,78 +59,99 @@ function colorizeJson(data: unknown, indent = 0): string {
5859 return String ( data ) ;
5960}
6061
61- function findTableData ( data : unknown ) : unknown [ ] | null {
62- if ( Array . isArray ( data ) ) return data ;
62+ interface TableData {
63+ rows : unknown [ ] ;
64+ metadata : Record < string , unknown > ;
65+ }
66+
67+ function findTableData ( data : unknown ) : TableData | null {
68+ if ( Array . isArray ( data ) ) return { rows : data , metadata : { } } ;
6369 if ( data && typeof data === "object" ) {
6470 const entries = Object . entries ( data as Record < string , unknown > ) ;
65- for ( const [ , value ] of entries ) {
66- if ( Array . isArray ( value ) && value . length > 0 ) {
67- return value ;
71+ for ( const [ arrayKey , value ] of entries ) {
72+ if ( Array . isArray ( value ) ) {
73+ const metadata : Record < string , unknown > = { } ;
74+ for ( const [ key , val ] of entries ) {
75+ if ( key !== arrayKey ) metadata [ key ] = val ;
76+ }
77+ return { rows : value , metadata } ;
6878 }
6979 }
7080 }
7181 return null ;
7282}
7383
74- function formatTable ( data : unknown , columns ?: string [ ] ) : string {
84+ function collectKeys ( rows : unknown [ ] ) : string [ ] {
85+ const keys = new Set < string > ( ) ;
86+ for ( const item of rows ) {
87+ if ( item && typeof item === "object" ) {
88+ for ( const k of Object . keys ( item as Record < string , unknown > ) ) {
89+ keys . add ( k ) ;
90+ }
91+ }
92+ }
93+ return [ ...keys ] ;
94+ }
95+
96+ function resolveColumns (
97+ requested : string [ ] | undefined ,
98+ available : string [ ]
99+ ) : string [ ] {
100+ if ( ! requested || requested . length === 0 ) return available ;
101+
102+ const validSet = new Set ( available ) ;
103+ const invalid = requested . filter ( ( c ) => ! validSet . has ( c ) ) ;
104+ if ( invalid . length > 0 ) {
105+ console . error (
106+ // eslint-disable-line no-console
107+ `Warning: unknown column(s): ${ invalid . join ( ", " ) } . Available: ${ available . join ( ", " ) } `
108+ ) ;
109+ }
110+ const valid = requested . filter ( ( c ) => validSet . has ( c ) ) ;
111+ return valid . length > 0 ? valid : available ;
112+ }
113+
114+ function formatTable (
115+ data : unknown ,
116+ columns ?: string [ ] ,
117+ params ?: Record < string , unknown >
118+ ) : string {
75119 if ( data === null || data === undefined ) {
76120 return theme . muted ( "(empty)" ) ;
77121 }
78122
79- const arrayData = findTableData ( data ) ;
80-
81- if ( arrayData && arrayData . length > 0 ) {
82- const firstItem = arrayData [ 0 ] ;
83- if ( firstItem && typeof firstItem === "object" ) {
84- let selectedColumns : string [ ] ;
85- if ( columns && columns . length > 0 ) {
86- const allKeys = new Set < string > ( ) ;
87- for ( const item of arrayData ) {
88- if ( item && typeof item === "object" ) {
89- Object . keys ( item as Record < string , unknown > ) . forEach ( ( k ) =>
90- allKeys . add ( k )
91- ) ;
92- }
93- }
94- const invalid = columns . filter ( ( c ) => ! allKeys . has ( c ) ) ;
95- if ( invalid . length > 0 ) {
96- console . error (
97- // eslint-disable-line no-console
98- `Warning: unknown column(s): ${ invalid . join ( ", " ) } . Available: ${ [ ...allKeys ] . join ( ", " ) } `
99- ) ;
100- }
101- selectedColumns = columns . filter ( ( c ) => allKeys . has ( c ) ) ;
102- if ( selectedColumns . length === 0 ) {
103- selectedColumns = [ ...allKeys ] ;
104- }
105- } else {
106- const allKeys = new Set < string > ( ) ;
107- for ( const item of arrayData ) {
108- if ( item && typeof item === "object" ) {
109- Object . keys ( item as Record < string , unknown > ) . forEach ( ( k ) =>
110- allKeys . add ( k )
111- ) ;
112- }
113- }
114- selectedColumns = [ ...allKeys ] ;
115- }
123+ const tableData = findTableData ( data ) ;
124+
125+ if ( tableData ) {
126+ const { rows, metadata } = tableData ;
127+ const meta = formatMetadata ( metadata , rows . length , params ) ;
128+
129+ if ( rows . length === 0 ) {
130+ return meta ? meta . trimStart ( ) : theme . muted ( "No results" ) ;
131+ }
132+
133+ const isObjectRows = rows [ 0 ] && typeof rows [ 0 ] === "object" ;
134+ if ( isObjectRows ) {
135+ const selectedColumns = resolveColumns ( columns , collectKeys ( rows ) ) ;
116136 const table = new Table ( {
117137 head : selectedColumns . map ( ( c ) => theme . bold ( c ) ) ,
118138 wordWrap : true ,
119139 } ) ;
120- for ( const item of arrayData ) {
140+ for ( const item of rows ) {
121141 const obj = ( item ?? { } ) as Record < string , unknown > ;
122142 table . push ( selectedColumns . map ( ( col ) => formatCellValue ( obj [ col ] ) ) ) ;
123143 }
124- return table . toString ( ) ;
144+ return table . toString ( ) + meta ;
125145 }
146+
126147 const table = new Table ( { head : [ theme . bold ( "Value" ) ] } ) ;
127- for ( const item of arrayData ) {
148+ for ( const item of rows ) {
128149 table . push ( [ formatCellValue ( item ) ] ) ;
129150 }
130- return table . toString ( ) ;
151+ return table . toString ( ) + meta ;
131152 }
132153
154+ // Single object: key-value table
133155 if ( data && typeof data === "object" && ! Array . isArray ( data ) ) {
134156 const entries = Object . entries ( data as Record < string , unknown > ) ;
135157 const filteredEntries =
@@ -148,6 +170,39 @@ function formatTable(data: unknown, columns?: string[]): string {
148170 return String ( data ) ;
149171}
150172
173+ function formatMetadata (
174+ metadata : Record < string , unknown > ,
175+ rowCount : number ,
176+ params ?: Record < string , unknown >
177+ ) : string {
178+ const totalKey = Object . keys ( metadata ) . find (
179+ ( k ) => k . startsWith ( "total" ) && typeof metadata [ k ] === "number"
180+ ) ;
181+ const total = totalKey ? ( metadata [ totalKey ] as number ) : undefined ;
182+ const hasMore = typeof metadata . nextPageUrl === "string" ;
183+
184+ if ( total === undefined && ! hasMore ) return "" ;
185+
186+ const page = typeof params ?. page === "number" ? params . page : 1 ;
187+ const pageSize =
188+ typeof params ?. pageSize === "number" ? params . pageSize : rowCount ;
189+ const start = ( page - 1 ) * pageSize + 1 ;
190+ const end = start + rowCount - 1 ;
191+
192+ let summary : string ;
193+ if ( rowCount === 0 ) {
194+ summary =
195+ total !== undefined ? `No results (${ total } total)` : "No results" ;
196+ } else if ( total !== undefined ) {
197+ summary = `Showing ${ start } -${ end } of ${ total } ` ;
198+ } else {
199+ summary = `Showing ${ start } -${ end } ` ;
200+ }
201+ if ( hasMore ) summary += ` (next: --page ${ page + 1 } --pageSize ${ pageSize } )` ;
202+
203+ return "\n" + theme . muted ( summary ) ;
204+ }
205+
151206function formatCellValue ( value : unknown ) : string {
152207 if ( value === null || value === undefined ) return theme . muted ( "null" ) ;
153208 if ( typeof value === "object" ) {
0 commit comments