@delma/fylo 1.1.2 → 2.0.1
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/README.md +141 -62
- package/eslint.config.js +8 -4
- package/package.json +9 -7
- package/src/CLI +16 -14
- package/src/adapters/cipher.ts +12 -6
- package/src/adapters/redis.ts +193 -123
- package/src/adapters/s3.ts +6 -12
- package/src/core/collection.ts +5 -0
- package/src/core/directory.ts +120 -151
- package/src/core/extensions.ts +4 -2
- package/src/core/format.ts +390 -419
- package/src/core/parser.ts +167 -142
- package/src/core/query.ts +31 -26
- package/src/core/walker.ts +68 -61
- package/src/core/write-queue.ts +7 -4
- package/src/engines/s3-files.ts +1068 -0
- package/src/engines/types.ts +21 -0
- package/src/index.ts +754 -378
- package/src/migrate-cli.ts +22 -0
- package/src/migrate.ts +74 -0
- package/src/types/bun-runtime.d.ts +73 -0
- package/src/types/fylo.d.ts +115 -27
- package/src/types/node-runtime.d.ts +61 -0
- package/src/types/query.d.ts +6 -2
- package/src/types/vendor-modules.d.ts +8 -7
- package/src/worker.ts +7 -1
- package/src/workers/write-worker.ts +25 -24
- package/tests/collection/truncate.test.js +35 -0
- package/tests/{data.ts → data.js} +8 -21
- package/tests/{index.ts → index.js} +4 -9
- package/tests/integration/aws-s3-files.canary.test.js +22 -0
- package/tests/integration/{create.test.ts → create.test.js} +13 -31
- package/tests/integration/delete.test.js +95 -0
- package/tests/integration/{edge-cases.test.ts → edge-cases.test.js} +50 -124
- package/tests/integration/{encryption.test.ts → encryption.test.js} +20 -65
- package/tests/integration/{export.test.ts → export.test.js} +8 -23
- package/tests/integration/{join-modes.test.ts → join-modes.test.js} +37 -104
- package/tests/integration/migration.test.js +38 -0
- package/tests/integration/nested.test.js +142 -0
- package/tests/integration/operators.test.js +122 -0
- package/tests/integration/{queue.test.ts → queue.test.js} +24 -40
- package/tests/integration/read.test.js +119 -0
- package/tests/integration/rollback.test.js +60 -0
- package/tests/integration/s3-files.test.js +192 -0
- package/tests/integration/update.test.js +99 -0
- package/tests/mocks/{cipher.ts → cipher.js} +11 -26
- package/tests/mocks/redis.js +123 -0
- package/tests/mocks/{s3.ts → s3.js} +24 -58
- package/tests/schemas/album.json +1 -1
- package/tests/schemas/comment.json +1 -1
- package/tests/schemas/photo.json +1 -1
- package/tests/schemas/post.json +1 -1
- package/tests/schemas/tip.json +1 -1
- package/tests/schemas/todo.json +1 -1
- package/tests/schemas/user.d.ts +12 -12
- package/tests/schemas/user.json +1 -1
- package/tsconfig.json +4 -2
- package/tsconfig.typecheck.json +31 -0
- package/tests/collection/truncate.test.ts +0 -56
- package/tests/integration/delete.test.ts +0 -147
- package/tests/integration/nested.test.ts +0 -212
- package/tests/integration/operators.test.ts +0 -167
- package/tests/integration/read.test.ts +0 -203
- package/tests/integration/rollback.test.ts +0 -105
- package/tests/integration/update.test.ts +0 -130
- package/tests/mocks/redis.ts +0 -169
package/src/core/format.ts
CHANGED
|
@@ -1,486 +1,457 @@
|
|
|
1
1
|
import TTID from '@delma/ttid'
|
|
2
2
|
|
|
3
3
|
class Format {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Math.max(...Object.keys(docs).map((key) => key.length)) + 2; // Add padding
|
|
4
|
+
static table(docs: Record<string, any>) {
|
|
5
|
+
// Calculate the _id column width (considering both the column name and the actual keys)
|
|
6
|
+
const idColumnWidth = Math.max(...Object.keys(docs).map((key) => key.length)) + 2 // Add padding
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
const { maxWidths, maxHeight } = this.getHeaderDim(Object.values(docs))
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
let key = Object.keys(docs).shift()!
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
const keys = key.split(',')
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
if (TTID.isTTID(key) || keys.some((key) => TTID.isTTID(key ?? ''))) {
|
|
15
|
+
key = '_id'
|
|
16
|
+
} else key = '_key'
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
// Add the _id column to the front of maxWidths
|
|
19
|
+
const fullWidths = {
|
|
20
|
+
[key]: idColumnWidth,
|
|
21
|
+
...maxWidths
|
|
22
|
+
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// Render the header
|
|
25
|
+
const header = this.renderHeader(fullWidths, maxHeight, key)
|
|
26
|
+
console.log('\n' + header)
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// Render the data rows
|
|
29
|
+
const dataRows = this.renderDataRows(docs, fullWidths, key)
|
|
30
|
+
console.log(dataRows)
|
|
31
|
+
}
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
private static getHeaderDim(docs: Record<string, any>[]) {
|
|
34
|
+
let maxWidths: Record<string, any> = {}
|
|
35
|
+
let maxHeight = 1
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// Create a copy to avoid mutating the original array
|
|
38
|
+
const docsCopy = [...docs]
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
while (docsCopy.length > 0) {
|
|
41
|
+
const doc = docsCopy.shift()!
|
|
42
|
+
const widths = this.getValueWidth(doc)
|
|
43
|
+
const height = this.getHeaderHeight(doc) // Fix: get height for this doc
|
|
44
|
+
maxHeight = Math.max(maxHeight, height) // Fix: take maximum height
|
|
45
|
+
maxWidths = this.increaseWidths(maxWidths, widths)
|
|
46
|
+
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private static getValueWidth(doc: Record<string, any>) {
|
|
53
|
-
const keyWidths: Record<string, any> = {};
|
|
54
|
-
|
|
55
|
-
for (const key in doc) {
|
|
56
|
-
if (
|
|
57
|
-
typeof doc[key] === "object" &&
|
|
58
|
-
doc[key] !== null &&
|
|
59
|
-
!Array.isArray(doc[key])
|
|
60
|
-
) {
|
|
61
|
-
keyWidths[key] = this.getValueWidth(doc[key]);
|
|
62
|
-
} else {
|
|
63
|
-
// Consider both the key name length and the value length
|
|
64
|
-
const valueWidth = JSON.stringify(doc[key]).length;
|
|
65
|
-
const keyWidth = key.length;
|
|
66
|
-
// Add padding: 1 space before + content + 1 space after
|
|
67
|
-
keyWidths[key] = Math.max(valueWidth, keyWidth) + 2;
|
|
68
|
-
}
|
|
48
|
+
return { maxWidths, maxHeight }
|
|
69
49
|
}
|
|
70
50
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
) {
|
|
86
|
-
increasedWidths[key] = this.increaseWidths(
|
|
87
|
-
oldWidths[key],
|
|
88
|
-
newWidths[key]
|
|
89
|
-
);
|
|
90
|
-
} else if (
|
|
91
|
-
oldWidths[key] &&
|
|
92
|
-
typeof newWidths[key] === "number" &&
|
|
93
|
-
typeof oldWidths[key] === "number"
|
|
94
|
-
) {
|
|
95
|
-
increasedWidths[key] = Math.max(newWidths[key], oldWidths[key]);
|
|
96
|
-
} else {
|
|
97
|
-
increasedWidths[key] = newWidths[key];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
51
|
+
private static getValueWidth(doc: Record<string, any>) {
|
|
52
|
+
const keyWidths: Record<string, any> = {}
|
|
53
|
+
|
|
54
|
+
for (const key in doc) {
|
|
55
|
+
if (typeof doc[key] === 'object' && doc[key] !== null && !Array.isArray(doc[key])) {
|
|
56
|
+
keyWidths[key] = this.getValueWidth(doc[key])
|
|
57
|
+
} else {
|
|
58
|
+
// Consider both the key name length and the value length
|
|
59
|
+
const valueWidth = JSON.stringify(doc[key]).length
|
|
60
|
+
const keyWidth = key.length
|
|
61
|
+
// Add padding: 1 space before + content + 1 space after
|
|
62
|
+
keyWidths[key] = Math.max(valueWidth, keyWidth) + 2
|
|
63
|
+
}
|
|
64
|
+
}
|
|
100
65
|
|
|
101
|
-
|
|
102
|
-
for (const key in newWidths) {
|
|
103
|
-
if (!(key in increasedWidths)) {
|
|
104
|
-
increasedWidths[key] = newWidths[key];
|
|
105
|
-
}
|
|
66
|
+
return keyWidths
|
|
106
67
|
}
|
|
107
68
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
69
|
+
private static increaseWidths(oldWidths: Record<string, any>, newWidths: Record<string, any>) {
|
|
70
|
+
const increasedWidths: Record<string, any> = { ...oldWidths }
|
|
71
|
+
|
|
72
|
+
for (const key in newWidths) {
|
|
73
|
+
if (
|
|
74
|
+
oldWidths[key] &&
|
|
75
|
+
typeof newWidths[key] === 'object' &&
|
|
76
|
+
typeof oldWidths[key] === 'object'
|
|
77
|
+
) {
|
|
78
|
+
increasedWidths[key] = this.increaseWidths(oldWidths[key], newWidths[key])
|
|
79
|
+
} else if (
|
|
80
|
+
oldWidths[key] &&
|
|
81
|
+
typeof newWidths[key] === 'number' &&
|
|
82
|
+
typeof oldWidths[key] === 'number'
|
|
83
|
+
) {
|
|
84
|
+
increasedWidths[key] = Math.max(newWidths[key], oldWidths[key])
|
|
85
|
+
} else {
|
|
86
|
+
increasedWidths[key] = newWidths[key]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle keys that exist in newWidths but not in oldWidths
|
|
91
|
+
for (const key in newWidths) {
|
|
92
|
+
if (!(key in increasedWidths)) {
|
|
93
|
+
increasedWidths[key] = newWidths[key]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Also ensure column family names fit within their total width
|
|
98
|
+
for (const key in increasedWidths) {
|
|
99
|
+
if (typeof increasedWidths[key] === 'object' && increasedWidths[key] !== null) {
|
|
100
|
+
const totalChildWidth = this.calculateTotalWidth(increasedWidths[key])
|
|
101
|
+
const keyWidth = key.length
|
|
102
|
+
|
|
103
|
+
// If the column family name (with padding) is longer than the total child width,
|
|
104
|
+
// we need to adjust the child column widths proportionally
|
|
105
|
+
const keyWidthWithPadding = keyWidth + 2 // Add padding for family name too
|
|
106
|
+
if (keyWidthWithPadding > totalChildWidth) {
|
|
107
|
+
const childKeys = Object.keys(increasedWidths[key])
|
|
108
|
+
const extraWidth = keyWidthWithPadding - totalChildWidth
|
|
109
|
+
const widthPerChild = Math.ceil(extraWidth / childKeys.length)
|
|
110
|
+
|
|
111
|
+
for (const childKey of childKeys) {
|
|
112
|
+
if (typeof increasedWidths[key][childKey] === 'number') {
|
|
113
|
+
increasedWidths[key][childKey] += widthPerChild
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
128
117
|
}
|
|
129
|
-
}
|
|
130
118
|
}
|
|
131
|
-
|
|
119
|
+
|
|
120
|
+
return increasedWidths
|
|
132
121
|
}
|
|
133
122
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
) {
|
|
146
|
-
const nestedDepth = 1 + this.getHeaderHeight(doc[key]); // Fix: add 1 for current level
|
|
147
|
-
maxDepth = Math.max(maxDepth, nestedDepth); // Fix: track maximum depth
|
|
148
|
-
}
|
|
123
|
+
private static getHeaderHeight(doc: Record<string, any>): number {
|
|
124
|
+
let maxDepth = 1 // Fix: start with 1 for current level
|
|
125
|
+
|
|
126
|
+
for (const key in doc) {
|
|
127
|
+
if (typeof doc[key] === 'object' && doc[key] !== null && !Array.isArray(doc[key])) {
|
|
128
|
+
const nestedDepth = 1 + this.getHeaderHeight(doc[key]) // Fix: add 1 for current level
|
|
129
|
+
maxDepth = Math.max(maxDepth, nestedDepth) // Fix: track maximum depth
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return maxDepth
|
|
149
134
|
}
|
|
150
135
|
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
private static renderHeader(
|
|
137
|
+
widths: Record<string, any>,
|
|
138
|
+
height: number,
|
|
139
|
+
idColumnKey: string
|
|
140
|
+
): string {
|
|
141
|
+
const lines: string[] = []
|
|
153
142
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
height: number,
|
|
157
|
-
idColumnKey: string
|
|
158
|
-
): string {
|
|
159
|
-
const lines: string[] = [];
|
|
143
|
+
// Flatten the structure to get all columns
|
|
144
|
+
const columns = this.flattenColumns(widths)
|
|
160
145
|
|
|
161
|
-
|
|
162
|
-
|
|
146
|
+
// Add top border
|
|
147
|
+
lines.push(this.renderTopBorder(columns))
|
|
163
148
|
|
|
164
|
-
|
|
165
|
-
|
|
149
|
+
// Add header content rows
|
|
150
|
+
for (let level = 0; level < height; level++) {
|
|
151
|
+
lines.push(this.renderHeaderRow(widths, level, height, idColumnKey))
|
|
152
|
+
|
|
153
|
+
// Add middle border between levels (except after last level)
|
|
154
|
+
if (level < height - 1) {
|
|
155
|
+
lines.push(this.renderMiddleBorder(columns))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
166
158
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
lines.push(this.renderHeaderRow(widths, level, height, idColumnKey));
|
|
159
|
+
// Add bottom border
|
|
160
|
+
lines.push(this.renderBottomBorder(columns))
|
|
170
161
|
|
|
171
|
-
|
|
172
|
-
if (level < height - 1) {
|
|
173
|
-
lines.push(this.renderMiddleBorder(columns));
|
|
174
|
-
}
|
|
162
|
+
return lines.join('\n')
|
|
175
163
|
}
|
|
176
164
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
165
|
+
private static renderDataRows<T extends Record<string, any>>(
|
|
166
|
+
docs: Record<string, T>,
|
|
167
|
+
widths: Record<string, any>,
|
|
168
|
+
idColumnKey: string
|
|
169
|
+
): string {
|
|
170
|
+
const lines: string[] = []
|
|
171
|
+
const columns = this.flattenColumns(widths)
|
|
172
|
+
const entries = Object.entries(docs)
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < entries.length; i++) {
|
|
175
|
+
const [docId, doc] = entries[i]
|
|
176
|
+
// Render data row
|
|
177
|
+
lines.push(this.renderDataRow(docId, doc, widths, columns, idColumnKey))
|
|
178
|
+
|
|
179
|
+
// Add separator between rows (except for last row)
|
|
180
|
+
if (i < entries.length - 1) {
|
|
181
|
+
lines.push(this.renderRowSeparator(columns))
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Add final bottom border
|
|
186
|
+
lines.push(this.renderBottomBorder(columns))
|
|
187
|
+
|
|
188
|
+
return lines.join('\n')
|
|
201
189
|
}
|
|
202
190
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
191
|
+
private static renderDataRow(
|
|
192
|
+
docId: string,
|
|
193
|
+
doc: Record<string, any>,
|
|
194
|
+
widths: Record<string, any>,
|
|
195
|
+
columns: Array<{ name: string; width: number; path: string[] }>,
|
|
196
|
+
idColumnKey: string
|
|
197
|
+
): string {
|
|
198
|
+
let line = '│'
|
|
199
|
+
|
|
200
|
+
// Handle the ID column (could be _id or another key)
|
|
201
|
+
if (idColumnKey in widths && typeof widths[idColumnKey] === 'number') {
|
|
202
|
+
const contentWidth = widths[idColumnKey] - 2
|
|
203
|
+
const content = docId
|
|
204
|
+
const padding = Math.max(0, contentWidth - content.length)
|
|
205
|
+
const leftPad = Math.floor(padding / 2)
|
|
206
|
+
const rightPad = padding - leftPad
|
|
207
|
+
|
|
208
|
+
line += ' ' + ' '.repeat(leftPad) + content + ' '.repeat(rightPad) + ' │'
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Handle data columns
|
|
212
|
+
for (const column of columns) {
|
|
213
|
+
// Skip the ID column as it's handled separately
|
|
214
|
+
if (column.name === idColumnKey) continue
|
|
215
|
+
|
|
216
|
+
const value = this.getNestedValue(doc, column.path)
|
|
217
|
+
const stringValue = this.formatValue(value)
|
|
218
|
+
const contentWidth = column.width - 2 // Subtract padding
|
|
219
|
+
|
|
220
|
+
// Truncate if value is too long
|
|
221
|
+
const truncatedValue =
|
|
222
|
+
stringValue.length > contentWidth
|
|
223
|
+
? stringValue.substring(0, contentWidth - 3) + '...'
|
|
224
|
+
: stringValue
|
|
225
|
+
|
|
226
|
+
const padding = Math.max(0, contentWidth - truncatedValue.length)
|
|
227
|
+
const leftPad = Math.floor(padding / 2)
|
|
228
|
+
const rightPad = padding - leftPad
|
|
229
|
+
|
|
230
|
+
line += ' ' + ' '.repeat(leftPad) + truncatedValue + ' '.repeat(rightPad) + ' │'
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return line
|
|
227
234
|
}
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
stringValue.length > contentWidth
|
|
241
|
-
? stringValue.substring(0, contentWidth - 3) + "..."
|
|
242
|
-
: stringValue;
|
|
243
|
-
|
|
244
|
-
const padding = Math.max(0, contentWidth - truncatedValue.length);
|
|
245
|
-
const leftPad = Math.floor(padding / 2);
|
|
246
|
-
const rightPad = padding - leftPad;
|
|
247
|
-
|
|
248
|
-
line +=
|
|
249
|
-
" " +
|
|
250
|
-
" ".repeat(leftPad) +
|
|
251
|
-
truncatedValue +
|
|
252
|
-
" ".repeat(rightPad) +
|
|
253
|
-
" │";
|
|
236
|
+
private static getNestedValue(obj: Record<string, any>, path: string[]): any {
|
|
237
|
+
let current = obj
|
|
238
|
+
|
|
239
|
+
for (const key of path) {
|
|
240
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
current = current[key]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return current
|
|
254
247
|
}
|
|
255
248
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
current === null ||
|
|
265
|
-
current === undefined ||
|
|
266
|
-
typeof current !== "object"
|
|
267
|
-
) {
|
|
268
|
-
return undefined;
|
|
269
|
-
}
|
|
270
|
-
current = current[key];
|
|
249
|
+
private static formatValue(value: any): string {
|
|
250
|
+
if (value === null) return 'null'
|
|
251
|
+
if (value === undefined) return ''
|
|
252
|
+
if (Array.isArray(value)) return JSON.stringify(value)
|
|
253
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
254
|
+
if (typeof value === 'string') return value
|
|
255
|
+
return String(value)
|
|
271
256
|
}
|
|
272
257
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
): string {
|
|
288
|
-
let line = "├";
|
|
289
|
-
|
|
290
|
-
for (let i = 0; i < columns.length; i++) {
|
|
291
|
-
line += "─".repeat(columns[i].width);
|
|
292
|
-
if (i < columns.length - 1) {
|
|
293
|
-
line += "┼";
|
|
294
|
-
}
|
|
258
|
+
private static renderRowSeparator(
|
|
259
|
+
columns: Array<{ name: string; width: number; path: string[] }>
|
|
260
|
+
): string {
|
|
261
|
+
let line = '├'
|
|
262
|
+
|
|
263
|
+
for (let i = 0; i < columns.length; i++) {
|
|
264
|
+
line += '─'.repeat(columns[i].width)
|
|
265
|
+
if (i < columns.length - 1) {
|
|
266
|
+
line += '┼'
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
line += '┤'
|
|
271
|
+
return line
|
|
295
272
|
}
|
|
296
273
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
274
|
+
private static flattenColumns(
|
|
275
|
+
widths: Record<string, any>,
|
|
276
|
+
path: string[] = []
|
|
277
|
+
): Array<{ name: string; width: number; path: string[] }> {
|
|
278
|
+
const columns: Array<{ name: string; width: number; path: string[] }> = []
|
|
279
|
+
|
|
280
|
+
for (const key in widths) {
|
|
281
|
+
const currentPath = [...path, key]
|
|
282
|
+
|
|
283
|
+
if (typeof widths[key] === 'object' && widths[key] !== null) {
|
|
284
|
+
// Recursively flatten nested objects
|
|
285
|
+
columns.push(...this.flattenColumns(widths[key], currentPath))
|
|
286
|
+
} else {
|
|
287
|
+
// This is a leaf column
|
|
288
|
+
columns.push({
|
|
289
|
+
name: key,
|
|
290
|
+
width: widths[key],
|
|
291
|
+
path: currentPath
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return columns
|
|
321
297
|
}
|
|
322
298
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
299
|
+
private static renderHeaderRow(
|
|
300
|
+
widths: Record<string, any>,
|
|
301
|
+
currentLevel: number,
|
|
302
|
+
totalHeight: number,
|
|
303
|
+
idColumnKey: string
|
|
304
|
+
): string {
|
|
305
|
+
let line = '│'
|
|
306
|
+
|
|
307
|
+
// Handle the ID column specially (could be _id or another key)
|
|
308
|
+
if (idColumnKey in widths && typeof widths[idColumnKey] === 'number') {
|
|
309
|
+
if (currentLevel === 0) {
|
|
310
|
+
// Show the ID column header at the top level
|
|
311
|
+
const contentWidth = widths[idColumnKey] - 2
|
|
312
|
+
const headerText = idColumnKey === '_id' ? '_id' : idColumnKey
|
|
313
|
+
const padding = Math.max(0, contentWidth - headerText.length)
|
|
314
|
+
const leftPad = Math.floor(padding / 2)
|
|
315
|
+
const rightPad = padding - leftPad
|
|
316
|
+
|
|
317
|
+
line += ' ' + ' '.repeat(leftPad) + headerText + ' '.repeat(rightPad) + ' │'
|
|
318
|
+
} else {
|
|
319
|
+
// Empty cell for other levels
|
|
320
|
+
line += ' '.repeat(widths[idColumnKey]) + '│'
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const processLevel = (
|
|
325
|
+
obj: Record<string, any>,
|
|
326
|
+
level: number,
|
|
327
|
+
targetLevel: number
|
|
328
|
+
): string => {
|
|
329
|
+
let result = ''
|
|
330
|
+
|
|
331
|
+
for (const key in obj) {
|
|
332
|
+
// Skip the ID column as it's handled separately
|
|
333
|
+
if (key === idColumnKey) continue
|
|
334
|
+
|
|
335
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
336
|
+
if (level === targetLevel) {
|
|
337
|
+
// This is a column family at the target level
|
|
338
|
+
const totalWidth = this.calculateTotalWidth(obj[key])
|
|
339
|
+
const contentWidth = totalWidth - 2 // Subtract padding
|
|
340
|
+
const padding = Math.max(0, contentWidth - key.length)
|
|
341
|
+
const leftPad = Math.floor(padding / 2)
|
|
342
|
+
const rightPad = padding - leftPad
|
|
343
|
+
|
|
344
|
+
// Add 1 space padding + centered content + 1 space padding
|
|
345
|
+
result += ' ' + ' '.repeat(leftPad) + key + ' '.repeat(rightPad) + ' │'
|
|
346
|
+
} else if (level < targetLevel) {
|
|
347
|
+
// Recurse deeper
|
|
348
|
+
result += processLevel(obj[key], level + 1, targetLevel)
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
if (level === targetLevel) {
|
|
352
|
+
// This is a leaf column at the target level
|
|
353
|
+
const contentWidth = obj[key] - 2 // Subtract padding
|
|
354
|
+
const padding = Math.max(0, contentWidth - key.length)
|
|
355
|
+
const leftPad = Math.floor(padding / 2)
|
|
356
|
+
const rightPad = padding - leftPad
|
|
357
|
+
|
|
358
|
+
// Add 1 space padding + centered content + 1 space padding
|
|
359
|
+
result += ' ' + ' '.repeat(leftPad) + key + ' '.repeat(rightPad) + ' │'
|
|
360
|
+
} else if (level < targetLevel) {
|
|
361
|
+
// Empty cell - span the full width
|
|
362
|
+
result += ' '.repeat(obj[key]) + '│'
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return result
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
line += processLevel(widths, 0, currentLevel)
|
|
371
|
+
return line
|
|
349
372
|
}
|
|
350
373
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (level === targetLevel) {
|
|
364
|
-
// This is a column family at the target level
|
|
365
|
-
const totalWidth = this.calculateTotalWidth(obj[key]);
|
|
366
|
-
const contentWidth = totalWidth - 2; // Subtract padding
|
|
367
|
-
const padding = Math.max(0, contentWidth - key.length);
|
|
368
|
-
const leftPad = Math.floor(padding / 2);
|
|
369
|
-
const rightPad = padding - leftPad;
|
|
370
|
-
|
|
371
|
-
// Add 1 space padding + centered content + 1 space padding
|
|
372
|
-
result +=
|
|
373
|
-
" " + " ".repeat(leftPad) + key + " ".repeat(rightPad) + " │";
|
|
374
|
-
} else if (level < targetLevel) {
|
|
375
|
-
// Recurse deeper
|
|
376
|
-
result += processLevel(obj[key], level + 1, targetLevel);
|
|
377
|
-
}
|
|
378
|
-
} else {
|
|
379
|
-
if (level === targetLevel) {
|
|
380
|
-
// This is a leaf column at the target level
|
|
381
|
-
const contentWidth = obj[key] - 2; // Subtract padding
|
|
382
|
-
const padding = Math.max(0, contentWidth - key.length);
|
|
383
|
-
const leftPad = Math.floor(padding / 2);
|
|
384
|
-
const rightPad = padding - leftPad;
|
|
385
|
-
|
|
386
|
-
// Add 1 space padding + centered content + 1 space padding
|
|
387
|
-
result +=
|
|
388
|
-
" " + " ".repeat(leftPad) + key + " ".repeat(rightPad) + " │";
|
|
389
|
-
} else if (level < targetLevel) {
|
|
390
|
-
// Empty cell - span the full width
|
|
391
|
-
result += " ".repeat(obj[key]) + "│";
|
|
392
|
-
}
|
|
374
|
+
private static calculateTotalWidth(obj: Record<string, any>): number {
|
|
375
|
+
let total = 0
|
|
376
|
+
let columnCount = 0
|
|
377
|
+
|
|
378
|
+
for (const key in obj) {
|
|
379
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
380
|
+
total += this.calculateTotalWidth(obj[key])
|
|
381
|
+
columnCount += this.countLeafColumns(obj[key])
|
|
382
|
+
} else {
|
|
383
|
+
total += obj[key]
|
|
384
|
+
columnCount++
|
|
385
|
+
}
|
|
393
386
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
line += processLevel(widths, 0, currentLevel);
|
|
400
|
-
return line;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
private static calculateTotalWidth(obj: Record<string, any>): number {
|
|
404
|
-
let total = 0;
|
|
405
|
-
let columnCount = 0;
|
|
406
|
-
|
|
407
|
-
for (const key in obj) {
|
|
408
|
-
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
409
|
-
total += this.calculateTotalWidth(obj[key]);
|
|
410
|
-
columnCount += this.countLeafColumns(obj[key]);
|
|
411
|
-
} else {
|
|
412
|
-
total += obj[key];
|
|
413
|
-
columnCount++;
|
|
414
|
-
}
|
|
387
|
+
|
|
388
|
+
// Add space for separators between columns (one less than column count)
|
|
389
|
+
return total + Math.max(0, columnCount - 1)
|
|
415
390
|
}
|
|
416
391
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
392
|
+
private static countLeafColumns(obj: Record<string, any>): number {
|
|
393
|
+
let count = 0
|
|
420
394
|
|
|
421
|
-
|
|
422
|
-
|
|
395
|
+
for (const key in obj) {
|
|
396
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
397
|
+
count += this.countLeafColumns(obj[key])
|
|
398
|
+
} else {
|
|
399
|
+
count++
|
|
400
|
+
}
|
|
401
|
+
}
|
|
423
402
|
|
|
424
|
-
|
|
425
|
-
if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
426
|
-
count += this.countLeafColumns(obj[key]);
|
|
427
|
-
} else {
|
|
428
|
-
count++;
|
|
429
|
-
}
|
|
403
|
+
return count
|
|
430
404
|
}
|
|
431
405
|
|
|
432
|
-
|
|
433
|
-
|
|
406
|
+
private static renderTopBorder(
|
|
407
|
+
columns: Array<{ name: string; width: number; path: string[] }>
|
|
408
|
+
): string {
|
|
409
|
+
let line = '┌'
|
|
434
410
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
411
|
+
for (let i = 0; i < columns.length; i++) {
|
|
412
|
+
line += '─'.repeat(columns[i].width)
|
|
413
|
+
if (i < columns.length - 1) {
|
|
414
|
+
line += '┬'
|
|
415
|
+
}
|
|
416
|
+
}
|
|
439
417
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (i < columns.length - 1) {
|
|
443
|
-
line += "┬";
|
|
444
|
-
}
|
|
418
|
+
line += '┐'
|
|
419
|
+
return line
|
|
445
420
|
}
|
|
446
421
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
422
|
+
private static renderMiddleBorder(
|
|
423
|
+
columns: Array<{ name: string; width: number; path: string[] }>
|
|
424
|
+
): string {
|
|
425
|
+
let line = '├'
|
|
450
426
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
427
|
+
for (let i = 0; i < columns.length; i++) {
|
|
428
|
+
line += '─'.repeat(columns[i].width)
|
|
429
|
+
if (i < columns.length - 1) {
|
|
430
|
+
line += '┼'
|
|
431
|
+
}
|
|
432
|
+
}
|
|
455
433
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (i < columns.length - 1) {
|
|
459
|
-
line += "┼";
|
|
460
|
-
}
|
|
434
|
+
line += '┤'
|
|
435
|
+
return line
|
|
461
436
|
}
|
|
462
437
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
438
|
+
private static renderBottomBorder(
|
|
439
|
+
columns: Array<{ name: string; width: number; path: string[] }>
|
|
440
|
+
): string {
|
|
441
|
+
let line = '└'
|
|
466
442
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
443
|
+
for (let i = 0; i < columns.length; i++) {
|
|
444
|
+
line += '─'.repeat(columns[i].width)
|
|
445
|
+
if (i < columns.length - 1) {
|
|
446
|
+
line += '┴'
|
|
447
|
+
}
|
|
448
|
+
}
|
|
471
449
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (i < columns.length - 1) {
|
|
475
|
-
line += "┴";
|
|
476
|
-
}
|
|
450
|
+
line += '┘'
|
|
451
|
+
return line
|
|
477
452
|
}
|
|
478
|
-
|
|
479
|
-
line += "┘";
|
|
480
|
-
return line;
|
|
481
|
-
}
|
|
482
453
|
}
|
|
483
454
|
|
|
484
|
-
console.format = function(docs: Record<string, any>) {
|
|
485
|
-
|
|
455
|
+
console.format = function (docs: Record<string, any>) {
|
|
456
|
+
Format.table(docs)
|
|
486
457
|
}
|