@delma/fylo 1.1.2 → 2.0.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 (66) hide show
  1. package/README.md +141 -62
  2. package/eslint.config.js +8 -4
  3. package/package.json +9 -7
  4. package/src/CLI +16 -14
  5. package/src/adapters/cipher.ts +12 -6
  6. package/src/adapters/redis.ts +193 -123
  7. package/src/adapters/s3.ts +6 -12
  8. package/src/core/collection.ts +5 -0
  9. package/src/core/directory.ts +120 -151
  10. package/src/core/extensions.ts +4 -2
  11. package/src/core/format.ts +390 -419
  12. package/src/core/parser.ts +167 -142
  13. package/src/core/query.ts +31 -26
  14. package/src/core/walker.ts +68 -61
  15. package/src/core/write-queue.ts +7 -4
  16. package/src/engines/s3-files.ts +888 -0
  17. package/src/engines/types.ts +21 -0
  18. package/src/index.ts +754 -378
  19. package/src/migrate-cli.ts +22 -0
  20. package/src/migrate.ts +74 -0
  21. package/src/types/bun-runtime.d.ts +73 -0
  22. package/src/types/fylo.d.ts +115 -27
  23. package/src/types/node-runtime.d.ts +61 -0
  24. package/src/types/query.d.ts +6 -2
  25. package/src/types/vendor-modules.d.ts +8 -7
  26. package/src/worker.ts +7 -1
  27. package/src/workers/write-worker.ts +25 -24
  28. package/tests/collection/truncate.test.js +35 -0
  29. package/tests/{data.ts → data.js} +8 -21
  30. package/tests/{index.ts → index.js} +4 -9
  31. package/tests/integration/aws-s3-files.canary.test.js +22 -0
  32. package/tests/integration/{create.test.ts → create.test.js} +13 -31
  33. package/tests/integration/delete.test.js +95 -0
  34. package/tests/integration/{edge-cases.test.ts → edge-cases.test.js} +50 -124
  35. package/tests/integration/{encryption.test.ts → encryption.test.js} +20 -65
  36. package/tests/integration/{export.test.ts → export.test.js} +8 -23
  37. package/tests/integration/{join-modes.test.ts → join-modes.test.js} +37 -104
  38. package/tests/integration/migration.test.js +38 -0
  39. package/tests/integration/nested.test.js +142 -0
  40. package/tests/integration/operators.test.js +122 -0
  41. package/tests/integration/{queue.test.ts → queue.test.js} +24 -40
  42. package/tests/integration/read.test.js +119 -0
  43. package/tests/integration/rollback.test.js +60 -0
  44. package/tests/integration/s3-files.test.js +108 -0
  45. package/tests/integration/update.test.js +99 -0
  46. package/tests/mocks/{cipher.ts → cipher.js} +11 -26
  47. package/tests/mocks/redis.js +123 -0
  48. package/tests/mocks/{s3.ts → s3.js} +24 -58
  49. package/tests/schemas/album.json +1 -1
  50. package/tests/schemas/comment.json +1 -1
  51. package/tests/schemas/photo.json +1 -1
  52. package/tests/schemas/post.json +1 -1
  53. package/tests/schemas/tip.json +1 -1
  54. package/tests/schemas/todo.json +1 -1
  55. package/tests/schemas/user.d.ts +12 -12
  56. package/tests/schemas/user.json +1 -1
  57. package/tsconfig.json +4 -2
  58. package/tsconfig.typecheck.json +31 -0
  59. package/tests/collection/truncate.test.ts +0 -56
  60. package/tests/integration/delete.test.ts +0 -147
  61. package/tests/integration/nested.test.ts +0 -212
  62. package/tests/integration/operators.test.ts +0 -167
  63. package/tests/integration/read.test.ts +0 -203
  64. package/tests/integration/rollback.test.ts +0 -105
  65. package/tests/integration/update.test.ts +0 -130
  66. package/tests/mocks/redis.ts +0 -169
@@ -1,486 +1,457 @@
1
1
  import TTID from '@delma/ttid'
2
2
 
3
3
  class Format {
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 =
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
- const { maxWidths, maxHeight } = this.getHeaderDim(Object.values(docs));
8
+ const { maxWidths, maxHeight } = this.getHeaderDim(Object.values(docs))
10
9
 
11
- let key = Object.keys(docs).shift()!
10
+ let key = Object.keys(docs).shift()!
12
11
 
13
- const keys = key.split(',')
12
+ const keys = key.split(',')
14
13
 
15
- if(TTID.isTTID(key) || keys.some(key => TTID.isTTID(key ?? ''))) {
16
- key = '_id'
17
- } else key = '_key'
14
+ if (TTID.isTTID(key) || keys.some((key) => TTID.isTTID(key ?? ''))) {
15
+ key = '_id'
16
+ } else key = '_key'
18
17
 
19
- // Add the _id column to the front of maxWidths
20
- const fullWidths = {
21
- [key]: idColumnWidth,
22
- ...maxWidths,
23
- };
18
+ // Add the _id column to the front of maxWidths
19
+ const fullWidths = {
20
+ [key]: idColumnWidth,
21
+ ...maxWidths
22
+ }
24
23
 
25
- // Render the header
26
- const header = this.renderHeader(fullWidths, maxHeight, key);
27
- console.log("\n" + header);
24
+ // Render the header
25
+ const header = this.renderHeader(fullWidths, maxHeight, key)
26
+ console.log('\n' + header)
28
27
 
29
- // Render the data rows
30
- const dataRows = this.renderDataRows(docs, fullWidths, key);
31
- console.log(dataRows);
32
- }
28
+ // Render the data rows
29
+ const dataRows = this.renderDataRows(docs, fullWidths, key)
30
+ console.log(dataRows)
31
+ }
33
32
 
34
- private static getHeaderDim(docs: Record<string, any>[]) {
35
- let maxWidths: Record<string, any> = {};
36
- let maxHeight = 1;
33
+ private static getHeaderDim(docs: Record<string, any>[]) {
34
+ let maxWidths: Record<string, any> = {}
35
+ let maxHeight = 1
37
36
 
38
- // Create a copy to avoid mutating the original array
39
- const docsCopy = [...docs];
37
+ // Create a copy to avoid mutating the original array
38
+ const docsCopy = [...docs]
40
39
 
41
- while (docsCopy.length > 0) {
42
- const doc = docsCopy.shift()!;
43
- const widths = this.getValueWidth(doc);
44
- const height = this.getHeaderHeight(doc); // Fix: get height for this doc
45
- maxHeight = Math.max(maxHeight, height); // Fix: take maximum height
46
- maxWidths = this.increaseWidths(maxWidths, widths);
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
- return { maxWidths, maxHeight };
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
- return keyWidths;
72
- }
73
-
74
- private static increaseWidths(
75
- oldWidths: Record<string, any>,
76
- newWidths: Record<string, any>
77
- ) {
78
- const increasedWidths: Record<string, any> = { ...oldWidths };
79
-
80
- for (const key in newWidths) {
81
- if (
82
- oldWidths[key] &&
83
- typeof newWidths[key] === "object" &&
84
- typeof oldWidths[key] === "object"
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
- // Handle keys that exist in newWidths but not in oldWidths
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
- // Also ensure column family names fit within their total width
109
- for (const key in increasedWidths) {
110
- if (
111
- typeof increasedWidths[key] === "object" &&
112
- increasedWidths[key] !== null
113
- ) {
114
- const totalChildWidth = this.calculateTotalWidth(increasedWidths[key]);
115
- const keyWidth = key.length;
116
-
117
- // If the column family name (with padding) is longer than the total child width,
118
- // we need to adjust the child column widths proportionally
119
- const keyWidthWithPadding = keyWidth + 2; // Add padding for family name too
120
- if (keyWidthWithPadding > totalChildWidth) {
121
- const childKeys = Object.keys(increasedWidths[key]);
122
- const extraWidth = keyWidthWithPadding - totalChildWidth;
123
- const widthPerChild = Math.ceil(extraWidth / childKeys.length);
124
-
125
- for (const childKey of childKeys) {
126
- if (typeof increasedWidths[key][childKey] === "number") {
127
- increasedWidths[key][childKey] += widthPerChild;
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
- return increasedWidths;
135
- }
136
-
137
- private static getHeaderHeight(doc: Record<string, any>): number {
138
- let maxDepth = 1; // Fix: start with 1 for current level
139
-
140
- for (const key in doc) {
141
- if (
142
- typeof doc[key] === "object" &&
143
- doc[key] !== null &&
144
- !Array.isArray(doc[key])
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
- return maxDepth;
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
- private static renderHeader(
155
- widths: Record<string, any>,
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
- // Flatten the structure to get all columns
162
- const columns = this.flattenColumns(widths);
146
+ // Add top border
147
+ lines.push(this.renderTopBorder(columns))
163
148
 
164
- // Add top border
165
- lines.push(this.renderTopBorder(columns));
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
- // Add header content rows
168
- for (let level = 0; level < height; level++) {
169
- lines.push(this.renderHeaderRow(widths, level, height, idColumnKey));
159
+ // Add bottom border
160
+ lines.push(this.renderBottomBorder(columns))
170
161
 
171
- // Add middle border between levels (except after last level)
172
- if (level < height - 1) {
173
- lines.push(this.renderMiddleBorder(columns));
174
- }
162
+ return lines.join('\n')
175
163
  }
176
164
 
177
- // Add bottom border
178
- lines.push(this.renderBottomBorder(columns));
179
-
180
- return lines.join("\n");
181
- }
182
-
183
- private static renderDataRows<T extends Record<string, any>>(
184
- docs: Record<string, T>,
185
- widths: Record<string, any>,
186
- idColumnKey: string
187
- ): string {
188
- const lines: string[] = [];
189
- const columns = this.flattenColumns(widths);
190
- const entries = Object.entries(docs);
191
-
192
- for (let i = 0; i < entries.length; i++) {
193
- const [docId, doc] = entries[i];
194
- // Render data row
195
- lines.push(this.renderDataRow(docId, doc, widths, columns, idColumnKey));
196
-
197
- // Add separator between rows (except for last row)
198
- if (i < entries.length - 1) {
199
- lines.push(this.renderRowSeparator(columns));
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
- // Add final bottom border
204
- lines.push(this.renderBottomBorder(columns));
205
-
206
- return lines.join("\n");
207
- }
208
-
209
- private static renderDataRow(
210
- docId: string,
211
- doc: Record<string, any>,
212
- widths: Record<string, any>,
213
- columns: Array<{ name: string; width: number; path: string[] }>,
214
- idColumnKey: string
215
- ): string {
216
- let line = "│";
217
-
218
- // Handle the ID column (could be _id or another key)
219
- if (idColumnKey in widths && typeof widths[idColumnKey] === "number") {
220
- const contentWidth = widths[idColumnKey] - 2;
221
- const content = docId;
222
- const padding = Math.max(0, contentWidth - content.length);
223
- const leftPad = Math.floor(padding / 2);
224
- const rightPad = padding - leftPad;
225
-
226
- line += " " + " ".repeat(leftPad) + content + " ".repeat(rightPad) + " │";
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
- // Handle data columns
230
- for (const column of columns) {
231
- // Skip the ID column as it's handled separately
232
- if (column.name === idColumnKey) continue;
233
-
234
- const value = this.getNestedValue(doc, column.path);
235
- const stringValue = this.formatValue(value);
236
- const contentWidth = column.width - 2; // Subtract padding
237
-
238
- // Truncate if value is too long
239
- const truncatedValue =
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
- return line;
257
- }
258
-
259
- private static getNestedValue(obj: Record<string, any>, path: string[]): any {
260
- let current = obj;
261
-
262
- for (const key of path) {
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
- return current;
274
- }
275
-
276
- private static formatValue(value: any): string {
277
- if (value === null) return "null";
278
- if (value === undefined) return "";
279
- if (Array.isArray(value)) return JSON.stringify(value);
280
- if (typeof value === "object") return JSON.stringify(value);
281
- if (typeof value === "string") return value;
282
- return String(value);
283
- }
284
-
285
- private static renderRowSeparator(
286
- columns: Array<{ name: string; width: number; path: string[] }>
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
- line += "┤";
298
- return line;
299
- }
300
-
301
- private static flattenColumns(
302
- widths: Record<string, any>,
303
- path: string[] = []
304
- ): Array<{ name: string; width: number; path: string[] }> {
305
- const columns: Array<{ name: string; width: number; path: string[] }> = [];
306
-
307
- for (const key in widths) {
308
- const currentPath = [...path, key];
309
-
310
- if (typeof widths[key] === "object" && widths[key] !== null) {
311
- // Recursively flatten nested objects
312
- columns.push(...this.flattenColumns(widths[key], currentPath));
313
- } else {
314
- // This is a leaf column
315
- columns.push({
316
- name: key,
317
- width: widths[key],
318
- path: currentPath,
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
- return columns;
324
- }
325
-
326
- private static renderHeaderRow(
327
- widths: Record<string, any>,
328
- currentLevel: number,
329
- totalHeight: number,
330
- idColumnKey: string
331
- ): string {
332
- let line = "│";
333
-
334
- // Handle the ID column specially (could be _id or another key)
335
- if (idColumnKey in widths && typeof widths[idColumnKey] === "number") {
336
- if (currentLevel === 0) {
337
- // Show the ID column header at the top level
338
- const contentWidth = widths[idColumnKey] - 2;
339
- const headerText = idColumnKey === '_id' ? '_id' : idColumnKey;
340
- const padding = Math.max(0, contentWidth - headerText.length);
341
- const leftPad = Math.floor(padding / 2);
342
- const rightPad = padding - leftPad;
343
-
344
- line += " " + " ".repeat(leftPad) + headerText + " ".repeat(rightPad) + " ";
345
- } else {
346
- // Empty cell for other levels
347
- line += " ".repeat(widths[idColumnKey]) + "│";
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
- const processLevel = (
352
- obj: Record<string, any>,
353
- level: number,
354
- targetLevel: number
355
- ): string => {
356
- let result = "";
357
-
358
- for (const key in obj) {
359
- // Skip the ID column as it's handled separately
360
- if (key === idColumnKey) continue;
361
-
362
- if (typeof obj[key] === "object" && obj[key] !== null) {
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
- return result;
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
- // Add space for separators between columns (one less than column count)
418
- return total + Math.max(0, columnCount - 1);
419
- }
392
+ private static countLeafColumns(obj: Record<string, any>): number {
393
+ let count = 0
420
394
 
421
- private static countLeafColumns(obj: Record<string, any>): number {
422
- let count = 0;
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
- for (const key in obj) {
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
- return count;
433
- }
406
+ private static renderTopBorder(
407
+ columns: Array<{ name: string; width: number; path: string[] }>
408
+ ): string {
409
+ let line = '┌'
434
410
 
435
- private static renderTopBorder(
436
- columns: Array<{ name: string; width: number; path: string[] }>
437
- ): string {
438
- let line = "┌";
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
- for (let i = 0; i < columns.length; i++) {
441
- line += "─".repeat(columns[i].width);
442
- if (i < columns.length - 1) {
443
- line += "┬";
444
- }
418
+ line += '┐'
419
+ return line
445
420
  }
446
421
 
447
- line += "┐";
448
- return line;
449
- }
422
+ private static renderMiddleBorder(
423
+ columns: Array<{ name: string; width: number; path: string[] }>
424
+ ): string {
425
+ let line = '├'
450
426
 
451
- private static renderMiddleBorder(
452
- columns: Array<{ name: string; width: number; path: string[] }>
453
- ): string {
454
- let line = "├";
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
- for (let i = 0; i < columns.length; i++) {
457
- line += "─".repeat(columns[i].width);
458
- if (i < columns.length - 1) {
459
- line += "┼";
460
- }
434
+ line += '┤'
435
+ return line
461
436
  }
462
437
 
463
- line += "┤";
464
- return line;
465
- }
438
+ private static renderBottomBorder(
439
+ columns: Array<{ name: string; width: number; path: string[] }>
440
+ ): string {
441
+ let line = '└'
466
442
 
467
- private static renderBottomBorder(
468
- columns: Array<{ name: string; width: number; path: string[] }>
469
- ): string {
470
- let line = "└";
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
- for (let i = 0; i < columns.length; i++) {
473
- line += "─".repeat(columns[i].width);
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
- Format.table(docs)
455
+ console.format = function (docs: Record<string, any>) {
456
+ Format.table(docs)
486
457
  }